Browse files

initial revision

git-svn-id: http://multithreadedtc-junit4.googlecode.com/svn/trunk/MultithreadedTC@2 7aeca032-9163-11de-bb70-8db92513eca4
  • Loading branch information...
0 parents commit 2a8d5a43c54e500a5d4a881ee6b3e69541565bb2 janvanbesien committed Aug 25, 2009
Showing with 10,945 additions and 0 deletions.
  1. +7 −0 CHANGELOG.txt
  2. +21 −0 LICENSE.txt
  3. +2 −0 README-JDK14.txt
  4. +8 −0 README.txt
  5. +57 −0 build.xml
  6. +44 −0 examples/plain_vs_mtc/CompareAndSetTestsMTC.java
  7. +44 −0 examples/plain_vs_mtc/CompareAndSetTestsPlain.java
  8. +49 −0 examples/plain_vs_mtc/InterruptBlockedTestsMTC.java
  9. +75 −0 examples/plain_vs_mtc/InterruptBlockedTestsPlain.java
  10. +54 −0 examples/plain_vs_mtc/ThreadControlTestsMTC.java
  11. +94 −0 examples/plain_vs_mtc/ThreadControlTestsPlain.java
  12. +59 −0 examples/plain_vs_mtc/TimeoutTestsMTC.java
  13. +87 −0 examples/plain_vs_mtc/TimeoutTestsPlain.java
  14. +48 −0 examples/sampletests/MTCBoundedBufferDeadlockTest.java
  15. +47 −0 examples/sampletests/MTCBoundedBufferTest.java
  16. +33 −0 examples/sanity/TestFrameworkTests.java
  17. +38 −0 examples/sanity/basictests/SanityGetThreadByNameReturnsCorrectThread.java
  18. +33 −0 examples/sanity/basictests/SanityGetThreadReturnsCorrectThread.java
  19. +67 −0 examples/sanity/basictests/SanityMetronomeOrder.java
  20. +40 −0 examples/sanity/basictests/SanityThreadMethodsInvokedInDifferentThreads.java
  21. +32 −0 examples/sanity/basictests/SanityThreadTerminatesBeforeFinishIsCalled.java
  22. +58 −0 examples/sanity/basictests/SanityThreadsBeforeTest.java
  23. +49 −0 examples/sanity/basictests/SanityWaitForTickAdvancesWhenTestsAreBlocked.java
  24. +33 −0 examples/sanity/basictests/SanityWaitForTickBlocksThread.java
  25. +31 −0 examples/sanity/basictests/TUnitTestTestWithNoThreads.java
  26. +46 −0 examples/sanity/errordetectiontests/TUnitTestDeadlockDetected.java
  27. +43 −0 examples/sanity/errordetectiontests/TUnitTestLiveLockTimesOut.java
  28. +29 −0 examples/sanity/errordetectiontests/TUnitTestMissingUnfreeze.java
  29. +29 −0 examples/sanity/package-info.java
  30. +43 −0 examples/sanity/timing/TUnitTestClockDoesNotAdvanceWhenFrozen.java
  31. +36 −0 src/edu/umd/cs/mtc/MultiThreadedRunner.java
  32. +18 −0 src/edu/umd/cs/mtc/MultithreadedTest.java
  33. +425 −0 src/edu/umd/cs/mtc/MultithreadedTestCase.java
  34. +670 −0 src/edu/umd/cs/mtc/RunThreadsThenInvokeMethod.java
  35. +21 −0 src/edu/umd/cs/mtc/Threaded.java
  36. +21 −0 src/edu/umd/cs/mtc/package-info.java
  37. +244 −0 web/EnvisionTemplate.html
  38. +35 −0 web/docs/allclasses-frame.html
  39. +35 −0 web/docs/allclasses-noframe.html
  40. +180 −0 web/docs/constant-values.html
  41. +146 −0 web/docs/deprecated-list.html
  42. +442 −0 web/docs/edu/umd/cs/mtc/MultithreadedTest.html
  43. +976 −0 web/docs/edu/umd/cs/mtc/MultithreadedTestCase.html
  44. +685 −0 web/docs/edu/umd/cs/mtc/TestFramework.html
  45. +169 −0 web/docs/edu/umd/cs/mtc/class-use/MultithreadedTest.html
  46. +245 −0 web/docs/edu/umd/cs/mtc/class-use/MultithreadedTestCase.html
  47. +144 −0 web/docs/edu/umd/cs/mtc/class-use/TestFramework.html
  48. +36 −0 web/docs/edu/umd/cs/mtc/package-frame.html
  49. +199 −0 web/docs/edu/umd/cs/mtc/package-summary.html
  50. +155 −0 web/docs/edu/umd/cs/mtc/package-tree.html
  51. +165 −0 web/docs/edu/umd/cs/mtc/package-use.html
  52. +223 −0 web/docs/help-doc.html
  53. +158 −0 web/docs/index-files/index-1.html
  54. +146 −0 web/docs/index-files/index-10.html
  55. +162 −0 web/docs/index-files/index-11.html
  56. +145 −0 web/docs/index-files/index-12.html
  57. +174 −0 web/docs/index-files/index-13.html
  58. +156 −0 web/docs/index-files/index-14.html
  59. +148 −0 web/docs/index-files/index-15.html
  60. +145 −0 web/docs/index-files/index-16.html
  61. +152 −0 web/docs/index-files/index-17.html
  62. +150 −0 web/docs/index-files/index-2.html
  63. +160 −0 web/docs/index-files/index-3.html
  64. +148 −0 web/docs/index-files/index-4.html
  65. +143 −0 web/docs/index-files/index-5.html
  66. +154 −0 web/docs/index-files/index-6.html
  67. +159 −0 web/docs/index-files/index-7.html
  68. +146 −0 web/docs/index-files/index-8.html
  69. +149 −0 web/docs/index-files/index-9.html
  70. +34 −0 web/docs/index.html
  71. +173 −0 web/docs/overview-summary.html
  72. +157 −0 web/docs/overview-tree.html
  73. +20 −0 web/docs/overviewbody.html
  74. +1 −0 web/docs/package-list
  75. BIN web/docs/resources/inherit.gif
  76. +29 −0 web/docs/stylesheet.css
  77. +435 −0 web/images/Envision.css
  78. BIN web/images/boundedbuffer1.png
  79. BIN web/images/boundedbuffer2.png
  80. BIN web/images/bullet.gif
  81. BIN web/images/button-bg.jpg
  82. BIN web/images/clock.gif
  83. BIN web/images/comment.gif
  84. BIN web/images/content.jpg
  85. BIN web/images/dots.jpg
  86. BIN web/images/footer.jpg
  87. BIN web/images/header.jpg
  88. BIN web/images/menu.jpg
  89. BIN web/images/mtclogo.png
  90. BIN web/images/page.gif
  91. BIN web/images/quote.gif
  92. BIN web/images/square-green.png
  93. +123 −0 web/index.html
  94. +808 −0 web/overview.html
