blob: 58d39564f558c382b824ac426c854097156a547f [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.Promise.UnhandledRejectionException;
import org.chromium.base.test.BaseRobolectricTestRunner;
/** Unit tests for {@link Promise}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class PromiseTest {
// We need a simple mutable reference type for testing.
private static class Value {
private int mValue;
public int get() {
return mValue;
}
public void set(int value) {
mValue = value;
}
}
/** Tests that the callback is called on fulfillment. */
@Test
public void callback() {
final Value value = new Value();
Promise<Integer> promise = new Promise<Integer>();
promise.then(PromiseTest.<Integer>setValue(value, 1));
assertEquals(value.get(), 0);
promise.fulfill(new Integer(1));
assertEquals(value.get(), 1);
}
/** Tests that multiple callbacks are called. */
@Test
public void multipleCallbacks() {
final Value value = new Value();
Promise<Integer> promise = new Promise<Integer>();
Callback<Integer> callback = new Callback<Integer>() {
@Override
public void onResult(Integer result) {
value.set(value.get() + 1);
}
};
promise.then(callback);
promise.then(callback);
assertEquals(value.get(), 0);
promise.fulfill(new Integer(0));
assertEquals(value.get(), 2);
}
/** Tests that a callback is called immediately when given to a fulfilled Promise. */
@Test
public void callbackOnFulfilled() {
final Value value = new Value();
Promise<Integer> promise = Promise.fulfilled(new Integer(0));
assertEquals(value.get(), 0);
promise.then(PromiseTest.<Integer>setValue(value, 1));
assertEquals(value.get(), 1);
}
/** Tests that promises can chain synchronous functions correctly. */
@Test
public void promiseChaining() {
Promise<Integer> promise = new Promise<Integer>();
final Value value = new Value();
promise.then(new Promise.Function<Integer, String>(){
@Override
public String apply(Integer arg) {
return arg.toString();
}
}).then(new Promise.Function<String, String>(){
@Override
public String apply(String arg) {
return arg + arg;
}
}).then(new Callback<String>() {
@Override
public void onResult(String result) {
value.set(result.length());
}
});
promise.fulfill(new Integer(123));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(6, value.get());
}
/** Tests that promises can chain asynchronous functions correctly. */
@Test
public void promiseChainingAsyncFunctions() {
Promise<Integer> promise = new Promise<Integer>();
final Value value = new Value();
final Promise<String> innerPromise = new Promise<String>();
promise.then(new Promise.AsyncFunction<Integer, String>() {
@Override
public Promise<String> apply(Integer arg) {
return innerPromise;
}
}).then(new Callback<String>(){
@Override
public void onResult(String result) {
value.set(result.length());
}
});
assertEquals(0, value.get());
promise.fulfill(5);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(0, value.get());
innerPromise.fulfill("abc");
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(3, value.get());
}
/** Tests that a Promise that does not use its result does not throw on rejection. */
@Test
public void rejectPromiseNoCallbacks() {
Promise<Integer> promise = new Promise<Integer>();
boolean caught = false;
try {
promise.reject();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
} catch (UnhandledRejectionException e) {
caught = true;
}
assertFalse(caught);
}
/** Tests that a Promise that uses its result throws on rejection if it has no handler. */
@Test
public void rejectPromiseNoHandler() {
Promise<Integer> promise = new Promise<Integer>();
promise.then(PromiseTest.<Integer>identity()).then(PromiseTest.<Integer>pass());
boolean caught = false;
try {
promise.reject();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
} catch (UnhandledRejectionException e) {
caught = true;
}
assertTrue(caught);
}
/** Tests that a Promise that handles rejection does not throw on rejection. */
@Test
public void rejectPromiseHandled() {
Promise<Integer> promise = new Promise<Integer>();
promise.then(PromiseTest.<Integer>identity())
.then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>pass());
boolean caught = false;
try {
promise.reject();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
} catch (UnhandledRejectionException e) {
caught = true;
}
assertFalse(caught);
}
/** Tests that rejections carry the exception information. */
@Test
public void rejectionInformation() {
Promise<Integer> promise = new Promise<Integer>();
promise.then(PromiseTest.<Integer>pass());
String message = "Promise Test";
try {
promise.reject(new NegativeArraySizeException(message));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
fail();
} catch (UnhandledRejectionException e) {
assertTrue(e.getCause() instanceof NegativeArraySizeException);
assertEquals(e.getCause().getMessage(), message);
}
}
/** Tests that rejections propagate. */
@Test
public void rejectionChaining() {
final Value value = new Value();
Promise<Integer> promise = new Promise<Integer>();
Promise<Integer> result =
promise.then(PromiseTest.<Integer>identity()).then(PromiseTest.<Integer>identity());
result.then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
promise.reject(new Exception());
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(value.get(), 5);
assertTrue(result.isRejected());
}
/** Tests that Promises get rejected if a Function throws. */
@Test
public void rejectOnThrow() {
Value value = new Value();
Promise<Integer> promise = new Promise<Integer>();
promise.then(new Promise.Function<Integer, Integer>() {
@Override
public Integer apply(Integer argument) {
throw new IllegalArgumentException();
}
}).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
promise.fulfill(0);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(value.get(), 5);
}
/** Tests that Promises get rejected if an AsyncFunction throws. */
@Test
public void rejectOnAsyncThrow() {
Value value = new Value();
Promise<Integer> promise = new Promise<Integer>();
promise.then(new Promise.AsyncFunction<Integer, Integer>() {
@Override
public Promise<Integer> apply(Integer argument) {
throw new IllegalArgumentException();
}
}).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
promise.fulfill(0);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(value.get(), 5);
}
/** Tests that Promises get rejected if an AsyncFunction rejects. */
@Test
public void rejectOnAsyncReject() {
Value value = new Value();
Promise<Integer> promise = new Promise<Integer>();
final Promise<Integer> inner = new Promise<Integer>();
promise.then(new Promise.AsyncFunction<Integer, Integer>() {
@Override
public Promise<Integer> apply(Integer argument) {
return inner;
}
}).then(PromiseTest.<Integer>pass(), PromiseTest.<Exception>setValue(value, 5));
promise.fulfill(0);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(value.get(), 0);
inner.reject();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertEquals(value.get(), 5);
}
/** Convenience method that returns a Callback that does nothing with its result. */
private static <T> Callback<T> pass() {
return new Callback<T>() {
@Override
public void onResult(T result) {}
};
}
/** Convenience method that returns a Function that just passes through its argument. */
private static <T> Promise.Function<T, T> identity() {
return new Promise.Function<T, T>() {
@Override
public T apply(T argument) {
return argument;
}
};
}
/** Convenience method that returns a Callback that sets the given Value on execution. */
private static <T> Callback<T> setValue(final Value toSet, final int value) {
return new Callback<T>() {
@Override
public void onResult(T result) {
toSet.set(value);
}
};
}
}