summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoResultTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoResultTest.java')
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoResultTest.java673
1 files changed, 673 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoResultTest.java b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoResultTest.java
new file mode 100644
index 0000000000..8ffd4bcbec
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoResultTest.java
@@ -0,0 +1,673 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+import android.os.Handler;
+import android.os.Looper;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.geckoview.GeckoResult;
+import org.mozilla.geckoview.test.util.Environment;
+import org.mozilla.geckoview.test.util.UiThreadUtils;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class GeckoResultTest {
+ private static class MockException extends RuntimeException {}
+
+ private boolean mDone;
+
+ private final Environment mEnv = new Environment();
+
+ private void waitUntilDone() {
+ assertThat("We should not be done", mDone, equalTo(false));
+ UiThreadUtils.waitForCondition(() -> mDone, mEnv.getDefaultTimeoutMillis());
+ }
+
+ private void done() {
+ UiThreadUtils.HANDLER.post(() -> mDone = true);
+ }
+
+ @Before
+ public void setup() {
+ mDone = false;
+ }
+
+ @Test
+ @UiThreadTest
+ public void thenWithResult() {
+ GeckoResult.fromValue(42)
+ .accept(
+ value -> {
+ assertThat("Value should match", value, equalTo(42));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void thenWithException() {
+ final Throwable boom = new Exception("boom");
+ GeckoResult.fromException(boom)
+ .accept(
+ null,
+ error -> {
+ assertThat("Exception should match", error, equalTo(boom));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @UiThreadTest
+ public void thenNoListeners() {
+ GeckoResult.fromValue(42).then(null, null);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCopy() {
+ final GeckoResult<Integer> result = new GeckoResult<>(GeckoResult.fromValue(42));
+ result.accept(
+ value -> {
+ assertThat("Value should match", value, equalTo(42));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void allOfError() throws Throwable {
+ final GeckoResult<List<Integer>> result =
+ GeckoResult.allOf(
+ new GeckoResult<>(GeckoResult.fromValue(12)),
+ new GeckoResult<>(GeckoResult.fromValue(35)),
+ new GeckoResult<>(GeckoResult.fromException(new RuntimeException("Sorry not sorry"))),
+ new GeckoResult<>(GeckoResult.fromValue(0)));
+
+ UiThreadUtils.waitForResult(
+ result.accept(
+ value -> {
+ throw new AssertionError("result should fail");
+ },
+ error -> {
+ assertThat("Error should match", error instanceof RuntimeException, is(true));
+ assertThat("Error should match", error.getMessage(), equalTo("Sorry not sorry"));
+ }),
+ mEnv.getDefaultTimeoutMillis());
+ }
+
+ @Test
+ @UiThreadTest
+ public void allOfEmpty() {
+ final GeckoResult<List<Integer>> result = GeckoResult.allOf();
+
+ result.accept(
+ value -> {
+ assertThat("Value should match", value.isEmpty(), is(true));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void allOfNull() {
+ final GeckoResult<List<Integer>> result = GeckoResult.allOf((List<GeckoResult<Integer>>) null);
+
+ result.accept(
+ value -> {
+ assertThat("Value should match", value, equalTo(null));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void allOfMany() {
+ final GeckoResult<Integer> pending1 = new GeckoResult<>();
+ final GeckoResult<Integer> pending2 = new GeckoResult<>();
+
+ final GeckoResult<List<Integer>> result =
+ GeckoResult.allOf(
+ pending1,
+ new GeckoResult<>(GeckoResult.fromValue(12)),
+ pending2,
+ new GeckoResult<>(GeckoResult.fromValue(35)),
+ new GeckoResult<>(GeckoResult.fromValue(9)),
+ new GeckoResult<>(GeckoResult.fromValue(0)));
+
+ result.accept(
+ value -> {
+ assertThat("Value should match", value, equalTo(Arrays.asList(123, 12, 321, 35, 9, 0)));
+ done();
+ });
+
+ try {
+ Thread.sleep(50);
+ } catch (final InterruptedException ex) {
+ }
+
+ // Complete the results out of order so that we can verify the input order is preserved
+ pending2.complete(321);
+ pending1.complete(123);
+ waitUntilDone();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @UiThreadTest
+ public void completeMultiple() {
+ final GeckoResult<Integer> deferred = new GeckoResult<>();
+ deferred.complete(42);
+ deferred.complete(43);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @UiThreadTest
+ public void completeMultipleExceptions() {
+ final GeckoResult<Integer> deferred = new GeckoResult<>();
+ deferred.completeExceptionally(new Exception("boom"));
+ deferred.completeExceptionally(new Exception("boom again"));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ @UiThreadTest
+ public void completeMixed() {
+ final GeckoResult<Integer> deferred = new GeckoResult<>();
+ deferred.complete(42);
+ deferred.completeExceptionally(new Exception("boom again"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ @UiThreadTest
+ public void completeExceptionallyNull() {
+ new GeckoResult<Integer>().completeExceptionally(null);
+ }
+
+ @Test
+ @UiThreadTest
+ public void completeThreaded() {
+ final GeckoResult<Integer> deferred = new GeckoResult<>();
+ final Thread thread = new Thread(() -> deferred.complete(42));
+
+ deferred.accept(
+ value -> {
+ assertThat("Value should match", value, equalTo(42));
+ ThreadUtils.assertOnUiThread();
+ done();
+ });
+
+ thread.start();
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void dispatchOnInitialThread() throws InterruptedException {
+ final Thread thread =
+ new Thread(
+ () -> {
+ Looper.prepare();
+ final Thread dispatchThread = Thread.currentThread();
+
+ GeckoResult.fromValue(42)
+ .accept(
+ value -> {
+ assertThat(
+ "Thread should match", Thread.currentThread(), equalTo(dispatchThread));
+ Looper.myLooper().quit();
+ });
+
+ Looper.loop();
+ });
+
+ thread.start();
+ thread.join();
+ }
+
+ @Test
+ @UiThreadTest
+ public void completeExceptionallyThreaded() {
+ final GeckoResult<Integer> deferred = new GeckoResult<>();
+ final Throwable boom = new Exception("boom");
+ final Thread thread = new Thread(() -> deferred.completeExceptionally(boom));
+
+ deferred.exceptionally(
+ error -> {
+ assertThat("Exception should match", error, equalTo(boom));
+ ThreadUtils.assertOnUiThread();
+ done();
+ return null;
+ });
+
+ thread.start();
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void testFinallyException() {
+ final GeckoResult<Integer> subject = new GeckoResult<>();
+ final Throwable boom = new Exception("boom");
+
+ subject
+ .map(
+ value -> {
+ assertThat("This should not be called", true, equalTo(false));
+ return null;
+ },
+ error -> {
+ assertThat("Error matches", error, equalTo(boom));
+ return error;
+ })
+ .finally_(() -> done());
+
+ subject.completeExceptionally(boom);
+ waitUntilDone();
+ }
+
+ @Test
+ @UiThreadTest
+ public void testFinallySuccessful() {
+ final GeckoResult<Integer> subject = new GeckoResult<>();
+
+ subject.accept(value -> assertThat("Value matches", value, equalTo(42))).finally_(() -> done());
+
+ subject.complete(42);
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void resultMapChaining() {
+ assertThat(
+ "We're on the UI thread",
+ Thread.currentThread(),
+ equalTo(Looper.getMainLooper().getThread()));
+
+ GeckoResult.fromValue(42)
+ .map(
+ value -> {
+ assertThat("Value should match", value, equalTo(42));
+ return "hello";
+ })
+ .map(
+ value -> {
+ assertThat("Value should match", value, equalTo("hello"));
+ return 42.0f;
+ })
+ .map(
+ value -> {
+ assertThat("Value should match", value, equalTo(42.0f));
+ throw new Exception("boom");
+ })
+ .map(
+ null,
+ error -> {
+ assertThat("Error message should match", error.getMessage(), equalTo("boom"));
+ return new MockException();
+ })
+ .accept(
+ null,
+ exception -> {
+ assertThat(
+ "Exception should be MockException", exception, instanceOf(MockException.class));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void resultChaining() {
+ assertThat(
+ "We're on the UI thread",
+ Thread.currentThread(),
+ equalTo(Looper.getMainLooper().getThread()));
+
+ GeckoResult.fromValue(42)
+ .then(
+ value -> {
+ assertThat("Value should match", value, equalTo(42));
+ return GeckoResult.fromValue("hello");
+ })
+ .then(
+ value -> {
+ assertThat("Value should match", value, equalTo("hello"));
+ return GeckoResult.fromValue(42.0f);
+ })
+ .then(
+ value -> {
+ assertThat("Value should match", value, equalTo(42.0f));
+ return GeckoResult.fromException(new Exception("boom"));
+ })
+ .exceptionally(
+ error -> {
+ assertThat("Error message should match", error.getMessage(), equalTo("boom"));
+ throw new MockException();
+ })
+ .accept(
+ null,
+ exception -> {
+ assertThat(
+ "Exception should be MockException", exception, instanceOf(MockException.class));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void then_propagatedValue() {
+ // The first GeckoResult only has an exception listener, so when the value 42 is
+ // propagated to subsequent GeckoResult instances, the propagated value is coerced to null.
+ GeckoResult.fromValue(42)
+ .exceptionally(error -> null)
+ .accept(
+ value -> {
+ assertThat("Propagated value is null", value, nullValue());
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test(expected = GeckoResult.UncaughtException.class)
+ public void then_uncaughtException() {
+ GeckoResult.fromValue(42)
+ .then(
+ value -> {
+ throw new MockException();
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test(expected = GeckoResult.UncaughtException.class)
+ public void then_propagatedUncaughtException() {
+ GeckoResult.fromValue(42)
+ .then(
+ value -> {
+ throw new MockException();
+ })
+ .accept(value -> {});
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void then_caughtException() {
+ GeckoResult.fromValue(42)
+ .then(
+ value -> {
+ throw new MockException();
+ })
+ .accept(value -> {})
+ .exceptionally(
+ exception -> {
+ assertThat(
+ "Exception should be expected", exception, instanceOf(MockException.class));
+ done();
+ return null;
+ });
+
+ waitUntilDone();
+ }
+
+ @Test(expected = IllegalThreadStateException.class)
+ public void noLooperThenThrows() {
+ assertThat("We shouldn't have a Looper", Looper.myLooper(), nullValue());
+ GeckoResult.fromValue(42).then(value -> null);
+ }
+
+ @Test
+ public void noLooperPoll() throws Throwable {
+ assertThat("We shouldn't have a Looper", Looper.myLooper(), nullValue());
+ assertThat("Value should match", GeckoResult.fromValue(42).poll(0), equalTo(42));
+ }
+
+ @Test
+ public void withHandler() {
+
+ final SynchronousQueue<Handler> queue = new SynchronousQueue<>();
+ final Thread thread =
+ new Thread(
+ () -> {
+ Looper.prepare();
+
+ try {
+ queue.put(new Handler());
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ Looper.loop();
+ });
+
+ thread.start();
+
+ final GeckoResult<Integer> result = GeckoResult.fromValue(42);
+ assertThat("We shouldn't have a Looper", result.getLooper(), nullValue());
+
+ try {
+ result
+ .withHandler(queue.take())
+ .accept(
+ value -> {
+ assertThat("Thread should match", Thread.currentThread(), equalTo(thread));
+ assertThat("Value should match", value, equalTo(42));
+ Looper.myLooper().quit();
+ });
+
+ thread.join();
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void pollCompleteWithValue() throws Throwable {
+ assertThat("Value should match", GeckoResult.fromValue(42).poll(0), equalTo(42));
+ }
+
+ @Test(expected = MockException.class)
+ public void pollCompleteWithError() throws Throwable {
+ GeckoResult.fromException(new MockException()).poll(0);
+ }
+
+ @Test(expected = TimeoutException.class)
+ public void pollTimeout() throws Throwable {
+ new GeckoResult<Void>().poll(1);
+ }
+
+ @UiThreadTest
+ @Test(expected = TimeoutException.class)
+ public void pollTimeoutWithLooper() throws Throwable {
+ new GeckoResult<Void>().poll(1);
+ }
+
+ @UiThreadTest
+ @Test(expected = IllegalThreadStateException.class)
+ public void pollWithLooper() throws Throwable {
+ new GeckoResult<Void>().poll();
+ }
+
+ @UiThreadTest
+ @Test
+ public void cancelNoDelegate() {
+ final GeckoResult<Void> result = new GeckoResult<Void>();
+ result
+ .cancel()
+ .accept(
+ value -> {
+ assertThat("Cancellation should fail", value, equalTo(false));
+ done();
+ });
+ waitUntilDone();
+ }
+
+ private GeckoResult<Integer> createCancellableResult() {
+ final GeckoResult<Integer> result = new GeckoResult<>();
+ result.setCancellationDelegate(
+ new GeckoResult.CancellationDelegate() {
+ @Override
+ public GeckoResult<Boolean> cancel() {
+ return GeckoResult.fromValue(true);
+ }
+ });
+
+ return result;
+ }
+
+ @UiThreadTest
+ @Test
+ public void cancelSuccess() {
+ final GeckoResult<Integer> result = createCancellableResult();
+
+ result
+ .cancel()
+ .accept(
+ value -> {
+ assertThat("Cancel should succeed", value, equalTo(true));
+ result.exceptionally(
+ exception -> {
+ assertThat(
+ "Exception should match",
+ exception,
+ instanceOf(CancellationException.class));
+ done();
+
+ return null;
+ });
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void cancelCompleted() {
+ final GeckoResult<Integer> result = createCancellableResult();
+ result.complete(42);
+ result
+ .cancel()
+ .accept(
+ value -> {
+ assertThat("Cancel should fail", value, equalTo(false));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void cancelParent() {
+ final GeckoResult<Integer> result = createCancellableResult();
+ final GeckoResult<Integer> result2 = result.then(value -> GeckoResult.fromValue(42));
+
+ result
+ .cancel()
+ .accept(
+ value -> {
+ assertThat("Cancel should succeed", value, equalTo(true));
+ result2.exceptionally(
+ exception -> {
+ assertThat(
+ "Exception should match",
+ exception,
+ instanceOf(CancellationException.class));
+ done();
+
+ return null;
+ });
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void cancelChildParentNotComplete() {
+ final GeckoResult<Integer> result =
+ new GeckoResult<Integer>()
+ .then(value -> createCancellableResult())
+ .then(value -> new GeckoResult<Integer>());
+
+ result
+ .cancel()
+ .accept(
+ value -> {
+ assertThat("Cancel should fail", value, equalTo(false));
+ done();
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void cancelChildParentComplete() {
+ final GeckoResult<Integer> result =
+ GeckoResult.fromValue(42)
+ .then(value -> createCancellableResult())
+ .then(value -> new GeckoResult<Integer>());
+
+ final Handler handler = new Handler();
+ handler.post(
+ () -> {
+ result
+ .cancel()
+ .accept(
+ value -> {
+ assertThat("Cancel should succeed", value, equalTo(true));
+ done();
+ });
+ });
+
+ waitUntilDone();
+ }
+
+ @UiThreadTest
+ @Test
+ public void getOrAccept()
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ final Method ai =
+ GeckoResult.class.getDeclaredMethod("getOrAccept", GeckoResult.Consumer.class);
+ ai.setAccessible(true);
+
+ final AtomicBoolean ran = new AtomicBoolean(false);
+ ai.invoke(GeckoResult.fromValue(42), (GeckoResult.Consumer<Integer>) o -> ran.set(true));
+ assertThat("Should've ran", ran.get(), equalTo(true));
+ }
+}