Index: base/android/junit/src/org/chromium/base/PromiseTest.java |
diff --git a/base/android/junit/src/org/chromium/base/PromiseTest.java b/base/android/junit/src/org/chromium/base/PromiseTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..63d35635cb603f8302b1f829278c12a9006b0ac5 |
--- /dev/null |
+++ b/base/android/junit/src/org/chromium/base/PromiseTest.java |
@@ -0,0 +1,312 @@ |
+// 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 org.chromium.base.Promise.UnhandledRejectionException; |
+import org.chromium.testing.local.LocalRobolectricTestRunner; |
+import org.junit.Test; |
+import org.junit.runner.RunWith; |
+import org.robolectric.annotation.Config; |
+import org.robolectric.shadows.ShadowHandler; |
+ |
+/** Unit tests for {@link Promise}. */ |
+@RunWith(LocalRobolectricTestRunner.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(this.<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(this.<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)); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ 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); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ assertEquals(0, value.get()); |
+ |
+ innerPromise.fulfill("abc"); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ 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(); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ } 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(this.<Integer>identity()).then(this.<Integer>pass()); |
+ |
+ boolean caught = false; |
+ try { |
+ promise.reject(); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ } 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(this.<Integer>identity()).then(this.<Integer>pass(), this.<Exception>pass()); |
+ |
+ boolean caught = false; |
+ try { |
+ promise.reject(); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ } 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(this.<Integer>pass()); |
+ |
+ String message = "Promise Test"; |
+ try { |
+ promise.reject(new NegativeArraySizeException(message)); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ } 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(this.<Integer>identity()).then(this.<Integer>identity()); |
+ |
+ result.then(this.<Integer>pass(), this.<Exception>setValue(value, 5)); |
+ |
+ promise.reject(new Exception()); |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ |
+ 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(this.<Integer>pass(), this.<Exception>setValue(value, 5)); |
+ |
+ promise.fulfill(0); |
+ |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ 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(this.<Integer>pass(), this.<Exception>setValue(value, 5)); |
+ |
+ promise.fulfill(0); |
+ |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ 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(this.<Integer>pass(), this.<Exception>setValue(value, 5)); |
+ |
+ promise.fulfill(0); |
+ |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ assertEquals(value.get(), 0); |
+ |
+ inner.reject(); |
+ |
+ ShadowHandler.runMainLooperToEndOfTasks(); |
+ 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); |
+ } |
+ }; |
+ } |
+} |