michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: package org.mozilla.gecko.background.common; michael@0: michael@0: import junit.framework.AssertionFailedError; michael@0: michael@0: import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; michael@0: import org.mozilla.gecko.background.testhelpers.WaitHelper; michael@0: import org.mozilla.gecko.background.testhelpers.WaitHelper.InnerError; michael@0: import org.mozilla.gecko.background.testhelpers.WaitHelper.TimeoutError; michael@0: import org.mozilla.gecko.sync.ThreadPool; michael@0: michael@0: public class TestWaitHelper extends AndroidSyncTestCase { michael@0: private static final String ERROR_UNIQUE_IDENTIFIER = "error unique identifier"; michael@0: michael@0: public static int NO_WAIT = 1; // Milliseconds. michael@0: public static int SHORT_WAIT = 100; // Milliseconds. michael@0: public static int LONG_WAIT = 3 * SHORT_WAIT; michael@0: michael@0: private Object notifyMonitor = new Object(); michael@0: // Guarded by notifyMonitor. michael@0: private boolean performNotifyCalled = false; michael@0: private boolean performNotifyErrorCalled = false; michael@0: private void setPerformNotifyCalled() { michael@0: synchronized (notifyMonitor) { michael@0: performNotifyCalled = true; michael@0: } michael@0: } michael@0: private void setPerformNotifyErrorCalled() { michael@0: synchronized (notifyMonitor) { michael@0: performNotifyErrorCalled = true; michael@0: } michael@0: } michael@0: private void resetNotifyCalled() { michael@0: synchronized (notifyMonitor) { michael@0: performNotifyCalled = false; michael@0: performNotifyErrorCalled = false; michael@0: } michael@0: } michael@0: private void assertBothCalled() { michael@0: synchronized (notifyMonitor) { michael@0: assertTrue(performNotifyCalled); michael@0: assertTrue(performNotifyErrorCalled); michael@0: } michael@0: } michael@0: private void assertErrorCalled() { michael@0: synchronized (notifyMonitor) { michael@0: assertFalse(performNotifyCalled); michael@0: assertTrue(performNotifyErrorCalled); michael@0: } michael@0: } michael@0: private void assertCalled() { michael@0: synchronized (notifyMonitor) { michael@0: assertTrue(performNotifyCalled); michael@0: assertFalse(performNotifyErrorCalled); michael@0: } michael@0: } michael@0: michael@0: public WaitHelper waitHelper; michael@0: michael@0: public TestWaitHelper() { michael@0: super(); michael@0: } michael@0: michael@0: public void setUp() { michael@0: WaitHelper.resetTestWaiter(); michael@0: waitHelper = WaitHelper.getTestWaiter(); michael@0: resetNotifyCalled(); michael@0: } michael@0: michael@0: public void tearDown() { michael@0: assertTrue(waitHelper.isIdle()); michael@0: } michael@0: michael@0: public Runnable performNothingRunnable() { michael@0: return new Runnable() { michael@0: public void run() { michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable performNotifyRunnable() { michael@0: return new Runnable() { michael@0: public void run() { michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable performNotifyAfterDelayRunnable(final int delayInMillis) { michael@0: return new Runnable() { michael@0: public void run() { michael@0: try { michael@0: Thread.sleep(delayInMillis); michael@0: } catch (InterruptedException e) { michael@0: fail("Interrupted."); michael@0: } michael@0: michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable performNotifyErrorRunnable() { michael@0: return new Runnable() { michael@0: public void run() { michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(new AssertionFailedError(ERROR_UNIQUE_IDENTIFIER)); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable inThreadPool(final Runnable runnable) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: ThreadPool.run(runnable); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public Runnable inThread(final Runnable runnable) { michael@0: return new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: new Thread(runnable).start(); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: protected void expectAssertionFailedError(Runnable runnable) { michael@0: try { michael@0: waitHelper.performWait(runnable); michael@0: } catch (InnerError e) { michael@0: AssertionFailedError inner = (AssertionFailedError)e.innerError; michael@0: setPerformNotifyErrorCalled(); michael@0: String message = inner.getMessage(); michael@0: assertTrue("Expected '" + message + "' to contain '" + ERROR_UNIQUE_IDENTIFIER + "'", michael@0: message.contains(ERROR_UNIQUE_IDENTIFIER)); michael@0: } michael@0: } michael@0: michael@0: protected void expectAssertionFailedErrorAfterDelay(int wait, Runnable runnable) { michael@0: try { michael@0: waitHelper.performWait(wait, runnable); michael@0: } catch (InnerError e) { michael@0: AssertionFailedError inner = (AssertionFailedError)e.innerError; michael@0: setPerformNotifyErrorCalled(); michael@0: String message = inner.getMessage(); michael@0: assertTrue("Expected '" + message + "' to contain '" + ERROR_UNIQUE_IDENTIFIER + "'", michael@0: message.contains(ERROR_UNIQUE_IDENTIFIER)); michael@0: } michael@0: } michael@0: michael@0: public void testPerformWait() { michael@0: waitHelper.performWait(performNotifyRunnable()); michael@0: assertCalled(); michael@0: } michael@0: michael@0: public void testPerformWaitInThread() { michael@0: waitHelper.performWait(inThread(performNotifyRunnable())); michael@0: assertCalled(); michael@0: } michael@0: michael@0: public void testPerformWaitInThreadPool() { michael@0: waitHelper.performWait(inThreadPool(performNotifyRunnable())); michael@0: assertCalled(); michael@0: } michael@0: michael@0: public void testPerformTimeoutWait() { michael@0: waitHelper.performWait(SHORT_WAIT, performNotifyRunnable()); michael@0: assertCalled(); michael@0: } michael@0: michael@0: public void testPerformTimeoutWaitInThread() { michael@0: waitHelper.performWait(SHORT_WAIT, inThread(performNotifyRunnable())); michael@0: assertCalled(); michael@0: } michael@0: michael@0: public void testPerformTimeoutWaitInThreadPool() { michael@0: waitHelper.performWait(SHORT_WAIT, inThreadPool(performNotifyRunnable())); michael@0: assertCalled(); michael@0: } michael@0: michael@0: public void testPerformErrorWaitInThread() { michael@0: expectAssertionFailedError(inThread(performNotifyErrorRunnable())); michael@0: assertBothCalled(); michael@0: } michael@0: michael@0: public void testPerformErrorWaitInThreadPool() { michael@0: expectAssertionFailedError(inThreadPool(performNotifyErrorRunnable())); michael@0: assertBothCalled(); michael@0: } michael@0: michael@0: public void testPerformErrorTimeoutWaitInThread() { michael@0: expectAssertionFailedErrorAfterDelay(SHORT_WAIT, inThread(performNotifyErrorRunnable())); michael@0: assertBothCalled(); michael@0: } michael@0: michael@0: public void testPerformErrorTimeoutWaitInThreadPool() { michael@0: expectAssertionFailedErrorAfterDelay(SHORT_WAIT, inThreadPool(performNotifyErrorRunnable())); michael@0: assertBothCalled(); michael@0: } michael@0: michael@0: public void testTimeout() { michael@0: try { michael@0: waitHelper.performWait(SHORT_WAIT, performNothingRunnable()); michael@0: } catch (TimeoutError e) { michael@0: setPerformNotifyErrorCalled(); michael@0: assertEquals(SHORT_WAIT, e.waitTimeInMillis); michael@0: } michael@0: assertErrorCalled(); michael@0: } michael@0: michael@0: /** michael@0: * This will pass. The sequence in the main thread is: michael@0: * - A short delay. michael@0: * - performNotify is called. michael@0: * - performWait is called and immediately finds that performNotify was called before. michael@0: */ michael@0: public void testDelay() { michael@0: try { michael@0: waitHelper.performWait(1, performNotifyAfterDelayRunnable(SHORT_WAIT)); michael@0: } catch (AssertionFailedError e) { michael@0: setPerformNotifyErrorCalled(); michael@0: assertTrue(e.getMessage(), e.getMessage().contains("TIMEOUT")); michael@0: } michael@0: assertCalled(); michael@0: } michael@0: michael@0: public Runnable performNotifyMultipleTimesRunnable() { michael@0: return new Runnable() { michael@0: public void run() { michael@0: waitHelper.performNotify(); michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: public void testPerformNotifyMultipleTimesFails() { michael@0: try { michael@0: waitHelper.performWait(NO_WAIT, performNotifyMultipleTimesRunnable()); // Not run on thread, so runnable executes before performWait looks for notifications. michael@0: } catch (WaitHelper.MultipleNotificationsError e) { michael@0: setPerformNotifyErrorCalled(); michael@0: } michael@0: assertBothCalled(); michael@0: assertFalse(waitHelper.isIdle()); // First perform notify should be hanging around. michael@0: waitHelper.performWait(NO_WAIT, performNothingRunnable()); michael@0: } michael@0: michael@0: public void testNestedWaitsAndNotifies() { michael@0: waitHelper.performWait(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: waitHelper.performWait(new Runnable() { michael@0: public void run() { michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: }); michael@0: setPerformNotifyErrorCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: }); michael@0: assertBothCalled(); michael@0: } michael@0: michael@0: public void testAssertIsReported() { michael@0: try { michael@0: waitHelper.performWait(1, new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: assertTrue("unique identifier", false); michael@0: } michael@0: }); michael@0: } catch (AssertionFailedError e) { michael@0: setPerformNotifyErrorCalled(); michael@0: assertTrue(e.getMessage(), e.getMessage().contains("unique identifier")); michael@0: } michael@0: assertErrorCalled(); michael@0: } michael@0: michael@0: /** michael@0: * The inner wait will timeout, but the outer wait will succeed. The sequence in the helper thread is: michael@0: * - A short delay. michael@0: * - performNotify is called. michael@0: * michael@0: * The sequence in the main thread is: michael@0: * - performWait is called and times out because the helper thread does not call michael@0: * performNotify quickly enough. michael@0: */ michael@0: public void testDelayInThread() throws InterruptedException { michael@0: waitHelper.performWait(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: waitHelper.performWait(NO_WAIT, inThread(new Runnable() { michael@0: public void run() { michael@0: try { michael@0: Thread.sleep(SHORT_WAIT); michael@0: } catch (InterruptedException e) { michael@0: fail("Interrupted."); michael@0: } michael@0: michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: })); michael@0: } catch (WaitHelper.TimeoutError e) { michael@0: setPerformNotifyErrorCalled(); michael@0: assertEquals(NO_WAIT, e.waitTimeInMillis); michael@0: } michael@0: } michael@0: }); michael@0: assertBothCalled(); michael@0: } michael@0: michael@0: /** michael@0: * The inner wait will timeout, but the outer wait will succeed. The sequence in the helper thread is: michael@0: * - A short delay. michael@0: * - performNotify is called. michael@0: * michael@0: * The sequence in the main thread is: michael@0: * - performWait is called and times out because the helper thread does not call michael@0: * performNotify quickly enough. michael@0: */ michael@0: public void testDelayInThreadPool() throws InterruptedException { michael@0: waitHelper.performWait(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: try { michael@0: waitHelper.performWait(NO_WAIT, inThreadPool(new Runnable() { michael@0: public void run() { michael@0: try { michael@0: Thread.sleep(SHORT_WAIT); michael@0: } catch (InterruptedException e) { michael@0: fail("Interrupted."); michael@0: } michael@0: michael@0: setPerformNotifyCalled(); michael@0: waitHelper.performNotify(); michael@0: } michael@0: })); michael@0: } catch (WaitHelper.TimeoutError e) { michael@0: setPerformNotifyErrorCalled(); michael@0: assertEquals(NO_WAIT, e.waitTimeInMillis); michael@0: } michael@0: } michael@0: }); michael@0: assertBothCalled(); michael@0: } michael@0: }