7 CHANGELOG.txt
@@ -0,0 +1,7 @@
+CHANGELOG
+
+1.01 August 13, 2007
+- - Update to better support translation into JDK 1.4 compatible bytecode using Retrotranslator
+
+1.0 May 10, 2007
+- - Initial Release
21 LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright (c) 2007, University of Maryland
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of the University of Maryland nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2 README-JDK14.txt
@@ -0,0 +1,2 @@
+The jar file MultithreadedTC-XXX-jdk14.jar is compiled for JDK 1.4 and requires all the libraries
+distributed with Retrotranslator (http://retrotranslator.sourceforge.net/)
8 README.txt
@@ -0,0 +1,8 @@
+For more information about MultithreadedTC, visit
+http://www.cs.umd.edu/projects/PL/multithreadedtc/
+
+To use this library, you need JUnit (3.8 or 4.*). Get it at
+http://www.junit.org
+
+MultithreadedTC is released under the BSD License,
+http://www.opensource.org/licenses/bsd-license.php
57 build.xml
@@ -0,0 +1,57 @@
+<project name="MultithreadedTC" default="distjar" basedir=".">
+
+ <property name="release.id" value="1.01" />
+ <property name="jarfile" value="MultithreadedTC-${release.id}.jar" />
+ <property name="jarfile14" value="MultithreadedTC-${release.id}-jdk14.jar" />
+
+ <property name="sourcename" value="MultithreadedTC-${release.id}-source" />
+
+ <property name="info.files" value="README.txt,LICENSE.txt,CHANGELOG.txt" />
+ <property name="all.info.files" value="${info.files},README-JDK14.txt" />
+
+ <!-- Create distribution jar -->
+ <target name="distjar">
+ <jar destfile="${jarfile}" update="true">
+ <fileset dir="bin" includes="edu/**/*class" />
+ <fileset dir="src" includes="edu/**/*.java" />
+ <fileset dir="." includes="${info.files}" />
+ </jar>
+ </target>
+
+ <!-- Create Source Distribution zip -->
+ <target name="sourcezip" depends="distjar,distjar-jdk14">
+ <zip destfile="${sourcename}.zip" update="true">
+ <zipfileset dir="." includes="src/**" excludes="**.svn**" prefix="${sourcename}"/>
+ <zipfileset dir="." includes="examples/**" excludes="**.svn**" prefix="${sourcename}"/>
+ <zipfileset dir="." includes="web/**" excludes="**.svn**,sanity/**" prefix="${sourcename}"/>
+ <zipfileset dir="." includes="${all.info.files}" prefix="${sourcename}"/>
+ <zipfileset dir="." includes=".project,.classpath,build.xml" prefix="${sourcename}"/>
+ <zipfileset dir="." includes="${jarfile}" prefix="${sourcename}"/>
+ <zipfileset dir="." includes="${jarfile14}" prefix="${sourcename}"/>
+ </zip>
+ </target>
+
+ <!-- Create Website distribution zip -->
+ <target name="webzip">
+ <zip destfile="MultithreadedTCWebsite.zip" update="true">
+ <fileset dir="web" includes="**" excludes="**.svn**" />
+ </zip>
+ </target>
+
+ <!-- Retro Tasks: Create a Jar for 1.4 -->
+ <!-- The 'retrotranslator' task creates a jar file with JDK 1.4 compatible class files.
+ This task requires all the jar files in the retrotranslator distribution to be in
+ the lib folder. Find these at http://retrotranslator.sourceforge.net/ -->
+ <target name="distjar-jdk14">
+ <path id="retroclasspath">
+ <fileset dir="lib" includes="**/*.jar"/>
+ </path>
+
+ <taskdef name="retrotranslator" classpathref="retroclasspath"
+ classname="net.sf.retrotranslator.transformer.RetrotranslatorTask" />
+ <retrotranslator destjar="${jarfile14}" verify="false">
+ <fileset dir="bin" includes="edu/**/*.class"/>
+ <fileset dir="." includes="${all.info.files}" />
+ </retrotranslator>
+ </target>
+</project>
44 examples/plain_vs_mtc/CompareAndSetTestsMTC.java
@@ -0,0 +1,44 @@
+package plain_vs_mtc;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class CompareAndSetTestsMTC extends MultithreadedTestCase
+{
+
+ AtomicInteger ai;
+
+ @Before
+ public void initialize()
+ {
+ ai = new AtomicInteger(1);
+ }
+
+ @Threaded
+ public void thread1()
+ {
+ while (!ai.compareAndSet(2, 3)) Thread.yield();
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ assertTrue(ai.compareAndSet(1, 2));
+ }
+
+ @Test
+ public void finish()
+ {
+ assertEquals(ai.get(), 3);
+ }
+}
44 examples/plain_vs_mtc/CompareAndSetTestsPlain.java
@@ -0,0 +1,44 @@
+package plain_vs_mtc;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * compareAndSet in one thread enables another waiting for value to succeed
+ */
+public class CompareAndSetTestsPlain
+{
+
+ /* NOTES
+ * - Plain version requires a join before final asserts
+ * - MTC version does not need to check if thread is alive
+ */
+
+ @Test
+ public void testCompareAndSet() throws InterruptedException
+ {
+ final AtomicInteger ai = new AtomicInteger(1);
+ Thread t = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ while (!ai.compareAndSet(2, 3)) Thread.yield();
+ }
+ });
+
+ t.start();
+ assertTrue(ai.compareAndSet(1, 2));
+ t.join(2500);
+ assertFalse(t.isAlive());
+ assertEquals(ai.get(), 3);
+ }
+
+
+}
49 examples/plain_vs_mtc/InterruptBlockedTestsMTC.java
@@ -0,0 +1,49 @@
+package plain_vs_mtc;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+
+import java.util.concurrent.Semaphore;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class InterruptBlockedTestsMTC extends MultithreadedTestCase
+{
+ Semaphore s;
+
+ @Before
+ public void initialize()
+ {
+ s = new Semaphore(0);
+ }
+
+ @Threaded("1")
+ public void thread1()
+ {
+ try
+ {
+ s.acquire();
+ fail("should throw exception");
+ } catch (InterruptedException success)
+ {
+ assertTick(1);
+ }
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ waitForTick(1);
+ getThread(1).interrupt();
+ }
+
+ @Test
+ public void test()
+ {
+ }
+}
75 examples/plain_vs_mtc/InterruptBlockedTestsPlain.java
@@ -0,0 +1,75 @@
+package plain_vs_mtc;
+
+import java.util.concurrent.Semaphore;
+
+import static junit.framework.Assert.assertFalse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.After;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that a waiting acquire blocks interruptibly
+ */
+public class InterruptBlockedTestsPlain
+{
+
+ /* NOTES
+ * - Failures in threads require additional work in setup and teardown
+ * - Relies on Threaded.sleep to ensure acquire has blocked
+ * - Does not ensure that exceptions are definitely caused by interrupt
+ * - More verbose
+ * - Requires a join at the end
+ * - In MTC version, get reference to a thread using getThread(1)
+ */
+
+ volatile boolean threadFailed;
+
+ public void threadShouldThrow()
+ {
+ threadFailed = true;
+ fail("should throw exception");
+ }
+
+ @Before
+ public void setUp() throws Exception
+ {
+ threadFailed = false;
+ }
+
+ @Test
+ public void testInterruptedAcquire()
+ {
+ final Semaphore s = new Semaphore(0);
+ Thread t = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ s.acquire();
+ threadShouldThrow();
+ } catch (InterruptedException success)
+ {
+ }
+ }
+ });
+ t.start();
+ try
+ {
+ Thread.sleep(50);
+ t.interrupt();
+ t.join();
+ } catch (InterruptedException e)
+ {
+ fail("Unexpected exception");
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ assertFalse(threadFailed);
+ }
+
+}
54 examples/plain_vs_mtc/ThreadControlTestsMTC.java
@@ -0,0 +1,54 @@
+package plain_vs_mtc;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class ThreadControlTestsMTC extends MultithreadedTestCase
+{
+
+ AtomicInteger ai;
+
+ @Before
+ public void initialize()
+ {
+ ai = new AtomicInteger(0);
+ }
+
+ @Threaded
+ public void thread1()
+ {
+ assertTrue(ai.compareAndSet(0, 1)); // S1
+ waitForTick(3);
+ assertEquals(ai.get(), 3); // S4
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ waitForTick(1);
+ assertTrue(ai.compareAndSet(1, 2)); // S2
+ waitForTick(3);
+ assertEquals(ai.get(), 3); // S4
+ }
+
+ @Threaded
+ public void thread3()
+ {
+ waitForTick(2);
+ assertTrue(ai.compareAndSet(2, 3)); // S3
+ }
+
+ @Test
+ public void test()
+ {
+ }
+}
94 examples/plain_vs_mtc/ThreadControlTestsPlain.java
@@ -0,0 +1,94 @@
+package plain_vs_mtc;
+
+import org.junit.After;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Controlling the order in which threads are called
+ */
+public class ThreadControlTestsPlain
+{
+
+ volatile boolean threadFailed;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ threadFailed = false;
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ assertFalse(threadFailed);
+ }
+
+ public void unexpectedException()
+ {
+ threadFailed = true;
+ fail("Unexpected exception");
+ }
+
+ @Test
+ public void testLatchBasedThreadOrdering() throws InterruptedException
+ {
+ final CountDownLatch c1 = new CountDownLatch(1);
+ final CountDownLatch c2 = new CountDownLatch(1);
+ final CountDownLatch c3 = new CountDownLatch(1);
+ final AtomicInteger ai = new AtomicInteger(0);
+
+ Thread t1 = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ assertTrue(ai.compareAndSet(0, 1)); // S1
+ c1.countDown();
+ c3.await();
+ assertEquals(ai.get(), 3); // S4
+ } catch (Exception e)
+ { // Can't simply catch InterruptedException because we might miss some RuntimeException
+ unexpectedException();
+ }
+ }
+ });
+
+ Thread t2 = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ c1.await();
+ assertTrue(ai.compareAndSet(1, 2)); // S2
+ c2.countDown();
+ c3.await();
+ assertEquals(ai.get(), 3); // S4
+ } catch (Exception e)
+ {
+ unexpectedException();
+ }
+ }
+ });
+
+ t1.start();
+ t2.start();
+
+ c2.await();
+ assertTrue(ai.compareAndSet(2, 3)); // S3
+ c3.countDown();
+
+ t1.join();
+ t2.join();
+ }
+
+}
59 examples/plain_vs_mtc/TimeoutTestsMTC.java
@@ -0,0 +1,59 @@
+package plain_vs_mtc;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * In this test, the first offer is allowed to timeout, the second offer is interrupted. Use `freezeClock` to prevent the clock from
+ * advancing during the first offer.
+ */
+public class TimeoutTestsMTC extends MultithreadedTestCase
+{
+ ArrayBlockingQueue<Object> q;
+
+ @Before
+ public void initialize()
+ {
+ q = new ArrayBlockingQueue<Object>(2);
+ }
+
+ @Threaded("1")
+ public void thread1()
+ {
+ try
+ {
+ q.put(new Object());
+ q.put(new Object());
+
+ freezeClock();
+ assertFalse(q.offer(new Object(), 25, TimeUnit.MILLISECONDS));
+ unfreezeClock();
+
+ q.offer(new Object(), 2500, TimeUnit.MILLISECONDS);
+ fail("should throw exception");
+ } catch (InterruptedException success)
+ {
+ assertTick(1);
+ }
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ waitForTick(1);
+ getThread(1).interrupt();
+ }
+
+ @Test
+ public void test()
+ {
+ }
+}
87 examples/plain_vs_mtc/TimeoutTestsPlain.java
@@ -0,0 +1,87 @@
+package plain_vs_mtc;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertFalse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.After;
+
+/**
+ * Timed offer times out if ArrayBlockingQueue is full and elements are not taken
+ */
+public class TimeoutTestsPlain
+{
+
+ /* NOTES
+ * - Uses freezeClock to prevent clock from advancing
+ * - This also guarantees that interrupt is on second offer
+ */
+
+ // Plain Version
+
+ volatile boolean threadFailed;
+
+ public void threadShouldThrow()
+ {
+ threadFailed = true;
+ fail("should throw exception");
+ }
+
+ public void threadAssertFalse(boolean b)
+ {
+ if (b)
+ {
+ threadFailed = true;
+ assertFalse(b);
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception
+ {
+ threadFailed = false;
+ }
+
+ @Test
+ public void testTimedOffer()
+ {
+ final ArrayBlockingQueue<Object> q = new ArrayBlockingQueue<Object>(2);
+ Thread t = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ q.put(new Object());
+ q.put(new Object());
+ threadAssertFalse(q.offer(new Object(), 25, TimeUnit.MILLISECONDS));
+ q.offer(new Object(), 2500, TimeUnit.MILLISECONDS);
+ threadShouldThrow();
+ } catch (InterruptedException success)
+ {
+ }
+ }
+ });
+
+ try
+ {
+ t.start();
+ Thread.sleep(50);
+ t.interrupt();
+ t.join();
+ } catch (Exception e)
+ {
+ fail("Unexpected exception");
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ assertFalse(threadFailed);
+ }
+
+}
48 examples/sampletests/MTCBoundedBufferDeadlockTest.java
@@ -0,0 +1,48 @@
+package sampletests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import static junit.framework.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Can we implement the Bounded Buffer using CountDownLatch? Nope, this causes a deadlock! But MTC can detect deadlocks. So we'll use the
+ * CountDownLatch version to demonstrate MTC's deadlock detection capabilities.
+ */
+public class MTCBoundedBufferDeadlockTest extends MultithreadedTestCase
+{
+ ArrayBlockingQueue<Integer> buf;
+ CountDownLatch c;
+
+ @Before
+ public void initialize()
+ {
+ buf = new ArrayBlockingQueue<Integer>(1);
+ c = new CountDownLatch(1);
+ }
+
+ @Threaded
+ public void threadPutPut() throws InterruptedException
+ {
+ buf.put(42);
+ buf.put(17);
+ c.countDown();
+ }
+
+ @Threaded
+ public void thread2() throws InterruptedException
+ {
+ c.await();
+ assertEquals(Integer.valueOf(42), buf.take());
+ assertEquals(Integer.valueOf(17), buf.take());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void test()
+ {
+ }
+}
47 examples/sampletests/MTCBoundedBufferTest.java
@@ -0,0 +1,47 @@
+package sampletests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+import junit.framework.Assert;
+import static junit.framework.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class MTCBoundedBufferTest extends MultithreadedTestCase
+{
+ ArrayBlockingQueue<Integer> buf;
+
+ @Before
+ public void initialize()
+ {
+ buf = new ArrayBlockingQueue<Integer>(1);
+ }
+
+ @Threaded
+ public void threadPutPut() throws InterruptedException
+ {
+ buf.put(42);
+ buf.put(17);
+ assertTick(1);
+ }
+
+ @Threaded
+ public void threadTakeTake() throws InterruptedException
+ {
+ waitForTick(1);
+ assertTrue(buf.take() == 42);
+ assertTrue(buf.take() == 17);
+ }
+
+ @Test
+ public void finish()
+ {
+ assertTrue(buf.isEmpty());
+ }
+}
33 examples/sanity/TestFrameworkTests.java
@@ -0,0 +1,33 @@
+package sanity;
+
+import edu.umd.cs.mtc.Threaded;
+import edu.umd.cs.mtc.*;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class TestFrameworkTests extends MultithreadedTestCase
+{
+
+ int i = 0;
+
+ @Threaded("1")
+ public void thisWillRunInThread1()
+ {
+ i++;
+ }
+
+ @Threaded("2")
+ public void thisWillRunInThread2()
+ {
+ waitForTick(1);
+ i++;
+ }
+
+ @Test
+ @MultithreadedTest(times = 3)
+ public void testRunThreeTimes() throws Throwable
+ {
+ assertEquals(i, 6);
+ }
+
+}
38 examples/sanity/basictests/SanityGetThreadByNameReturnsCorrectThread.java
@@ -0,0 +1,38 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.Threaded;
+import edu.umd.cs.mtc.*;
+import org.junit.Test;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityGetThreadByNameReturnsCorrectThread extends MultithreadedTestCase
+{
+ Thread t;
+
+ @Threaded("Fooey")
+ public void threadFooey()
+ {
+ t = Thread.currentThread();
+
+ waitForTick(2);
+ }
+
+ @Threaded
+ public void threadBooey()
+ {
+ waitForTick(1);
+ assertEquals("threadBooey", Thread.currentThread().getName());
+ assertSame(getThreadByName("Fooey"), t);
+ }
+
+ @Test
+ public void test()
+ {
+ assertEquals(t.getName(), "Fooey");
+ }
+
+}
33 examples/sanity/basictests/SanityGetThreadReturnsCorrectThread.java
@@ -0,0 +1,33 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import static org.junit.Assert.assertSame;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityGetThreadReturnsCorrectThread extends MultithreadedTestCase
+{
+ Thread t;
+
+ @Threaded("1")
+ public void thread1()
+ {
+ t = Thread.currentThread();
+ waitForTick(2);
+ }
+
+ @Threaded("2")
+ public void thread2()
+ {
+ waitForTick(1);
+ assertSame(getThread(1), t);
+ }
+
+ @Test
+ public void test()
+ {
+ }
+}
67 examples/sanity/basictests/SanityMetronomeOrder.java
@@ -0,0 +1,67 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import org.junit.Test;
+import org.junit.Before;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityMetronomeOrder extends MultithreadedTestCase
+{
+ String s;
+
+ @Before
+ public void initialize()
+ {
+ s = "";
+ }
+
+ @Threaded
+ public void thread1()
+ {
+ waitForTick(1);
+ s += "A";
+
+ waitForTick(3);
+ s += "C";
+
+ waitForTick(6);
+ s += "F";
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ waitForTick(2);
+ s += "B";
+
+ waitForTick(5);
+ s += "E";
+
+ waitForTick(8);
+ s += "H";
+ }
+
+ @Threaded
+ public void thread3()
+ {
+ waitForTick(4);
+ s += "D";
+
+ waitForTick(7);
+ s += "G";
+
+ waitForTick(9);
+ s += "I";
+ }
+
+ @Test
+ public void finish()
+ {
+ assertEquals("Threads were not called in correct order",
+ s, "ABCDEFGHI");
+ }
+}
40 examples/sanity/basictests/SanityThreadMethodsInvokedInDifferentThreads.java
@@ -0,0 +1,40 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import org.junit.Test;
+import static org.junit.Assert.assertNotSame;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityThreadMethodsInvokedInDifferentThreads extends MultithreadedTestCase
+{
+ Thread t1, t2;
+
+ @Threaded
+ public void thread1()
+ {
+ t1 = Thread.currentThread();
+ waitForTick(2);
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ t2 = Thread.currentThread();
+ waitForTick(2);
+ }
+
+ @Threaded
+ public void thread3()
+ {
+ waitForTick(1);
+ assertNotSame(t1, t2);
+ }
+
+ @Test
+ public void test()
+ {
+ }
+}
32 examples/sanity/basictests/SanityThreadTerminatesBeforeFinishIsCalled.java
@@ -0,0 +1,32 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityThreadTerminatesBeforeFinishIsCalled extends MultithreadedTestCase
+{
+ Thread t1, t2;
+
+ @Threaded
+ public void thread1()
+ {
+ t1 = Thread.currentThread();
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ t2 = Thread.currentThread();
+ }
+
+ @Test
+ public void finish()
+ {
+ org.junit.Assert.assertEquals(Thread.State.TERMINATED, t1.getState());
+ org.junit.Assert.assertEquals(Thread.State.TERMINATED, t2.getState());
+ }
+}
58 examples/sanity/basictests/SanityThreadsBeforeTest.java
@@ -0,0 +1,58 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.Threaded;
+import edu.umd.cs.mtc.*;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test order called is init, then thread, then finish
+ *
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityThreadsBeforeTest extends MultithreadedTestCase
+{
+ private AtomicInteger v1, v2;
+
+ CountDownLatch c;
+
+ @Before
+ public void initialize()
+ {
+ v1 = new AtomicInteger(0);
+ v2 = new AtomicInteger(0);
+ assertTrue(v1.compareAndSet(0, 1));
+ assertTrue(v2.compareAndSet(0, 1));
+ c = new CountDownLatch(2);
+ }
+
+ @Threaded("1")
+ public void thread1() throws InterruptedException
+ {
+ assertTrue(v1.compareAndSet(1, 2));
+ c.countDown();
+ c.await();
+ }
+
+ @Threaded("2")
+ public void thread2() throws InterruptedException
+ {
+ assertTrue(v2.compareAndSet(1, 2));
+ c.countDown();
+ c.await();
+ }
+
+ @Test
+ public void finish()
+ {
+ assertEquals(2, v1.intValue());
+ assertEquals(2, v2.intValue());
+ }
+}
49 examples/sanity/basictests/SanityWaitForTickAdvancesWhenTestsAreBlocked.java
@@ -0,0 +1,49 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+*/
+public class SanityWaitForTickAdvancesWhenTestsAreBlocked extends MultithreadedTestCase
+{
+ CountDownLatch c;
+
+ @Before
+ public void initialize() {
+ c = new CountDownLatch(3);
+ }
+
+ @Threaded
+ public void thread1() throws InterruptedException {
+ c.countDown();
+ c.await();
+ }
+
+ @Threaded
+ public void thread2() throws InterruptedException {
+ c.countDown();
+ c.await();
+ }
+
+ @Threaded
+ public void thread3() {
+ waitForTick(1);
+ assertEquals(1, c.getCount());
+ waitForTick(2); // advances quickly
+ assertEquals(1, c.getCount());
+ c.countDown();
+ }
+
+ @Test
+ public void finish() {
+ assertEquals(0, c.getCount());
+ }
+}
33 examples/sanity/basictests/SanityWaitForTickBlocksThread.java
@@ -0,0 +1,33 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class SanityWaitForTickBlocksThread extends MultithreadedTestCase
+{
+ Thread t;
+
+ @Threaded
+ public void thread1()
+ {
+ t = Thread.currentThread();
+ waitForTick(2);
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ waitForTick(1);
+ assertEquals(Thread.State.WAITING, t.getState());
+ }
+
+ @Test
+ public void test()
+ {
+ }
+}
31 examples/sanity/basictests/TUnitTestTestWithNoThreads.java
@@ -0,0 +1,31 @@
+package sanity.basictests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class TUnitTestTestWithNoThreads extends MultithreadedTestCase
+{
+ private AtomicInteger v1;
+
+ @Test
+ public void initialize()
+ {
+ v1 = new AtomicInteger(0);
+ assertTrue(v1.compareAndSet(0, 1));
+ }
+
+ @After
+ public void finish()
+ {
+ assertTrue(v1.compareAndSet(1, 2));
+ }
+}
46 examples/sanity/errordetectiontests/TUnitTestDeadlockDetected.java
@@ -0,0 +1,46 @@
+package sanity.errordetectiontests;
+
+import edu.umd.cs.mtc.Threaded;
+import edu.umd.cs.mtc.*;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class TUnitTestDeadlockDetected extends MultithreadedTestCase
+{
+ ReentrantLock lockA;
+ ReentrantLock lockB;
+
+ @Before
+ public void initialize()
+ {
+ lockA = new ReentrantLock();
+ lockB = new ReentrantLock();
+ }
+
+ @Threaded
+ public void threadA()
+ {
+ lockA.lock();
+ waitForTick(1);
+ lockB.lock();
+ }
+
+ @Threaded
+ public void threadB()
+ {
+ lockB.lock();
+ waitForTick(1);
+ lockA.lock();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void test()
+ {
+ }
+}
43 examples/sanity/errordetectiontests/TUnitTestLiveLockTimesOut.java
@@ -0,0 +1,43 @@
+package sanity.errordetectiontests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.Assert;
+import static junit.framework.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class TUnitTestLiveLockTimesOut extends MultithreadedTestCase
+{
+ AtomicInteger ai;
+
+ @Before
+ public void initialize()
+ {
+ ai = new AtomicInteger(1);
+ }
+
+ @Threaded
+ public void thread1()
+ {
+ while (!ai.compareAndSet(2, 3)) Thread.yield();
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ while (!ai.compareAndSet(3, 2)) Thread.yield();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void finish()
+ {
+ assertTrue(ai.get() == 2 || ai.get() == 3);
+ }
+}
29 examples/sanity/errordetectiontests/TUnitTestMissingUnfreeze.java
@@ -0,0 +1,29 @@
+package sanity.errordetectiontests;
+
+import edu.umd.cs.mtc.MultithreadedTestCase;
+import edu.umd.cs.mtc.Threaded;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class TUnitTestMissingUnfreeze extends MultithreadedTestCase
+{
+ @Threaded
+ public void thread1() throws InterruptedException
+ {
+ freezeClock();
+ Thread.sleep(200);
+ }
+
+ @Threaded
+ public void thread2()
+ {
+ waitForTick(1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void test()
+ {
+ }
+}
29 examples/sanity/package-info.java
@@ -0,0 +1,29 @@
+/**
+ * The sanity package contains acceptance tests that test all the functionality in
+ * MultithreadedTC. These tests should be run whenever changes are made to
+ * MultithreadedTC.
+ * <p>
+ *
+ * Basic tests - Thread order and blocking based tests, waitForTick delay,
+ * <p>
+ *
+ * General TestFramework tests - getting/setting parameters, run many times, guarantee that threads are started in random order, interleaving from mayyield
+ * <p>
+ *
+ * Test TestFramework.buildTestSuite (a class with many kinds of inner classes, all detected and run)
+ * <p>
+ *
+ * Customized wait - test customized waiting functions
+ * <p>
+ *
+ * Timing issues - test freezeclock, tests with timing
+ * <p>
+ *
+ * Error detection tests - tests all the error states detected by framework, throw exception in thread
+ *
+ * @author William Pugh
+ * @author Nathaniel Ayewah
+ * @since 1.0
+ */
+package sanity;
+
43 examples/sanity/timing/TUnitTestClockDoesNotAdvanceWhenFrozen.java
@@ -0,0 +1,43 @@
+package sanity.timing;
+
+import edu.umd.cs.mtc.Threaded;
+import edu.umd.cs.mtc.*;
+import static junit.framework.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class TUnitTestClockDoesNotAdvanceWhenFrozen extends MultithreadedTestCase
+{
+ String s;
+
+ @Before
+ public void initialize()
+ {
+ s = "A";
+ }
+
+ @Threaded("first")
+ public void thread1() throws InterruptedException
+ {
+ freezeClock();
+ Thread.sleep(200);
+ assertEquals("Clock advanced while thread was sleeping", s, "A");
+ unfreezeClock();
+ }
+
+ @Threaded("second")
+ public void thread2()
+ {
+ waitForTick(1);
+ s = "B";
+ }
+
+ @Test
+ public void finish()
+ {
+ assertEquals(s, "B");
+ }
+}
36 src/edu/umd/cs/mtc/MultiThreadedRunner.java
@@ -0,0 +1,36 @@
+package edu.umd.cs.mtc;
+
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+/**
+ * A junit runner which runs the threaded methods before the actual junit test method.
+ *
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+public class MultiThreadedRunner extends BlockJUnit4ClassRunner
+{
+ /**
+ * Creates a BlockJUnit4ClassRunner to run {@code klass}
+ *
+ * @throws org.junit.runners.model.InitializationError
+ * if the test class is malformed.
+ */
+ public MultiThreadedRunner(Class<?> klass)
+ throws InitializationError
+ {
+ super(klass);
+ }
+
+ @Override
+ protected Statement methodInvoker(FrameworkMethod method, Object test)
+ {
+ final MultithreadedTest multithreadedTestAnnotation = method.getAnnotation(MultithreadedTest.class);
+ if (multithreadedTestAnnotation != null)
+ return new RunThreadsThenInvokeMethod(method, test, multithreadedTestAnnotation.times());
+ else
+ return new RunThreadsThenInvokeMethod(method, test, 1);
+ }
+}
18 src/edu/umd/cs/mtc/MultithreadedTest.java
@@ -0,0 +1,18 @@
+package edu.umd.cs.mtc;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * Annotation to indicate that a test triggers a multithreaded test case and that the threads should be run multiple times.
+ *
+ * @author <a href="mailto:jvb@newtec.eu">Jan Van Besien</a>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface MultithreadedTest
+{
+ int times() default 1;
+}
425 src/edu/umd/cs/mtc/MultithreadedTestCase.java
@@ -0,0 +1,425 @@
+package edu.umd.cs.mtc;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+
+/**
+ * This is the base class for each test in the MultithreadedTC framework. To create a multithreaded test case, simply extend this class. Any
+ * method annotated with the {@link Threaded} annotation is a thread method. Each thread method will be run in a seperate thread. JUnit test
+ * methods in your class will be run after the thread methods have finished.
+ * <p/>
+ * <p/>
+ * You can annotate your junit test methods with the {@link MultithreadedTest} method to specify how many times the threaded methods should
+ * be run before the junit test method is executed.
+ * <p/>
+ * <p/>
+ * There are several additional methods you can use in designing test cases. The MultithreadedTestCase maintains a metronome or clock, and
+ * ticks off intervals. You can get the current tick with {@link #getTick()} and you can wait until a particular tick with {@link
+ * #waitForTick(int)}. The metronome isn't a free running clock; it only advances to the next tick when all threads are blocked or waiting.
+ * Also, when all threads are blocked, if at least one thread isn't waiting for the metronome to advance, the system declares a deadlock to
+ * have occurred and terminates the test case (unless one of the threads is in state TIMED_WAITING).
+ * <p/>
+ * <p/>
+ * You can set a command line parameter -Dtunit.trace=true to cause tracing messages to be printed by the metronome frame, or invoke {@link
+ * MultithreadedTestCase#setTrace(boolean)} to turn tracing on or off.
+ * <p/>
+ * <p/>
+ * You can set command line parameter -Dtunit.runLimit=10 to cause a test case to fail if at least one thread stays in a runnable state for
+ * more than 10 seconds without becoming blocked or waiting for a metronome tick. Use different values for shorter or longer time limits.
+ *
+ * @author William Pugh
+ * @author Nathaniel Ayewah
+ * @see Threaded
+ * @see MultithreadedTest
+ * @since 1.0
+ */
+@RunWith(MultiThreadedRunner.class)
+abstract public class MultithreadedTestCase
+{
+
+ /**
+ * The metronome used to coordinate between threads. This clock is advanced by the clock thread started by {@link
+ * RunThreadsThenInvokeMethod}. The clock will not advance if it is frozen.
+ *
+ * @see #waitForTick(int)
+ * @see #freezeClock()
+ * @see #unfreezeClock()
+ */
+ int clock;
+
+ /**
+ * The primary lock to synchronize on in this test case before accessing fields in this class.
+ */
+ final Object lock = new Object();
+
+ /**
+ * If true, the debugging information is printed to standard out while the test runs
+ */
+ private boolean trace = Boolean.getBoolean("tunit.trace");
+
+ /**
+ * This flag is set to true when a test fails due to deadlock or timeout.
+ *
+ * @see RunThreadsThenInvokeMethod
+ */
+ boolean failed;
+
+ /**
+ * @param trace the trace to set
+ */
+ public void setTrace(boolean trace)
+ {
+ this.trace = trace;
+ }
+
+ /**
+ * @return the trace
+ */
+ public boolean getTrace()
+ {
+ return trace;
+ }
+
+
+ // =======================
+ // -- Thread Management --
+ // - - - - - - - - - - - -
+
+ /**
+ * Map each thread to the clock tick it is waiting for.
+ */
+ IdentityHashMap<Thread, Integer> threads = new IdentityHashMap<Thread, Integer>();
+
+ /**
+ * ThreadLocal containing a reference to the current instance of this class for each thread. When a thread completes or dies, its
+ * reference to this class is removed.
+ */
+ static ThreadLocal<MultithreadedTestCase> currentTestCase =
+ new ThreadLocal<MultithreadedTestCase>();
+
+ /**
+ * This method is called right after a new testcase thread is created by the {@link RunThreadsThenInvokeMethod}. It provides initial
+ * values for {@link #currentTestCase} and {@link #threads}.
+ */
+ void hello()
+ {
+ currentTestCase.set(this);
+ synchronized (lock)
+ {
+ Thread currentThread = Thread.currentThread();
+ threads.put(currentThread, 0);
+ }
+
+ }
+
+ /**
+ * This method is called just before a testcase thread completes. It cleans out {@link #currentTestCase} and {@link #threads}.
+ */
+ void goodbye()
+ {
+ synchronized (lock)
+ {
+ Thread currentThread = Thread.currentThread();
+ threads.remove(currentThread);
+ }
+ currentTestCase.set(null);
+ }
+
+ /**
+ * Map a thread name to all test case threads as they are created, primarily so that they can be accessed by each other.
+ *
+ * @see #getThreadByName(String)
+ * @see #getThread(int)
+ */
+ HashMap<String, Thread> methodThreads = new HashMap<String, Thread>();
+
+ /**
+ * Get a thread given the method name that it corresponds to. E.g. to get the thread running the contents of the method
+ * <code>thread1()</code>, call <code>getThreadByName("thread1")</code>
+ *
+ * @param methodName the name of the method corresponding to the thread requested
+ * @return the thread corresponding to methodName
+ * @see #getThread(int)
+ */
+ public Thread getThreadByName(String methodName)
+ {
+ synchronized (lock)
+ {
+ return methodThreads.get(methodName);
+ }
+ }
+
+ /**
+ * Get a thread corresponding to the method whose name is formed using the prefix "thread" followed by an integer (represented by
+ * <code>index</code>. e.g. getThread(1) returns the thread that <code>thread1()</code> is running in.
+ *
+ * @param index an integer following "thread" in the name of the method
+ * @return the Thread corresponding to this method
+ * @see #getThreadByName(String)
+ */
+ public Thread getThread(int index)
+ {
+ return getThreadByName("" + index);
+ }
+
+ /**
+ * Associates a thread with given method name. If the method name is already associated with a Thread, the old thread is returned,
+ * otherwise null is returned
+ */
+ public Thread putThread(String methodName, Thread t)
+ {
+ synchronized (lock)
+ {
+ return methodThreads.put(methodName, t);
+ }
+ }
+
+
+ // ===========================
+ // -- Clock tick management --
+ // - - - - - - - - - - - - - -
+
+ /**
+ * Force this thread to block until the thread metronome reaches the specified value, at which point the thread is unblocked.
+ *
+ * @param c the tick value to wait for
+ */
+ public void waitForTick(int c)
+ {
+ synchronized (lock)
+ {
+ threads.put(Thread.currentThread(), c);
+ while (!failed && clock < c)
+ try
+ {
+ if (getTrace())
+ System.out.println(Thread.currentThread().getName()
+ + " is waiting for time " + c);
+ lock.wait();
+ } catch (InterruptedException e)
+ {
+ throw new AssertionError(e);
+ }
+ if (failed)
+ throw new IllegalStateException("Clock never reached " + c);
+ if (getTrace())
+ System.out.println("Releasing "
+ + Thread.currentThread().getName() + " at time "
+ + clock);
+ }
+ }
+
+ /**
+ * An Enum-based version of waitForTick. It simply looks up the ordinal and adds 1 to determine the clock tick to wait for.
+ *
+ * @param e An Enum representing the tick to wait for. The first enumeration constant represents tick 1, the second is tick 2, etc.
+ * @see #waitForTick(int)
+ */
+ public void waitForTick(Enum e)
+ {
+ waitForTick(e.ordinal() + 1);
+ }
+
+ /**
+ * Gets the current value of the thread metronome. Primarily useful in assert statements.
+ *
+ * @return the current tick value
+ * @see #assertTick(int)
+ */
+ public int getTick()
+ {
+ synchronized (lock)
+ {
+ return clock;
+ }
+ }
+
+ /**
+ * Assert that the clock is in tick <code>tick</code>
+ *
+ * @param tick a number >= 0
+ */
+ public void assertTick(int tick)
+ {
+ assertEquals(tick, getTick());
+ }
+
+
+ // =======================================
+ // -- Components for freezing the clock --
+ // - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Read locks are acquired when clock is frozen and must be released before the clock can advance in a waitForTick().
+ */
+ final ReentrantReadWriteLock clockLock = new ReentrantReadWriteLock();
+
+ /**
+ * When the clock is frozen, it will not advance even when all threads are blocked. Use this to block the current thread with a time
+ * limit, but prevent the clock from advancing due to a {@link #waitForTick(int)} in another thread. This statements that occur when
+ * clock is frozen should be followed by {@link #unfreezeClock()} in the same thread.
+ */
+ public void freezeClock()
+ {
+ clockLock.readLock().lock();
+ }
+
+ /**
+ * Unfreeze a clock that has been frozen by {@link #freezeClock()}. Both methods must be called from the same thread.
+ */
+ public void unfreezeClock()
+ {
+ clockLock.readLock().unlock();
+ }
+
+ /**
+ * Check if the clock has been frozen by any threads.
+ */
+ public boolean isClockFrozen()
+ {
+ return clockLock.getReadLockCount() > 0;
+ }
+
+
+ // ===============================
+ // -- Customized Wait Functions --
+ // - - - - - - - - - - - - - - - -
+
+ /**
+ * A boolean flag for each thread indicating whether the next call to {@link #waitOn(Object)} or {@link #awaitOn(Condition)} should
+ * return immediately.
+ *
+ * @see #skipNextWait()
+ */
+ private static ThreadLocal<Boolean> skipNextWait = new ThreadLocal<Boolean>()
+ {
+ @Override
+ public Boolean initialValue()
+ {
+ return Boolean.FALSE;
+ }
+ };
+
+ /**
+ * When this method is called from a thread, the next call to {@link #waitOn(Object)} or {@link #awaitOn(Condition)} will return
+ * immediately without blocking. Use this to make tests more robust.
+ */
+ static public void skipNextWait()
+ {
+ skipNextWait.set(true);
+ }
+
+ /**
+ * This method is a replacement for {@link Object#wait()}. It suppresses the {@link InterruptedException} that you would otherwise have
+ * to deal with, and allows automated skipping of the next wait. The method {@link #skipNextWait()} will force that thread to
+ * immediately return from the next call to this method. Designing your tests so that they work even if {@link Object#wait()}
+ * occasionally returns immediately will make your code much more robust in face of several potential threading issues.
+ *
+ * @param o the object to wait on
+ */
+ static public void waitOn(Object o)
+ {
+ // System.out.println("About to wait on " + System.identityHashCode(o));
+ MultithreadedTestCase thisTestCase = currentTestCase.get();
+ if (thisTestCase != null && thisTestCase.failed)
+ throw new RuntimeException("Test case has failed");
+ if (skipNextWait.get())
+ {
+ skipNextWait.set(false);
+ return;
+ }
+ try
+ {
+ o.wait(3000);
+ } catch (InterruptedException e)
+ {
+ throw new AssertionError(e);
+ } catch (IllegalMonitorStateException e)
+ {
+ System.out.println("Got illegal monitor state exception");
+ }
+ if (thisTestCase != null && thisTestCase.failed)
+ throw new RuntimeException("Test case has failed");
+ // System.out.println("waited on " + System.identityHashCode(o));
+ }
+
+ /**
+ * This method is a replacement for {@link Condition#await()}. It suppresses the {@link InterruptedException} that you would otherwise
+ * have to deal with, and allows automated skipping of the next wait. The method {@link #skipNextWait()} will force that thread to
+ * immediately return from the next call to this method. Designing your tests so that they work even if {@link Condition#await()}
+ * occasionally returns immediately will make your code much more robust in face of several potential threading issues.
+ *
+ * @param c the condition to await on
+ */
+ static public void awaitOn(Condition c)
+ {
+ MultithreadedTestCase thisTestCase = currentTestCase.get();
+ if (thisTestCase != null && thisTestCase.failed)
+ throw new RuntimeException("Test case has failed");
+
+ if (skipNextWait.get())
+ {
+ skipNextWait.set(false);
+ return;
+ }
+ try
+ {
+ c.await(3, TimeUnit.SECONDS);
+ } catch (InterruptedException e)
+ {
+ if (thisTestCase != null && thisTestCase.failed)
+ throw new RuntimeException("Test case has failed");
+ throw new AssertionError(e);
+ }
+ if (thisTestCase != null && thisTestCase.failed)
+ throw new RuntimeException("Test case has failed");
+
+ }
+
+
+ // ==================
+ // -- Experimental --
+ // -- - - - - - - - -
+
+ /**
+ * A ThreadLocal that contains a Random number generator. This is used in {@link #mayYield()}
+ *
+ * @see #mayYield()
+ */
+ private static ThreadLocal<Random> mtcRandomizer = new ThreadLocal<Random>()
+ {
+ @Override
+ public Random initialValue()
+ {
+ return new Random();
+ }
+ };
+
+ /**
+ * Calling this method from one of the test threads may cause the thread to yield. Use this between statements to generate more
+ * interleavings.
+ */
+ public void mayYield()
+ {
+ mayYield(0.5);
+ }
+
+ /**
+ * Calling this method from one of the test threads may cause the thread to yield. Use this between statements to generate more
+ * interleavings.
+ *
+ * @param probability (a number between 0 and 1) the likelihood that Thread.yield() is called
+ */
+ public void mayYield(double probability)
+ {
+ if (mtcRandomizer.get().nextDouble() < probability) Thread.yield();
+ }
+}
670 src/edu/umd/cs/mtc/RunThreadsThenInvokeMethod.java
@@ -0,0 +1,670 @@
+package edu.umd.cs.mtc;
+
+import org.junit.internal.runners.statements.InvokeMethod;
+import org.junit.runners.model.FrameworkMethod;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class plugs the {@link MultithreadedTestCase} into the JUnit framework.
+ * <p/>
+ * <p/>
+ * Each test case starts by running all the thread methods in different threads. After that, the actual junit test method will be executed
+ * when all threads have finished. The thread methods are run in a new thread group, and are regulated by a separate clock thread. The clock
+ * thread checks periodically to see if all threads are blocked. If all threads are blocked and at least one is waiting for a tick, the
+ * clock thread advances the clock to the next desired tick. (A slight delay -- about a clock period -- is applied before advancing the
+ * clock to ensure that this is not done prematurely and any threads trying to unblock are given a chance to do so.)
+ * <p/>
+ * <p/>
+ * The clock thread also detects deadlock (when all threads are blocked, none are waiting for a tick, and none are in state TIMED_WAITING),
+ * and can stop a test that is going on too long (a thread is in state RUNNABLE for too long.)
+ * <p/>
+ * <p/>
+ * Since the test case threads are placed in a new thread group, any other threads created by these test cases will be placed in this thread
+ * group by default. All threads in the thread group will be considered by the clock thread when deciding whether to advance the clock,
+ * declare a deadlock, or stop a long-running test.
+ * <p/>
+ * <p/>
+ * The framework catches exceptions thrown in the threads and propagates them to the JUnit test (It also throws AssertionErrors)
+ * <p/>
+ * <p/>
+ * This class also defines a number of parameters to be used to control the tests. Set command line parameter -Dtunit.runLimit=<em>n</em> to
+ * cause a test case to fail if at least one thread stays in a runnable state for more than <em>n</em> seconds without becoming blocked or
+ * waiting for a metronome tick. Set command line parameter -Dtunit.clockPeriod=<em>p</em> to cause the clock thread to check the status of
+ * all the threads every <em>p</em> milliseconds.
+ *
+ * @author William Pugh
+ * @author Nathaniel Ayewah
+ * @see MultithreadedTestCase
+ * @see #runOnce(MultithreadedTestCase)
+ * @see #runManyTimes(MultithreadedTestCase, int)
+ * @since 1.0
+ */
+public class RunThreadsThenInvokeMethod extends InvokeMethod
+{
+ private final MultithreadedTestCase test;
+
+ private final int runTimes;
+
+ public RunThreadsThenInvokeMethod(FrameworkMethod testMethod, Object target, int runTimes)
+ {
+ super(testMethod, target);
+ this.test = (MultithreadedTestCase) target;
+ this.runTimes = runTimes;
+ }
+
+ /**
+ * Command line key for indicating the regularity (in milliseconds) with which the clock thread regulates the thread methods.
+ */
+ public static final String CLOCKPERIOD_KEY = "tunit.clockPeriod";
+
+ /**
+ * Command line key for indicating the time limit (in seconds) for runnable threads.
+ */
+ public static final String RUNLIMIT_KEY = "tunit.runLimit";
+
+ /**
+ * The default clock period in milliseconds
+ */
+ public static final Integer DEFAULT_CLOCKPERIOD = 10;
+
+ /**
+ * The default run limit in seconds
+ */
+ public static final Integer DEFAULT_RUNLIMIT = 5;
+
+ /**
+ * Change/set the system property for the clock period
+ *
+ * @param v the new value for the clock period
+ */
+ public static void setGlobalClockPeriod(Integer v)
+ {
+ if (v != null)
+ System.setProperty(CLOCKPERIOD_KEY, v.toString());
+ }
+
+ /**
+ * Change/set the system property for the run limit
+ *
+ * @param v the new value for the run limit
+ */
+ public static void setGlobalRunLimit(Integer v)
+ {
+ if (v != null)
+ System.setProperty(RUNLIMIT_KEY, v.toString());
+ }
+
+
+ /**
+ * Run multithreaded test case multiple times using the default or global settings for clock period and run limit. This method adds
+ * instrumentation to count the number of times failures occur (an exception is thrown). If the array <code>failureCount</code> is
+ * initialized to be of at least size 1, it returns this count in <code>failureCount[0]</code>. If failures do occur, it saves the first
+ * failure, and then throws it after running the test <code>count</code> times.
+ *
+ * @param test The multithreaded test case to run
+ * @param count the number of times to run the test case
+ * @param failureCount if this array is initialzed to at least size 1, the number of failures is returned in
+ * <code>failureCount[0]</code>
+ * @throws Throwable if there is at least one failure -- the first failure is thrown
+ */
+ public static void runInstrumentedManyTimes(final MultithreadedTestCase test, int count,
+ int[] failureCount) throws Throwable
+ {
+ int failures = 0;
+ Throwable t = null;
+ boolean failed = false;
+
+ System.out.println("Testing " + test.getClass());
+
+ for (int i = 0; i < count; i++)
+ {
+ try
+ {
+ runOnce(test);
+ } catch (Throwable e)
+ {
+ failed = true;
+ failures++;
+ if (t == null)
+ t = e;
+ }
+ if (i % 10 == 9)
+ {
+ if (failed)
+ {
+ System.out.print("f");
+ failed = false;
+ } else System.out.print(".");
+ if (i % 100 == 99) System.out.println(" " + (i + 1));
+ }
+ }
+ if (failureCount != null && failureCount.length > 0)
+ failureCount[0] = failures;
+ if (t != null)
+ throw t;
+ }
+
+
+ /**
+ * Run multithreaded test case multiple times using the default or global settings for clock period and run limit. The value of this is
+ * limited, since even running a test case a thousand or a million times may not expose any bugs dependent upon particular thread
+ * interleavings.
+ *
+ * @param test The multithreaded test case to run
+ * @param count the number of times to run the test case
+ * @throws Throwable -- if any of the test runs fails, the exception is thrown immediately without completing the rest of the test
+ * runs.
+ */
+ public static void runManyTimes(final MultithreadedTestCase test, int count)
+ throws Throwable
+ {
+ runManyTimes(test, count, null, null);
+ }
+
+ /**
+ * Run multithreaded test case multiple times. The value of this is limited, since even running a test case a thousand or a million
+ * times may not expose any bugs dependent upon particular thread interleavings.
+ *
+ * @param test The multithreaded test case to run
+ * @param count the number of times to run the test case
+ * @param clockPeriod The period (in ms) between checks for the clock (or null for default or global setting)
+ * @param runLimit The limit to run the test in seconds (or null for default or global setting)
+ * @throws Throwable -- if any of the test runs fails, the exception is thrown immediately without completing the rest of the test
+ * runs.
+ */
+ public static void runManyTimes(final MultithreadedTestCase test, int count,
+ Integer clockPeriod, Integer runLimit)
+ throws Throwable
+ {
+ for (int i = 0; i < count; i++)
+ runOnce(test, clockPeriod, runLimit);
+ }
+
+
+ /**
+ * Run a multithreaded test case once, using the default or global settings for clock period and run limit
+ *
+ * @param test The multithreaded test case to run
+ * @throws Throwable if the test runs fails or causes an exception
+ */
+ public static void runOnce(final MultithreadedTestCase test)
+ throws Throwable
+ {
+ runOnce(test, null, null);
+ }
+
+ /**
+ * Run multithreaded test case once.
+ *
+ * @param test The multithreaded test case to run
+ * @param clockPeriod The period (in ms) between checks for the clock (or null for default or global setting)
+ * @param runLimit The limit to run the test in seconds (or null for default or global setting)
+ * @throws Throwable if the test runs fails or causes an exception
+ */
+ public static void runOnce(final MultithreadedTestCase test,
+ Integer clockPeriod, Integer runLimit)
+ throws Throwable
+ {
+
+ // choose global setting if parameter is null, or default value if there
+ // is no global setting
+
+ if (clockPeriod == null)
+ clockPeriod = Integer.getInteger(CLOCKPERIOD_KEY, DEFAULT_CLOCKPERIOD);
+
+ if (runLimit == null)
+ runLimit = Integer.getInteger(RUNLIMIT_KEY, DEFAULT_RUNLIMIT);
+
+ // prepare run data structures
+ final SortedMap<String, Method> methods = getAllThreads(test);
+ LinkedList<java.lang.Thread> threads = new LinkedList<java.lang.Thread>();
+ final Throwable[] error = new Throwable[1];
+
+ // invoke initialize method before each run
+ //test.initialize();
+ test.clock = 0;
+
+ // invoke each thread method in a seperate thread and place all threads in a
+ // new thread group
+ ThreadGroup threadGroup = startMethodThreads(test, methods, threads, error);
+
+ // start and add clock thread
+ threads.add(startClock(test, threadGroup, error, clockPeriod, runLimit));
+
+ // wait until all threads have ended
+ waitForMethodThreads(threads, error);
+
+ // invoke finish at the end of each run
+ //test.finish();
+ }
+
+ /**
+ * Use reflection to get the thread methods in this test. Thread methods start with the name "thread", have no parameters and return
+ * void
+ *
+ * @param test the test case from which to extract methods
+ * @return a collection of Method objects, one for each thread method, mapped by name
+ */
+ private static SortedMap<String, Method> getAllThreads(MultithreadedTestCase test)
+ {
+ Class c = test.getClass();
+
+ SortedMap<String, Method> result = new TreeMap<String, Method>();
+ for (Class<?> eachClass : getSuperClasses(test.getClass()))
+ {
+ Method[] methods = eachClass.getDeclaredMethods();
+ for (Method method : methods)
+ {
+ final Threaded annotation = method.getAnnotation(Threaded.class);
+ if (annotation != null)
+ result.put("".equals(annotation.value()) ? method.getName() : annotation.value(), method);
+ }
+ }
+ return result;
+ }
+
+ private static List<Class<?>> getSuperClasses(Class<?> testClass)
+ {
+ ArrayList<Class<?>> results = new ArrayList<Class<?>>();
+ Class<?> current = testClass;
+ while (current != null)
+ {
+ results.add(current);
+ current = current.getSuperclass();
+ }
+ return results;
+ }
+
+ /**
+ * Start and return a clock thread which periodically checks all the test case threads and regulates them.
+ * <p/>
+ * <p/>
+ * If all the threads are blocked and at least one is waiting for a tick, the clock advances to the next tick and the waiting thread is
+ * notified. If none of the threads are waiting for a tick or in timed waiting, a deadlock is detected. The clock thread times out if a
+ * thread is in runnable or all are blocked and one is in timed waiting for longer than the runLimit.
+ *
+ * @param test the test case the clock thread is regulating
+ * @param threadGroup the thread group containing the running thread methods
+ * @param error an array containing any Errors/Exceptions that occur in thread methods or that are thrown by the clock thread
+ * @param clockPeriod The period (in ms) between checks for the clock (or null for default or global setting)
+ * @param runLimit The limit to run the test in seconds (or null for default or global setting)
+ * @return The (already started) clock thread
+ */
+ private static java.lang.Thread startClock(
+ final MultithreadedTestCase test,
+ final ThreadGroup threadGroup,
+ final Throwable[] error,
+ final int clockPeriod,
+ final int runLimit)
+ {
+
+ // hold a reference to the current thread. This thread
+ // will be waiting for all the test threads to finish. It
+ // should be interrupted if there is an deadlock or timeout
+ // in the clock thread
+ final java.lang.Thread mainThread = java.lang.Thread.currentThread();
+
+ java.lang.Thread t = new java.lang.Thread("Tick thread")
+ {
+ public void run()
+ {
+ try
+ {
+ long lastProgress = System.currentTimeMillis();
+ int deadlocksDetected = 0;
+ int readyToTick = 0;
+ while (true)
+ {
+
+ java.lang.Thread.sleep(clockPeriod);
+
+ // Attempt to get a write lock; this succeeds
+ // if clock is not frozen
+ if (!test.clockLock.writeLock().tryLock(
+ 1000L * runLimit, TimeUnit.MILLISECONDS))
+ {
+ synchronized (test.lock)
+ {
+ test.failed = true;
+ test.lock.notifyAll();
+ if (error[0] == null)
+ error[0] = new IllegalStateException(
+ "No progress");
+ mainThread.interrupt();
+ return;
+ }
+ }
+
+ synchronized (test.lock)
+ {
+
+ try
+ {
+
+ // Get the contents of the thread group
+ int tgCount = threadGroup.activeCount() + 10;
+ java.lang.Thread[] ths = new java.lang.Thread[tgCount];
+ tgCount = threadGroup.enumerate(ths, false);
+ if (tgCount == 0) return; // all threads are done
+
+ // will set to true to force a check for timeout conditions
+ // and restart the loop
+ boolean checkProgress = false;
+
+ // will set true if any thread is in state TIMED_WAITING
+ boolean timedWaiting = false;
+
+ int nextTick = Integer.MAX_VALUE;
+
+ // examine the threads in the thread group; look for
+ // next tick
+ for (int ii = 0; ii < tgCount; ii++)
+ {
+ java.lang.Thread t = ths[ii];
+
+ try
+ {
+ if (test.getTrace())
+ System.out.println(t.getName() + " is in state "
+ + t.getState());
+
+ if (t.getState() == java.lang.Thread.State.RUNNABLE)
+ checkProgress = true;
+ if (t.getState() == java.lang.Thread.State.TIMED_WAITING)
+ timedWaiting = true;
+ } catch (Throwable e)
+ {
+ // JVM may not support Threaded.State
+ checkProgress = false;
+ timedWaiting = true;
+ }
+
+ Integer waitingFor = test.threads.get(t);
+ if (waitingFor != null && waitingFor > test.clock)
+ nextTick = Math.min(nextTick, waitingFor);
+ }
+
+ // If not waiting for anything, but a thread is in
+ // TIMED_WAITING, then check progress and loop again
+ if (nextTick == Integer.MAX_VALUE && timedWaiting)
+ checkProgress = true;
+
+ // Check for timeout conditions and restart the loop
+ if (checkProgress)
+ {
+ if (readyToTick > 0)
+ {
+ if (test.getTrace())
+ System.out.println("Was Ready to tick too early");
+ readyToTick = 0;
+ }
+ long now = System.currentTimeMillis();
+ if (now - lastProgress > 1000L * runLimit)
+ {
+ if (false) for (int ii = 0; ii < tgCount; ii++)
+ {
+ java.lang.Thread t = ths[ii];