[jconch] r134 committed - Added TestCoordinator for GWT style asynchronous test creation

3 views
Skip to first unread message

codesite...@google.com

unread,
Dec 18, 2009, 2:37:53 PM12/18/09
to jco...@googlegroups.com
Revision: 134
Author: HamletDRC
Date: Fri Dec 18 11:36:49 2009
Log: Added TestCoordinator for GWT style asynchronous test creation
http://code.google.com/p/jconch/source/detail?r=134

Added:
/trunk/eg/eg/jconch/testing/TestCoordinatorExamples.groovy
/trunk/src/jconch/testing/TestCoordinator.java
/trunk/test/jconch/testing/TestCoordinatorTest.java

=======================================
--- /dev/null
+++ /trunk/eg/eg/jconch/testing/TestCoordinatorExamples.groovy Fri Dec 18
11:36:49 2009
@@ -0,0 +1,55 @@
+
+import java.awt.event.ActionEvent
+import java.awt.event.ActionListener
+import java.util.ArrayList
+import java.util.List
+import java.util.concurrent.TimeUnit
+
+import javax.swing.SwingUtilities
+
+/**
+ * Examples of how to use the TestCoordinator object.
+ */
+
+import jconch.testing.TestCoordinator
+
+TestCoordinator coord = new TestCoordinator()
+
+MyComponent component = new MyComponent()
+component.addActionListener({ ActionEvent e ->
+ assert e.source != null
+ assert "click" == e.actionCommand
+ coord.finishTest()
+ } as ActionListener)
+
+component.click()
+coord.delayTestFinish()
+
+component.click()
+coord.delayTestFinish(1000)
+
+component.click()
+coord.delayTestFinish(1, TimeUnit.SECONDS)
+
+println 'Success'
+
+/**
+ * Mock component to spawn off click events on a 2nd thread.
+ */
+class MyComponent {
+
+ private def listeners = []
+
+ public void addActionListener(ActionListener listener) {
+ listeners << listener
+ }
+
+ public void click() {
+ SwingUtilities.invokeLater {
+ listeners.each { listener ->
+ def event = new ActionEvent(new Object(),
24987234, "click");
+ listener.actionPerformed(event);
+ }
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/src/jconch/testing/TestCoordinator.java Fri Dec 18 11:36:49 2009
@@ -0,0 +1,268 @@
+
+package jconch.testing;
+
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * The TestCoordinator object is meant to provide an easy API to pause and
wait for
+ * events in unit tests. It abstracts away the details of having to
manually manage
+ * barriers, latches, or wait/joins.
+ *
+ * The main API revolves around the delayTestFinish() and finishTest()
methods. When you
+ * want your test to wait until something happens then call
delayTestFinish(). When you want
+ * your waiting test to proceed then call delayTestFinish().
+ *
+ * The examples page is a good place to start exploring how it works, as
is the unit test.
+ *
+ * The TestCoordinator was inspired by GWT's GWTTestCase.
+ *
+ * @author Hamlet D'Arcy
+ * @version $Revision: #1 $ submitted $DateTime: 2008/05/01 13:13:51 $ by
$Author: darchb $
+ */
+class TestCoordinator {
+
+ private final ResettableCountdownLatch latch = new
ResettableCountdownLatch(1);
+
+
+ /**
+ * This method pauses the current thread until finishTest() is called.
+ * @throws RuntimeException
+ * if an InterruptedException is thrown then it is wrapped in a
RuntimeException
+ */
+ public void delayTestFinish() {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e); // don't expose InterruptedException to
users
+ } finally {
+ latch.reset();
+ }
+ }
+
+
+ /**
+ * This method pauses the current thread until finishTest() is called.
+ *
+ * If finishTest() is not called within the specified time frame then the
thread is allowed to proceed.
+ *
+ * @param timeout
+ * the number of milliseconds to wait until giving up and proceeding
+ * @return
+ * true if the timeout value was exceeded
+ * @throws RuntimeException
+ * if an InterruptedException is thrown then it is wrapped in a
RuntimeException
+ */
+ public boolean delayTestFinish(int timeout) {
+ try {
+ return latch.await(timeout);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e); // don't expose InterruptedException to
users
+ } finally {
+ latch.reset();
+ }
+ }
+
+
+ /**
+ * This method pauses the current thread until finishTest() is called.
+ *
+ * If finishTest() is not called within the specified time frame then the
thread is allowed to proceed.
+ *
+ * @param timeout
+ * the number of units to wait until giving up and proceeding
+ * @param timeUnit
+ * the unit of measure that defines timeout
+ * @return
+ * true if the timeout value was exceeded
+ * @throws RuntimeException
+ * if an InterruptedException is thrown then it is wrapped in a
RuntimeException
+ */
+ public boolean delayTestFinish(int timeout, TimeUnit timeUnit) {
+ try {
+ return latch.await(timeout, timeUnit);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e); // don't expose InterruptedException to
users
+ } finally {
+ latch.reset();
+ }
+ }
+
+
+ /**
+ * This method tells the coordinator that the test is finished and any
waiting party can proceed.
+ */
+ public void finishTest() {
+ latch.countDown();
+ }
+
+
+ /**
+ * A {@linkplain CountDownLatch CountDownLatch} that supports resets.
+ * JDK 7 is slated to have something similar in its "Phase" objects.
+ * @author Hamlet D'Arcy
+ */
+ private static class ResettableCountdownLatch {
+ private final int parties;
+ private final AtomicReference<CountDownLatch> ref = new
AtomicReference<CountDownLatch>();
+
+
+ /**
+ * Creates the countdown latch.
+ * @param parties
+ * number of parties that will participate in the latch
+ */
+ private ResettableCountdownLatch(int parties) {
+ this.parties = parties;
+ reset();
+ }
+
+
+ /**
+ * Resets the latch back to the original number of parties.
+ */
+ private void reset() {
+ ref.set(new CountDownLatch(parties));
+ }
+
+
+ /**
+ * Causes the current thread to wait until the latch has counted down to
+ * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
+ *
+ * <p>If the current count is zero then this method returns immediately.
+ *
+ * <p>If the current count is greater than zero then the current
+ * thread becomes disabled for thread scheduling purposes and lies
+ * dormant until one of two things happen:
+ * <ul>
+ * <li>The count reaches zero due to invocations of the
+ * {@link #countDown} method; or
+ * <li>Some other thread {@linkplain Thread#interrupt interrupts}
+ * the current thread.
+ * </ul>
+ *
+ * <p>If the current thread:
+ * <ul>
+ * <li>has its interrupted status set on entry to this method; or
+ * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
+ * </ul>
+ * then {@link InterruptedException} is thrown and the current thread's
+ * interrupted status is cleared.
+ *
+ * @throws InterruptedException if the current thread is interrupted
+ * while waiting
+ */
+ private void await() throws InterruptedException {
+ ref.get().await();
+ }
+
+
+ /**
+ * Causes the current thread to wait until the latch has counted down to
+ * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
+ * or the specified waiting time elapses.
+ *
+ * <p>If the current count is zero then this method returns immediately
+ * with the value {@code true}.
+ *
+ * <p>If the current count is greater than zero then the current
+ * thread becomes disabled for thread scheduling purposes and lies
+ * dormant until one of three things happen:
+ * <ul>
+ * <li>The count reaches zero due to invocations of the
+ * {@link #countDown} method; or
+ * <li>Some other thread {@linkplain Thread#interrupt interrupts}
+ * the current thread; or
+ * <li>The specified waiting time elapses.
+ * </ul>
+ *
+ * <p>If the count reaches zero then the method returns with the
+ * value {@code true}.
+ *
+ * <p>If the current thread:
+ * <ul>
+ * <li>has its interrupted status set on entry to this method; or
+ * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
+ * </ul>
+ * then {@link InterruptedException} is thrown and the current thread's
+ * interrupted status is cleared.
+ *
+ * <p>If the specified waiting time elapses then the value {@code false}
+ * is returned. If the time is less than or equal to zero, the method
+ * will not wait at all.
+ *
+ * @param timeout the maximum time to wait in milliseconds
+ * @return {@code true} if the count reached zero and {@code false}
+ * if the waiting time elapsed before the count reached zero
+ * @throws InterruptedException if the current thread is interrupted
+ * while waiting
+ */
+ private boolean await(int timeout) throws InterruptedException {
+ return ref.get().await(timeout, TimeUnit.MILLISECONDS);
+ }
+
+
+ /**
+ * Causes the current thread to wait until the latch has counted down to
+ * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
+ * or the specified waiting time elapses.
+ *
+ * <p>If the current count is zero then this method returns immediately
+ * with the value {@code true}.
+ *
+ * <p>If the current count is greater than zero then the current
+ * thread becomes disabled for thread scheduling purposes and lies
+ * dormant until one of three things happen:
+ * <ul>
+ * <li>The count reaches zero due to invocations of the
+ * {@link #countDown} method; or
+ * <li>Some other thread {@linkplain Thread#interrupt interrupts}
+ * the current thread; or
+ * <li>The specified waiting time elapses.
+ * </ul>
+ *
+ * <p>If the count reaches zero then the method returns with the
+ * value {@code true}.
+ *
+ * <p>If the current thread:
+ * <ul>
+ * <li>has its interrupted status set on entry to this method; or
+ * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
+ * </ul>
+ * then {@link InterruptedException} is thrown and the current thread's
+ * interrupted status is cleared.
+ *
+ * <p>If the specified waiting time elapses then the value {@code false}
+ * is returned. If the time is less than or equal to zero, the method
+ * will not wait at all.
+ *
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the {@code timeout} argument
+ * @return {@code true} if the count reached zero and {@code false}
+ * if the waiting time elapsed before the count reached zero
+ * @throws InterruptedException if the current thread is interrupted
+ * while waiting
+ */
+ private boolean await(int timeout, TimeUnit unit) throws
InterruptedException {
+ return ref.get().await(timeout, unit);
+ }
+
+
+ /**
+ * Decrements the count of the latch, releasing all waiting threads if
+ * the count reaches zero.
+ *
+ * <p>If the current count is greater than zero then it is decremented.
+ * If the new count is zero then all waiting threads are re-enabled for
+ * thread scheduling purposes.
+ *
+ * <p>If the current count equals zero then nothing happens.
+ */
+ private void countDown() {
+ ref.get().countDown();
+ }
+ }
+}
=======================================
--- /dev/null
+++ /trunk/test/jconch/testing/TestCoordinatorTest.java Fri Dec 18 11:36:49
2009
@@ -0,0 +1,134 @@
+
+package jconch.testing;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeTest;
+import static org.testng.AssertJUnit.*;
+
+
+/**
+ * Test for TestCoordinator.
+ * @author Hamlet D'Arcy (haml...@gmail.com)
+ */
+public class TestCoordinatorTest {
+
+ private ExecutorService executor;
+
+
+ @BeforeTest
+ public void setUp() throws Exception {
+ executor = Executors.newSingleThreadExecutor();
+ }
+
+
+ @Test(timeOut = 1000)
+ public void testWaitsToFinish() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+
+ final AtomicBoolean wasCalled = new AtomicBoolean(false);
+ executor.submit(new Runnable() {
+ public void run() {
+ wasCalled.set(true);
+ coord.finishTest();
+ }
+ });
+
+ coord.delayTestFinish();
+ assertTrue("task not called!", wasCalled.get());
+ }
+
+ @Test(timeOut = 1000)
+ public void testWaitsToFinish_WithTimeout() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+
+ final AtomicBoolean wasCalled = new AtomicBoolean(false);
+ executor.submit(new Runnable() {
+ public void run() {
+ wasCalled.set(true);
+ coord.finishTest();
+ }
+ });
+
+ final boolean result = coord.delayTestFinish(1000); //default is
milliseconds
+ assertTrue("task not called!", wasCalled.get());
+ assertTrue("task should not have timed out", result);
+ }
+
+ @Test(timeOut = 5000)
+ public void testErrorCondition_WithTimeout() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+ boolean result = coord.delayTestFinish(2000);
+ assertFalse("task should have timed out", result);
+ }
+
+ @Test(timeOut = 5000)
+ public void testErrorCondition_WithTimeoutAndTimeUnit() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+ final boolean result = coord.delayTestFinish(1, TimeUnit.SECONDS);
+ assertFalse("task should have timed out", result);
+ }
+
+ @Test(timeOut = 1000)
+ public void testWaitsToFinish_WithTimeoutAndTimeUnit() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+
+ final AtomicBoolean wasCalled = new AtomicBoolean(false);
+ executor.submit(new Runnable() {
+ public void run() {
+ wasCalled.set(true);
+ coord.finishTest();
+ }
+ });
+
+ final boolean result = coord.delayTestFinish(1, TimeUnit.SECONDS);
+ assertTrue("task not called!", wasCalled.get());
+ assertTrue("task should not have timed out", result);
+ }
+
+
+ @Test(timeOut = 1000)
+ public void testWaitsToFinishCanBeCalledTwice() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+
+ final AtomicBoolean wasCalled = new AtomicBoolean(false);
+
+ final Runnable task = new Runnable() {
+ public void run() {
+ wasCalled.set(true);
+ coord.finishTest();
+ }
+ };
+
+ executor.submit(task);
+ coord.delayTestFinish();
+ assertTrue("1st task not called!", wasCalled.get());
+
+ wasCalled.set(false);
+ executor.submit(task);
+ coord.delayTestFinish();
+ assertTrue("2nd task not called!", wasCalled.get());
+ }
+
+
+ @Test(timeOut = 1000)
+ public void testErrorCondition_FinishCalledTwice() throws Exception {
+
+ final TestCoordinator coord = new TestCoordinator();
+ coord.finishTest();
+ // this shows that nothing bad happens if call finish twice
+ coord.finishTest();
+ }
+}
+
+

Reply all
Reply to author
Forward
0 new messages