Source code

Revision control

Copy as Markdown

Other Tools

/* Any copyright is dedicated to the Public Domain.
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));
}
}