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