diff --git a/threadposter/src/main/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDouble.java b/threadposter/src/main/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDouble.java index 678c84a..11cda79 100755 --- a/threadposter/src/main/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDouble.java +++ b/threadposter/src/main/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDouble.java @@ -6,20 +6,18 @@ import java.util.LinkedList; import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Test double of {@link UiThreadPoster} that can be used in tests in order to establish * a happens-before relationship between any {@link Runnable} sent to execution and subsequent * test assertions. - * Instead of using Android's UI (aka main) thread, this implementation sends each {@link Runnable} - * to a new background thread. Only one background thread is allowed to run at a time, thus - * simulating a serial execution of {@link Runnable}s. + * Instead of using Android's UI (aka main) thread, this implementation runs all {@link Runnable}s + * on a single background thread in order, thus simulating serial execution on UI thread. */ /* pp */ class UiThreadPosterTestDouble extends UiThreadPoster { - private final Object MONITOR = new Object(); - - private final Queue mThreads = new LinkedList<>(); + private final Queue mRunnables = new ConcurrentLinkedQueue<>(); @Override protected Handler getMainHandler() { @@ -30,47 +28,31 @@ protected Handler getMainHandler() { @Override public void post(final Runnable runnable) { - synchronized (MONITOR) { - Thread worker = new Thread(new Runnable() { - @Override - public void run() { - // make sure all previous threads finished - UiThreadPosterTestDouble.this.join(); - runnable.run(); - } - }); - mThreads.add(worker); - worker.start(); - } + mRunnables.add(runnable); } /** - * Call to this method will block until all {@link Runnable}s posted to this "test double" - * BEFORE THE MOMENT OF A CALL will be completed.
+ * Execute all {@link Runnable}s posted to this "test double". The caller will block until the operation completes
* Call to this method allows to establish a happens-before relationship between the previously * posted {@link Runnable}s and subsequent code. */ public void join() { - Queue threadsCopy; - synchronized (MONITOR) { - threadsCopy = new LinkedList<>(mThreads); - } - - Thread thread; - while ((thread = threadsCopy.poll()) != null) { - try { - - // due to the way post(Runnable) is being implemented, this method will be called - // by threads that were added to the queue; in this case, we need to join only on - // threads that precede the calling thread in the queue - if (thread.getId() == Thread.currentThread().getId()) { - break; - } else { - thread.join(); + final Thread fakeUiThread = new Thread() { + @Override + public void run() { + Runnable runnable; + while ((runnable = mRunnables.poll()) != null) { + runnable.run(); } - } catch (InterruptedException e) { - e.printStackTrace(); } + }; + + fakeUiThread.start(); + + try { + fakeUiThread.join(); + } catch (InterruptedException e) { + throw new RuntimeException("interrupted"); } } diff --git a/threadposter/src/test/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDoubleTest.java b/threadposter/src/test/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDoubleTest.java index 3179280..aa48e0a 100755 --- a/threadposter/src/test/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDoubleTest.java +++ b/threadposter/src/test/java/com/techyourchance/threadposter/testdoubles/UiThreadPosterTestDoubleTest.java @@ -10,24 +10,7 @@ public class UiThreadPosterTestDoubleTest { - private static final int TEST_TIMEOUT_MS = 1000; - private static final int TEST_DELAY_MS = TEST_TIMEOUT_MS / 10; - - /** - * This class will be used in order to check side effects in tests - */ - private class Appender { - - private String mString = ""; - - private void append(String string) { - mString += string; - } - - private String getString() { - return mString; - } - } + private static final int TEST_DELAY_MS = 10; private UiThreadPosterTestDouble SUT; @@ -43,11 +26,6 @@ public void executeThenJoin_singleRunnable_sideEffectNotVisibleBeforeJoin() thro Runnable runnable = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("a"); } }; @@ -66,18 +44,12 @@ public void executeThenJoin_singleRunnable_sideEffectsVisibleAfterJoin() throws Runnable runnable = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("a"); } }; // Act SUT.post(runnable); // Assert - Thread.sleep(TEST_DELAY_MS); SUT.join(); assertThat(appender.getString(), is("a")); } @@ -90,33 +62,18 @@ public void executeThenJoin_multipleRunnables_sideEffectsNotVisibleBeforeJoin() Runnable runnable1 = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("a"); } }; Runnable runnable2 = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("b"); } }; Runnable runnable3 = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("c"); } }; @@ -129,7 +86,6 @@ public void run() { assertThat(appender.getString(), is("")); } - @Test public void executeThenJoin_multipleRunnables_sideEffectsVisibleAfterJoinInOrder() throws Exception { // Arrange @@ -137,33 +93,18 @@ public void executeThenJoin_multipleRunnables_sideEffectsVisibleAfterJoinInOrder Runnable runnable1 = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("a"); } }; Runnable runnable2 = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("b"); } }; Runnable runnable3 = new Runnable() { @Override public void run() { - try { - Thread.sleep(2 * TEST_DELAY_MS); - } catch (InterruptedException e) { - e.printStackTrace(); - } appender.append("c"); } }; @@ -172,8 +113,55 @@ public void run() { SUT.post(runnable2); SUT.post(runnable3); // Assert - Thread.sleep(TEST_DELAY_MS); SUT.join(); assertThat(appender.getString(), is("abc")); } + + + @Test + public void executeThenJoin_multipleNestedRunnables_sideEffectsVisibleAfterJoinInReversedOrder() throws Exception { + // Arrange + final Appender appender = new Appender(); + final Runnable runnable1 = new Runnable() { + @Override + public void run() { + appender.append("a"); + } + }; + final Runnable runnable2 = new Runnable() { + @Override + public void run() { + SUT.post(runnable1); + appender.append("b"); + } + }; + final Runnable runnable3 = new Runnable() { + @Override + public void run() { + SUT.post(runnable2); + appender.append("c"); + } + }; + // Act + SUT.post(runnable3); + // Assert + SUT.join(); + assertThat(appender.getString(), is("cba")); + } + + /** + * This class will be used in order to check side effects in tests + */ + private class Appender { + + private String mString = ""; + + private synchronized void append(String string) { + mString += string; + } + + private synchronized String getString() { + return mString; + } + } } \ No newline at end of file