--- jsr166/src/test/tck/JSR166TestCase.java 2015/04/21 05:00:23 1.130 +++ jsr166/src/test/tck/JSR166TestCase.java 2018/04/05 03:36:54 1.243 @@ -1,12 +1,47 @@ /* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ * Other contributors include Andrew Wright, Jeffrey Hayes, * Pat Fisher, Mike Judd. */ +/* + * @test + * @summary JSR-166 tck tests, in a number of variations. + * The first is the conformance testing variant, + * while others also test implementation details. + * @build * + * @modules java.management + * @run junit/othervm/timeout=1000 JSR166TestCase + * @run junit/othervm/timeout=1000 + * --add-opens java.base/java.util.concurrent=ALL-UNNAMED + * --add-opens java.base/java.lang=ALL-UNNAMED + * -Djsr166.testImplementationDetails=true + * JSR166TestCase + * @run junit/othervm/timeout=1000 + * --add-opens java.base/java.util.concurrent=ALL-UNNAMED + * --add-opens java.base/java.lang=ALL-UNNAMED + * -Djsr166.testImplementationDetails=true + * -Djava.util.concurrent.ForkJoinPool.common.parallelism=0 + * JSR166TestCase + * @run junit/othervm/timeout=1000 + * --add-opens java.base/java.util.concurrent=ALL-UNNAMED + * --add-opens java.base/java.lang=ALL-UNNAMED + * -Djsr166.testImplementationDetails=true + * -Djava.util.concurrent.ForkJoinPool.common.parallelism=1 + * -Djava.util.secureRandomSeed=true + * JSR166TestCase + * @run junit/othervm/timeout=1000/policy=tck.policy + * --add-opens java.base/java.util.concurrent=ALL-UNNAMED + * --add-opens java.base/java.lang=ALL-UNNAMED + * -Djsr166.testImplementationDetails=true + * JSR166TestCase + */ + import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import java.io.ByteArrayInputStream; @@ -15,7 +50,10 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; @@ -25,31 +63,48 @@ import java.security.ProtectionDomain; import java.security.SecurityPermission; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Date; +import java.util.Deque; import java.util.Enumeration; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.PropertyPermission; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import java.util.concurrent.RecursiveAction; import java.util.concurrent.RecursiveTask; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.Semaphore; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; -import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestCase; +import junit.framework.TestResult; import junit.framework.TestSuite; /** @@ -62,18 +117,18 @@ import junit.framework.TestSuite; * *
    * - *
  1. All assertions in code running in generated threads must use + *
  2. All assertions in code running in generated threads must use * the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link * #threadAssertEquals}, or {@link #threadAssertNull}, (not * {@code fail}, {@code assertTrue}, etc.) It is OK (but not * particularly recommended) for other code to use these forms too. * Only the most typically used JUnit assertion methods are defined - * this way, but enough to live with.
  3. + * this way, but enough to live with. * - *
  4. If you override {@link #setUp} or {@link #tearDown}, make sure + *
  5. If you override {@link #setUp} or {@link #tearDown}, make sure * to invoke {@code super.setUp} and {@code super.tearDown} within * them. These methods are used to clear and check for thread - * assertion failures.
  6. + * assertion failures. * *
  7. All delays and timeouts must use one of the constants {@code * SHORT_DELAY_MS}, {@code SMALL_DELAY_MS}, {@code MEDIUM_DELAY_MS}, @@ -84,44 +139,43 @@ import junit.framework.TestSuite; * is always discriminable as larger than SHORT and smaller than * MEDIUM. And so on. These constants are set to conservative values, * but even so, if there is ever any doubt, they can all be increased - * in one spot to rerun tests on slower platforms.
  8. + * in one spot to rerun tests on slower platforms. * - *
  9. All threads generated must be joined inside each test case + *
  10. All threads generated must be joined inside each test case * method (or {@code fail} to do so) before returning from the * method. The {@code joinPool} method can be used to do this when - * using Executors.
  11. + * using Executors. * *
* *

Other notes *

*/ @@ -160,6 +214,53 @@ public class JSR166TestCase extends Test Integer.getInteger("jsr166.runsPerTest", 1); /** + * The number of repetitions of the test suite (for finding leaks?). + */ + private static final int suiteRuns = + Integer.getInteger("jsr166.suiteRuns", 1); + + /** + * Returns the value of the system property, or NaN if not defined. + */ + private static float systemPropertyValue(String name) { + String floatString = System.getProperty(name); + if (floatString == null) + return Float.NaN; + try { + return Float.parseFloat(floatString); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException( + String.format("Bad float value in system property %s=%s", + name, floatString)); + } + } + + /** + * The scaling factor to apply to standard delays used in tests. + * May be initialized from any of: + * - the "jsr166.delay.factor" system property + * - the "test.timeout.factor" system property (as used by jtreg) + * See: http://openjdk.java.net/jtreg/tag-spec.html + * - hard-coded fuzz factor when using a known slowpoke VM + */ + private static final float delayFactor = delayFactor(); + + private static float delayFactor() { + float x; + if (!Float.isNaN(x = systemPropertyValue("jsr166.delay.factor"))) + return x; + if (!Float.isNaN(x = systemPropertyValue("test.timeout.factor"))) + return x; + String prop = System.getProperty("java.vm.version"); + if (prop != null && prop.matches(".*debug.*")) + return 4.0f; // How much slower is fastdebug than product?! + return 1.0f; + } + + public JSR166TestCase() { super(); } + public JSR166TestCase(String name) { super(name); } + + /** * A filter for tests to run, matching strings of the form * methodName(className), e.g. "testInvokeAll5(ForkJoinPoolTest)" * Usefully combined with jsr166.runsPerTest. @@ -171,51 +272,135 @@ public class JSR166TestCase extends Test return (regex == null) ? null : Pattern.compile(regex); } - protected void runTest() throws Throwable { + // Instrumentation to debug very rare, but very annoying hung test runs. + static volatile TestCase currentTestCase; + // static volatile int currentRun = 0; + static { + Runnable checkForWedgedTest = new Runnable() { public void run() { + // Avoid spurious reports with enormous runsPerTest. + // A single test case run should never take more than 1 second. + // But let's cap it at the high end too ... + final int timeoutMinutes = + Math.min(15, Math.max(runsPerTest / 60, 1)); + for (TestCase lastTestCase = currentTestCase;;) { + try { MINUTES.sleep(timeoutMinutes); } + catch (InterruptedException unexpected) { break; } + if (lastTestCase == currentTestCase) { + System.err.printf( + "Looks like we're stuck running test: %s%n", + lastTestCase); +// System.err.printf( +// "Looks like we're stuck running test: %s (%d/%d)%n", +// lastTestCase, currentRun, runsPerTest); +// System.err.println("availableProcessors=" + +// Runtime.getRuntime().availableProcessors()); +// System.err.printf("cpu model = %s%n", cpuModel()); + dumpTestThreads(); + // one stack dump is probably enough; more would be spam + break; + } + lastTestCase = currentTestCase; + }}}; + Thread thread = new Thread(checkForWedgedTest, "checkForWedgedTest"); + thread.setDaemon(true); + thread.start(); + } + +// public static String cpuModel() { +// try { +// java.util.regex.Matcher matcher +// = Pattern.compile("model name\\s*: (.*)") +// .matcher(new String( +// java.nio.file.Files.readAllBytes( +// java.nio.file.Paths.get("/proc/cpuinfo")), "UTF-8")); +// matcher.find(); +// return matcher.group(1); +// } catch (Exception ex) { return null; } +// } + + public void runBare() throws Throwable { + currentTestCase = this; if (methodFilter == null - || methodFilter.matcher(toString()).find()) { - for (int i = 0; i < runsPerTest; i++) { - if (profileTests) - runTestProfiled(); - else - super.runTest(); - } + || methodFilter.matcher(toString()).find()) + super.runBare(); + } + + protected void runTest() throws Throwable { + for (int i = 0; i < runsPerTest; i++) { + // currentRun = i; + if (profileTests) + runTestProfiled(); + else + super.runTest(); } } protected void runTestProfiled() throws Throwable { - // Warmup run, notably to trigger all needed classloading. - super.runTest(); - long t0 = System.nanoTime(); - try { + for (int i = 0; i < 2; i++) { + long startTime = System.nanoTime(); super.runTest(); - } finally { - long elapsedMillis = millisElapsedSince(t0); - if (elapsedMillis >= profileThreshold) + long elapsedMillis = millisElapsedSince(startTime); + if (elapsedMillis < profileThreshold) + break; + // Never report first run of any test; treat it as a + // warmup run, notably to trigger all needed classloading, + if (i > 0) System.out.printf("%n%s: %d%n", toString(), elapsedMillis); } } /** * Runs all JSR166 unit tests using junit.textui.TestRunner. - * Optional command line arg provides the number of iterations to - * repeat running the tests. */ public static void main(String[] args) { + main(suite(), args); + } + + static class PithyResultPrinter extends junit.textui.ResultPrinter { + PithyResultPrinter(java.io.PrintStream writer) { super(writer); } + long runTime; + public void startTest(Test test) {} + protected void printHeader(long runTime) { + this.runTime = runTime; // defer printing for later + } + protected void printFooter(TestResult result) { + if (result.wasSuccessful()) { + getWriter().println("OK (" + result.runCount() + " tests)" + + " Time: " + elapsedTimeAsString(runTime)); + } else { + getWriter().println("Time: " + elapsedTimeAsString(runTime)); + super.printFooter(result); + } + } + } + + /** + * Returns a TestRunner that doesn't bother with unnecessary + * fluff, like printing a "." for each test case. + */ + static junit.textui.TestRunner newPithyTestRunner() { + junit.textui.TestRunner runner = new junit.textui.TestRunner(); + runner.setPrinter(new PithyResultPrinter(System.out)); + return runner; + } + + /** + * Runs all unit tests in the given test suite. + * Actual behavior influenced by jsr166.* system properties. + */ + static void main(Test suite, String[] args) { if (useSecurityManager) { System.err.println("Setting a permissive security manager"); Policy.setPolicy(permissivePolicy()); System.setSecurityManager(new SecurityManager()); } - int iters = (args.length == 0) ? 1 : Integer.parseInt(args[0]); - - Test s = suite(); - for (int i = 0; i < iters; ++i) { - junit.textui.TestRunner.run(s); + for (int i = 0; i < suiteRuns; i++) { + TestResult result = newPithyTestRunner().doRun(suite); + if (!result.wasSuccessful()) + System.exit(1); System.gc(); System.runFinalization(); } - System.exit(0); } public static TestSuite newTestSuite(Object... suiteOrClasses) { @@ -236,11 +421,10 @@ public class JSR166TestCase extends Test for (String testClassName : testClassNames) { try { Class testClass = Class.forName(testClassName); - Method m = testClass.getDeclaredMethod("suite", - new Class[0]); + Method m = testClass.getDeclaredMethod("suite"); suite.addTest(newTestSuite((Test)m.invoke(null))); - } catch (Exception e) { - throw new Error("Missing test class", e); + } catch (ReflectiveOperationException e) { + throw new AssertionError("Missing test class", e); } } } @@ -262,10 +446,11 @@ public class JSR166TestCase extends Test } } - public static boolean atLeastJava6() { return JAVA_CLASS_VERSION >= 50.0; } - public static boolean atLeastJava7() { return JAVA_CLASS_VERSION >= 51.0; } - public static boolean atLeastJava8() { return JAVA_CLASS_VERSION >= 52.0; } - public static boolean atLeastJava9() { return JAVA_CLASS_VERSION >= 53.0; } + public static boolean atLeastJava6() { return JAVA_CLASS_VERSION >= 50.0; } + public static boolean atLeastJava7() { return JAVA_CLASS_VERSION >= 51.0; } + public static boolean atLeastJava8() { return JAVA_CLASS_VERSION >= 52.0; } + public static boolean atLeastJava9() { return JAVA_CLASS_VERSION >= 53.0; } + public static boolean atLeastJava10() { return JAVA_CLASS_VERSION >= 54.0; } /** * Collects all JSR166 unit tests as one suite. @@ -286,6 +471,7 @@ public class JSR166TestCase extends Test AbstractQueuedLongSynchronizerTest.suite(), ArrayBlockingQueueTest.suite(), ArrayDequeTest.suite(), + ArrayListTest.suite(), AtomicBooleanTest.suite(), AtomicIntegerArrayTest.suite(), AtomicIntegerFieldUpdaterTest.suite(), @@ -308,6 +494,7 @@ public class JSR166TestCase extends Test CopyOnWriteArrayListTest.suite(), CopyOnWriteArraySetTest.suite(), CountDownLatchTest.suite(), + CountedCompleterTest.suite(), CyclicBarrierTest.suite(), DelayQueueTest.suite(), EntryTest.suite(), @@ -336,24 +523,31 @@ public class JSR166TestCase extends Test TreeMapTest.suite(), TreeSetTest.suite(), TreeSubMapTest.suite(), - TreeSubSetTest.suite()); + TreeSubSetTest.suite(), + VectorTest.suite()); // Java8+ test classes if (atLeastJava8()) { String[] java8TestClassNames = { + "ArrayDeque8Test", "Atomic8Test", "CompletableFutureTest", "ConcurrentHashMap8Test", - "CountedCompleterTest", + "CountedCompleter8Test", "DoubleAccumulatorTest", "DoubleAdderTest", "ForkJoinPool8Test", "ForkJoinTask8Test", + "HashMapTest", + "LinkedBlockingDeque8Test", + "LinkedBlockingQueue8Test", "LongAccumulatorTest", "LongAdderTest", "SplittableRandomTest", "StampedLockTest", + "SubmissionPublisherTest", "ThreadLocalRandom8Test", + "TimeUnit8Test", }; addNamedTestClasses(suite, java8TestClassNames); } @@ -361,7 +555,15 @@ public class JSR166TestCase extends Test // Java9+ test classes if (atLeastJava9()) { String[] java9TestClassNames = { - "ThreadPoolExecutor9Test", + "AtomicBoolean9Test", + "AtomicInteger9Test", + "AtomicIntegerArray9Test", + "AtomicLong9Test", + "AtomicLongArray9Test", + "AtomicReference9Test", + "AtomicReferenceArray9Test", + "ExecutorCompletionService9Test", + "ForkJoinPool9Test", }; addNamedTestClasses(suite, java9TestClassNames); } @@ -369,6 +571,67 @@ public class JSR166TestCase extends Test return suite; } + /** Returns list of junit-style test method names in given class. */ + public static ArrayList testMethodNames(Class testClass) { + Method[] methods = testClass.getDeclaredMethods(); + ArrayList names = new ArrayList<>(methods.length); + for (Method method : methods) { + if (method.getName().startsWith("test") + && Modifier.isPublic(method.getModifiers()) + // method.getParameterCount() requires jdk8+ + && method.getParameterTypes().length == 0) { + names.add(method.getName()); + } + } + return names; + } + + /** + * Returns junit-style testSuite for the given test class, but + * parameterized by passing extra data to each test. + */ + public static Test parameterizedTestSuite + (Class testClass, + Class dataClass, + ExtraData data) { + try { + TestSuite suite = new TestSuite(); + Constructor c = + testClass.getDeclaredConstructor(dataClass, String.class); + for (String methodName : testMethodNames(testClass)) + suite.addTest((Test) c.newInstance(data, methodName)); + return suite; + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + /** + * Returns junit-style testSuite for the jdk8 extension of the + * given test class, but parameterized by passing extra data to + * each test. Uses reflection to allow compilation in jdk7. + */ + public static Test jdk8ParameterizedTestSuite + (Class testClass, + Class dataClass, + ExtraData data) { + if (atLeastJava8()) { + String name = testClass.getName(); + String name8 = name.replaceAll("Test$", "8Test"); + if (name.equals(name8)) throw new AssertionError(name); + try { + return (Test) + Class.forName(name8) + .getMethod("testSuite", dataClass) + .invoke(null, data); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } else { + return new TestSuite(); + } + } + // Delays for timing-dependent tests, in milliseconds. public static long SHORT_DELAY_MS; @@ -376,12 +639,41 @@ public class JSR166TestCase extends Test public static long MEDIUM_DELAY_MS; public static long LONG_DELAY_MS; + private static final long RANDOM_TIMEOUT; + private static final long RANDOM_EXPIRED_TIMEOUT; + private static final TimeUnit RANDOM_TIMEUNIT; + static { + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + long[] timeouts = { Long.MIN_VALUE, -1, 0, 1, Long.MAX_VALUE }; + RANDOM_TIMEOUT = timeouts[rnd.nextInt(timeouts.length)]; + RANDOM_EXPIRED_TIMEOUT = timeouts[rnd.nextInt(3)]; + TimeUnit[] timeUnits = TimeUnit.values(); + RANDOM_TIMEUNIT = timeUnits[rnd.nextInt(timeUnits.length)]; + } + + /** + * Returns a timeout for use when any value at all will do. + */ + static long randomTimeout() { return RANDOM_TIMEOUT; } + + /** + * Returns a timeout that means "no waiting", i.e. not positive. + */ + static long randomExpiredTimeout() { return RANDOM_EXPIRED_TIMEOUT; } + + /** + * Returns a random non-null TimeUnit. + */ + static TimeUnit randomTimeUnit() { return RANDOM_TIMEUNIT; } + /** - * Returns the shortest timed delay. This could - * be reimplemented to use for example a Property. + * Returns the shortest timed delay. This can be scaled up for + * slow machines using the jsr166.delay.factor system property, + * or via jtreg's -timeoutFactor: flag. + * http://openjdk.java.net/jtreg/command-help.html */ protected long getShortDelay() { - return 50; + return (long) (50 * delayFactor); } /** @@ -394,27 +686,33 @@ public class JSR166TestCase extends Test LONG_DELAY_MS = SHORT_DELAY_MS * 200; } + private static final long TIMEOUT_DELAY_MS + = (long) (12.0 * Math.cbrt(delayFactor)); + /** - * Returns a timeout in milliseconds to be used in tests that - * verify that operations block or time out. + * Returns a timeout in milliseconds to be used in tests that verify + * that operations block or time out. We want this to be longer + * than the OS scheduling quantum, but not too long, so don't scale + * linearly with delayFactor; we use "crazy" cube root instead. */ - long timeoutMillis() { - return SHORT_DELAY_MS / 4; + static long timeoutMillis() { + return TIMEOUT_DELAY_MS; } /** - * Returns a new Date instance representing a time delayMillis - * milliseconds in the future. + * Returns a new Date instance representing a time at least + * delayMillis milliseconds in the future. */ Date delayedDate(long delayMillis) { - return new Date(System.currentTimeMillis() + delayMillis); + // Add 1 because currentTimeMillis is known to round into the past. + return new Date(System.currentTimeMillis() + delayMillis + 1); } /** * The first exception encountered if any threadAssertXXX method fails. */ private final AtomicReference threadFailure - = new AtomicReference(null); + = new AtomicReference<>(null); /** * Records an exception so that it can be rethrown later in the test @@ -423,6 +721,8 @@ public class JSR166TestCase extends Test * the same test have no effect. */ public void threadRecordFailure(Throwable t) { + System.err.println(t); + dumpTestThreads(); threadFailure.compareAndSet(null, t); } @@ -430,6 +730,13 @@ public class JSR166TestCase extends Test setDelays(); } + void tearDownFail(String format, Object... args) { + String msg = toString() + ": " + String.format(format, args); + System.err.println(msg); + dumpTestThreads(); + throw new AssertionError(msg); + } + /** * Extra checks that get done for all test cases. * @@ -448,25 +755,21 @@ public class JSR166TestCase extends Test throw (RuntimeException) t; else if (t instanceof Exception) throw (Exception) t; - else { - AssertionFailedError afe = - new AssertionFailedError(t.toString()); - afe.initCause(t); - throw afe; - } + else + throw new AssertionError(t.toString(), t); } if (Thread.interrupted()) - throw new AssertionFailedError("interrupt status set in main thread"); + tearDownFail("interrupt status set in main thread"); checkForkJoinPoolThreadLeaks(); } /** - * Finds missing try { ... } finally { joinPool(e); } + * Finds missing PoolCleaners */ void checkForkJoinPoolThreadLeaks() throws InterruptedException { - Thread[] survivors = new Thread[5]; + Thread[] survivors = new Thread[7]; int count = Thread.enumerate(survivors); for (int i = 0; i < count; i++) { Thread thread = survivors[i]; @@ -474,94 +777,96 @@ public class JSR166TestCase extends Test if (name.startsWith("ForkJoinPool-")) { // give thread some time to terminate thread.join(LONG_DELAY_MS); - if (!thread.isAlive()) continue; - thread.stop(); - throw new AssertionFailedError - (String.format("Found leaked ForkJoinPool thread test=%s thread=%s%n", - toString(), name)); + if (thread.isAlive()) + tearDownFail("Found leaked ForkJoinPool thread thread=%s", + thread); } } + + if (!ForkJoinPool.commonPool() + .awaitQuiescence(LONG_DELAY_MS, MILLISECONDS)) + tearDownFail("ForkJoin common pool thread stuck"); } /** * Just like fail(reason), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadFail(String reason) { try { fail(reason); - } catch (AssertionFailedError t) { - threadRecordFailure(t); - fail(reason); + } catch (AssertionError fail) { + threadRecordFailure(fail); + throw fail; } } /** * Just like assertTrue(b), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadAssertTrue(boolean b) { try { assertTrue(b); - } catch (AssertionFailedError t) { - threadRecordFailure(t); - throw t; + } catch (AssertionError fail) { + threadRecordFailure(fail); + throw fail; } } /** * Just like assertFalse(b), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadAssertFalse(boolean b) { try { assertFalse(b); - } catch (AssertionFailedError t) { - threadRecordFailure(t); - throw t; + } catch (AssertionError fail) { + threadRecordFailure(fail); + throw fail; } } /** * Just like assertNull(x), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadAssertNull(Object x) { try { assertNull(x); - } catch (AssertionFailedError t) { - threadRecordFailure(t); - throw t; + } catch (AssertionError fail) { + threadRecordFailure(fail); + throw fail; } } /** * Just like assertEquals(x, y), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadAssertEquals(long x, long y) { try { assertEquals(x, y); - } catch (AssertionFailedError t) { - threadRecordFailure(t); - throw t; + } catch (AssertionError fail) { + threadRecordFailure(fail); + throw fail; } } /** * Just like assertEquals(x, y), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadAssertEquals(Object x, Object y) { try { assertEquals(x, y); - } catch (AssertionFailedError fail) { + } catch (AssertionError fail) { threadRecordFailure(fail); throw fail; } catch (Throwable fail) { @@ -571,13 +876,13 @@ public class JSR166TestCase extends Test /** * Just like assertSame(x, y), but additionally recording (using - * threadRecordFailure) any AssertionFailedError thrown, so that - * the current testcase will fail. + * threadRecordFailure) any AssertionError thrown, so that the + * current testcase will fail. */ public void threadAssertSame(Object x, Object y) { try { assertSame(x, y); - } catch (AssertionFailedError fail) { + } catch (AssertionError fail) { threadRecordFailure(fail); throw fail; } @@ -599,8 +904,8 @@ public class JSR166TestCase extends Test /** * Records the given exception using {@link #threadRecordFailure}, - * then rethrows the exception, wrapping it in an - * AssertionFailedError if necessary. + * then rethrows the exception, wrapping it in an AssertionError + * if necessary. */ public void threadUnexpectedException(Throwable t) { threadRecordFailure(t); @@ -609,102 +914,192 @@ public class JSR166TestCase extends Test throw (RuntimeException) t; else if (t instanceof Error) throw (Error) t; - else { - AssertionFailedError afe = - new AssertionFailedError("unexpected exception: " + t); - afe.initCause(t); - throw afe; - } + else + throw new AssertionError("unexpected exception: " + t, t); } /** * Delays, via Thread.sleep, for the given millisecond delay, but * if the sleep is shorter than specified, may re-sleep or yield - * until time elapses. + * until time elapses. Ensures that the given time, as measured + * by System.nanoTime(), has elapsed. */ static void delay(long millis) throws InterruptedException { - long startTime = System.nanoTime(); - long ns = millis * 1000 * 1000; - for (;;) { + long nanos = millis * (1000 * 1000); + final long wakeupTime = System.nanoTime() + nanos; + do { if (millis > 0L) Thread.sleep(millis); else // too short to sleep Thread.yield(); - long d = ns - (System.nanoTime() - startTime); - if (d > 0L) - millis = d / (1000 * 1000); - else - break; + nanos = wakeupTime - System.nanoTime(); + millis = nanos / (1000 * 1000); + } while (nanos >= 0L); + } + + /** + * Allows use of try-with-resources with per-test thread pools. + */ + class PoolCleaner implements AutoCloseable { + private final ExecutorService pool; + public PoolCleaner(ExecutorService pool) { this.pool = pool; } + public void close() { joinPool(pool); } + } + + /** + * An extension of PoolCleaner that has an action to release the pool. + */ + class PoolCleanerWithReleaser extends PoolCleaner { + private final Runnable releaser; + public PoolCleanerWithReleaser(ExecutorService pool, Runnable releaser) { + super(pool); + this.releaser = releaser; } + public void close() { + try { + releaser.run(); + } finally { + super.close(); + } + } + } + + PoolCleaner cleaner(ExecutorService pool) { + return new PoolCleaner(pool); + } + + PoolCleaner cleaner(ExecutorService pool, Runnable releaser) { + return new PoolCleanerWithReleaser(pool, releaser); + } + + PoolCleaner cleaner(ExecutorService pool, CountDownLatch latch) { + return new PoolCleanerWithReleaser(pool, releaser(latch)); + } + + Runnable releaser(final CountDownLatch latch) { + return new Runnable() { public void run() { + do { latch.countDown(); } + while (latch.getCount() > 0); + }}; + } + + PoolCleaner cleaner(ExecutorService pool, AtomicBoolean flag) { + return new PoolCleanerWithReleaser(pool, releaser(flag)); + } + + Runnable releaser(final AtomicBoolean flag) { + return new Runnable() { public void run() { flag.set(true); }}; } /** * Waits out termination of a thread pool or fails doing so. */ - void joinPool(ExecutorService exec) { + void joinPool(ExecutorService pool) { try { - exec.shutdown(); - if (!exec.awaitTermination(2 * LONG_DELAY_MS, MILLISECONDS)) - fail("ExecutorService " + exec + - " did not terminate in a timely manner"); + pool.shutdown(); + if (!pool.awaitTermination(2 * LONG_DELAY_MS, MILLISECONDS)) { + try { + threadFail("ExecutorService " + pool + + " did not terminate in a timely manner"); + } finally { + // last resort, for the benefit of subsequent tests + pool.shutdownNow(); + pool.awaitTermination(MEDIUM_DELAY_MS, MILLISECONDS); + } + } } catch (SecurityException ok) { // Allowed in case test doesn't have privs } catch (InterruptedException fail) { - fail("Unexpected InterruptedException"); + threadFail("Unexpected InterruptedException"); } } /** - * A debugging tool to print all stack traces, as jstack does. + * Like Runnable, but with the freedom to throw anything. + * junit folks had the same idea: + * http://junit.org/junit5/docs/snapshot/api/org/junit/gen5/api/Executable.html */ - static void printAllStackTraces() { - for (ThreadInfo info : - ManagementFactory.getThreadMXBean() - .dumpAllThreads(true, true)) - System.err.print(info); - } + interface Action { public void run() throws Throwable; } /** - * Checks that thread does not terminate within the default - * millisecond delay of {@code timeoutMillis()}. + * Runs all the given actions in parallel, failing if any fail. + * Useful for running multiple variants of tests that are + * necessarily individually slow because they must block. */ - void assertThreadStaysAlive(Thread thread) { - assertThreadStaysAlive(thread, timeoutMillis()); + void testInParallel(Action ... actions) { + ExecutorService pool = Executors.newCachedThreadPool(); + try (PoolCleaner cleaner = cleaner(pool)) { + ArrayList> futures = new ArrayList<>(actions.length); + for (final Action action : actions) + futures.add(pool.submit(new CheckedRunnable() { + public void realRun() throws Throwable { action.run();}})); + for (Future future : futures) + try { + assertNull(future.get(LONG_DELAY_MS, MILLISECONDS)); + } catch (ExecutionException ex) { + threadUnexpectedException(ex.getCause()); + } catch (Exception ex) { + threadUnexpectedException(ex); + } + } } /** - * Checks that thread does not terminate within the given millisecond delay. + * A debugging tool to print stack traces of most threads, as jstack does. + * Uninteresting threads are filtered out. */ - void assertThreadStaysAlive(Thread thread, long millis) { - try { - // No need to optimize the failing case via Thread.join. - delay(millis); - assertTrue(thread.isAlive()); - } catch (InterruptedException fail) { - fail("Unexpected InterruptedException"); + static void dumpTestThreads() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + System.setSecurityManager(null); + } catch (SecurityException giveUp) { + return; + } } - } - /** - * Checks that the threads do not terminate within the default - * millisecond delay of {@code timeoutMillis()}. - */ - void assertThreadsStayAlive(Thread... threads) { - assertThreadsStayAlive(timeoutMillis(), threads); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + System.err.println("------ stacktrace dump start ------"); + for (ThreadInfo info : threadMXBean.dumpAllThreads(true, true)) { + final String name = info.getThreadName(); + String lockName; + if ("Signal Dispatcher".equals(name)) + continue; + if ("Reference Handler".equals(name) + && (lockName = info.getLockName()) != null + && lockName.startsWith("java.lang.ref.Reference$Lock")) + continue; + if ("Finalizer".equals(name) + && (lockName = info.getLockName()) != null + && lockName.startsWith("java.lang.ref.ReferenceQueue$Lock")) + continue; + if ("checkForWedgedTest".equals(name)) + continue; + System.err.print(info); + } + System.err.println("------ stacktrace dump end ------"); + + if (sm != null) System.setSecurityManager(sm); } /** - * Checks that the threads do not terminate within the given millisecond delay. + * Checks that thread eventually enters the expected blocked thread state. */ - void assertThreadsStayAlive(long millis, Thread... threads) { - try { - // No need to optimize the failing case via Thread.join. - delay(millis); - for (Thread thread : threads) - assertTrue(thread.isAlive()); - } catch (InterruptedException fail) { - fail("Unexpected InterruptedException"); + void assertThreadBlocks(Thread thread, Thread.State expected) { + // always sleep at least 1 ms, with high probability avoiding + // transitory states + for (long retries = LONG_DELAY_MS * 3 / 4; retries-->0; ) { + try { delay(1); } + catch (InterruptedException fail) { + throw new AssertionError("Unexpected InterruptedException", fail); + } + Thread.State s = thread.getState(); + if (s == expected) + return; + else if (s == Thread.State.TERMINATED) + fail("Unexpected thread termination"); } + fail("timed out waiting for thread to enter thread state " + expected); } /** @@ -745,6 +1140,12 @@ public class JSR166TestCase extends Test } /** + * The maximum number of consecutive spurious wakeups we should + * tolerate (from APIs like LockSupport.park) before failing a test. + */ + static final int MAX_SPURIOUS_WAKEUPS = 10; + + /** * The number of elements to place in collections, arrays, etc. */ public static final int SIZE = 20; @@ -848,7 +1249,7 @@ public class JSR166TestCase extends Test } public void refresh() {} public String toString() { - List ps = new ArrayList(); + List ps = new ArrayList<>(); for (Enumeration e = perms.elements(); e.hasMoreElements();) ps.add(e.nextElement()); return "AdjustablePolicy with permissions " + ps; @@ -876,16 +1277,13 @@ public class JSR166TestCase extends Test /** * Sleeps until the given time has elapsed. - * Throws AssertionFailedError if interrupted. + * Throws AssertionError if interrupted. */ - void sleep(long millis) { + static void sleep(long millis) { try { delay(millis); } catch (InterruptedException fail) { - AssertionFailedError afe = - new AssertionFailedError("Unexpected InterruptedException"); - afe.initCause(fail); - throw afe; + throw new AssertionError("Unexpected InterruptedException", fail); } } @@ -894,7 +1292,7 @@ public class JSR166TestCase extends Test * thread to enter a wait state: BLOCKED, WAITING, or TIMED_WAITING. */ void waitForThreadToEnterWaitState(Thread thread, long timeoutMillis) { - long startTime = System.nanoTime(); + long startTime = 0L; for (;;) { Thread.State s = thread.getState(); if (s == Thread.State.BLOCKED || @@ -903,23 +1301,65 @@ public class JSR166TestCase extends Test return; else if (s == Thread.State.TERMINATED) fail("Unexpected thread termination"); + else if (startTime == 0L) + startTime = System.nanoTime(); else if (millisElapsedSince(startTime) > timeoutMillis) { threadAssertTrue(thread.isAlive()); - return; + fail("timed out waiting for thread to enter wait state"); } Thread.yield(); } } /** - * Waits up to LONG_DELAY_MS for the given thread to enter a wait - * state: BLOCKED, WAITING, or TIMED_WAITING. + * Spin-waits up to the specified number of milliseconds for the given + * thread to enter a wait state: BLOCKED, WAITING, or TIMED_WAITING, + * and additionally satisfy the given condition. + */ + void waitForThreadToEnterWaitState( + Thread thread, long timeoutMillis, Callable waitingForGodot) { + long startTime = 0L; + for (;;) { + Thread.State s = thread.getState(); + if (s == Thread.State.BLOCKED || + s == Thread.State.WAITING || + s == Thread.State.TIMED_WAITING) { + try { + if (waitingForGodot.call()) + return; + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + else if (s == Thread.State.TERMINATED) + fail("Unexpected thread termination"); + else if (startTime == 0L) + startTime = System.nanoTime(); + else if (millisElapsedSince(startTime) > timeoutMillis) { + threadAssertTrue(thread.isAlive()); + fail("timed out waiting for thread to enter wait state"); + } + Thread.yield(); + } + } + + /** + * Spin-waits up to LONG_DELAY_MS milliseconds for the given thread to + * enter a wait state: BLOCKED, WAITING, or TIMED_WAITING. */ void waitForThreadToEnterWaitState(Thread thread) { waitForThreadToEnterWaitState(thread, LONG_DELAY_MS); } /** + * Spin-waits up to LONG_DELAY_MS milliseconds for the given thread to + * enter a wait state: BLOCKED, WAITING, or TIMED_WAITING, + * and additionally satisfy the given condition. + */ + void waitForThreadToEnterWaitState( + Thread thread, Callable waitingForGodot) { + waitForThreadToEnterWaitState(thread, LONG_DELAY_MS, waitingForGodot); + } + + /** * Returns the number of milliseconds since time given by * startNanoTime, which must have been previously returned from a * call to {@link System#nanoTime()}. @@ -934,7 +1374,7 @@ public class JSR166TestCase extends Test // r.run(); // } catch (Throwable fail) { threadUnexpectedException(fail); } // if (millisElapsedSince(startTime) > timeoutMillis/2) -// throw new AssertionFailedError("did not return promptly"); +// throw new AssertionError("did not return promptly"); // } // void assertTerminatesPromptly(Runnable r) { @@ -951,7 +1391,7 @@ public class JSR166TestCase extends Test assertEquals(expectedValue, f.get(timeoutMillis, MILLISECONDS)); } catch (Throwable fail) { threadUnexpectedException(fail); } if (millisElapsedSince(startTime) > timeoutMillis/2) - throw new AssertionFailedError("timed get did not return promptly"); + throw new AssertionError("timed get did not return promptly"); } void checkTimedGet(Future f, T expectedValue) { @@ -981,7 +1421,7 @@ public class JSR166TestCase extends Test } finally { if (t.getState() != Thread.State.TERMINATED) { t.interrupt(); - fail("Test timed out"); + threadFail("timed out waiting for thread to terminate"); } } } @@ -1009,26 +1449,6 @@ public class JSR166TestCase extends Test } } - public abstract class RunnableShouldThrow implements Runnable { - protected abstract void realRun() throws Throwable; - - final Class exceptionClass; - - RunnableShouldThrow(Class exceptionClass) { - this.exceptionClass = exceptionClass; - } - - public final void run() { - try { - realRun(); - threadShouldThrow(exceptionClass.getSimpleName()); - } catch (Throwable t) { - if (! exceptionClass.isInstance(t)) - threadUnexpectedException(t); - } - } - } - public abstract class ThreadShouldThrow extends Thread { protected abstract void realRun() throws Throwable; @@ -1106,7 +1526,10 @@ public class JSR166TestCase extends Test public static final String TEST_STRING = "a test string"; public static class StringTask implements Callable { - public String call() { return TEST_STRING; } + final String value; + public StringTask() { this(TEST_STRING); } + public StringTask(String value) { this.value = value; } + public String call() { return value; } } public Callable latchAwaitingStringTask(final CountDownLatch latch) { @@ -1119,24 +1542,58 @@ public class JSR166TestCase extends Test }}; } - public Runnable awaiter(final CountDownLatch latch) { + public Runnable countDowner(final CountDownLatch latch) { return new CheckedRunnable() { public void realRun() throws InterruptedException { - await(latch); + latch.countDown(); }}; } - public void await(CountDownLatch latch) { + class LatchAwaiter extends CheckedRunnable { + static final int NEW = 0; + static final int RUNNING = 1; + static final int DONE = 2; + final CountDownLatch latch; + int state = NEW; + LatchAwaiter(CountDownLatch latch) { this.latch = latch; } + public void realRun() throws InterruptedException { + state = 1; + await(latch); + state = 2; + } + } + + public LatchAwaiter awaiter(CountDownLatch latch) { + return new LatchAwaiter(latch); + } + + public void await(CountDownLatch latch, long timeoutMillis) { try { - assertTrue(latch.await(LONG_DELAY_MS, MILLISECONDS)); + if (!latch.await(timeoutMillis, MILLISECONDS)) + fail("timed out waiting for CountDownLatch for " + + (timeoutMillis/1000) + " sec"); } catch (Throwable fail) { threadUnexpectedException(fail); } } + public void await(CountDownLatch latch) { + await(latch, LONG_DELAY_MS); + } + public void await(Semaphore semaphore) { try { - assertTrue(semaphore.tryAcquire(LONG_DELAY_MS, MILLISECONDS)); + if (!semaphore.tryAcquire(LONG_DELAY_MS, MILLISECONDS)) + fail("timed out waiting for Semaphore for " + + (LONG_DELAY_MS/1000) + " sec"); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + + public void await(CyclicBarrier barrier) { + try { + barrier.await(LONG_DELAY_MS, MILLISECONDS); } catch (Throwable fail) { threadUnexpectedException(fail); } @@ -1156,7 +1613,7 @@ public class JSR166TestCase extends Test // long startTime = System.nanoTime(); // while (!flag.get()) { // if (millisElapsedSince(startTime) > timeoutMillis) -// throw new AssertionFailedError("timed out"); +// throw new AssertionError("timed out"); // Thread.yield(); // } // } @@ -1165,28 +1622,6 @@ public class JSR166TestCase extends Test public String call() { throw new NullPointerException(); } } - public static class CallableOne implements Callable { - public Integer call() { return one; } - } - - public class ShortRunnable extends CheckedRunnable { - protected void realRun() throws Throwable { - delay(SHORT_DELAY_MS); - } - } - - public class ShortInterruptedRunnable extends CheckedInterruptedRunnable { - protected void realRun() throws InterruptedException { - delay(SHORT_DELAY_MS); - } - } - - public class SmallRunnable extends CheckedRunnable { - protected void realRun() throws Throwable { - delay(SMALL_DELAY_MS); - } - } - public class SmallPossiblyInterruptedRunnable extends CheckedRunnable { protected void realRun() { try { @@ -1195,25 +1630,6 @@ public class JSR166TestCase extends Test } } - public class SmallCallable extends CheckedCallable { - protected Object realCall() throws InterruptedException { - delay(SMALL_DELAY_MS); - return Boolean.TRUE; - } - } - - public class MediumRunnable extends CheckedRunnable { - protected void realRun() throws Throwable { - delay(MEDIUM_DELAY_MS); - } - } - - public class MediumInterruptedRunnable extends CheckedInterruptedRunnable { - protected void realRun() throws InterruptedException { - delay(MEDIUM_DELAY_MS); - } - } - public Runnable possiblyInterruptedRunnable(final long timeoutMillis) { return new CheckedRunnable() { protected void realRun() { @@ -1223,22 +1639,6 @@ public class JSR166TestCase extends Test }}; } - public class MediumPossiblyInterruptedRunnable extends CheckedRunnable { - protected void realRun() { - try { - delay(MEDIUM_DELAY_MS); - } catch (InterruptedException ok) {} - } - } - - public class LongPossiblyInterruptedRunnable extends CheckedRunnable { - protected void realRun() { - try { - delay(LONG_DELAY_MS); - } catch (InterruptedException ok) {} - } - } - /** * For use as ThreadFactory in constructors */ @@ -1252,59 +1652,6 @@ public class JSR166TestCase extends Test boolean isDone(); } - public static TrackedRunnable trackedRunnable(final long timeoutMillis) { - return new TrackedRunnable() { - private volatile boolean done = false; - public boolean isDone() { return done; } - public void run() { - try { - delay(timeoutMillis); - done = true; - } catch (InterruptedException ok) {} - } - }; - } - - public static class TrackedShortRunnable implements Runnable { - public volatile boolean done = false; - public void run() { - try { - delay(SHORT_DELAY_MS); - done = true; - } catch (InterruptedException ok) {} - } - } - - public static class TrackedSmallRunnable implements Runnable { - public volatile boolean done = false; - public void run() { - try { - delay(SMALL_DELAY_MS); - done = true; - } catch (InterruptedException ok) {} - } - } - - public static class TrackedMediumRunnable implements Runnable { - public volatile boolean done = false; - public void run() { - try { - delay(MEDIUM_DELAY_MS); - done = true; - } catch (InterruptedException ok) {} - } - } - - public static class TrackedLongRunnable implements Runnable { - public volatile boolean done = false; - public void run() { - try { - delay(LONG_DELAY_MS); - done = true; - } catch (InterruptedException ok) {} - } - } - public static class TrackedNoOpRunnable implements Runnable { public volatile boolean done = false; public void run() { @@ -1312,17 +1659,6 @@ public class JSR166TestCase extends Test } } - public static class TrackedCallable implements Callable { - public volatile boolean done = false; - public Object call() { - try { - delay(SMALL_DELAY_MS); - done = true; - } catch (InterruptedException ok) {} - return Boolean.TRUE; - } - } - /** * Analog of CheckedRunnable for RecursiveAction */ @@ -1364,21 +1700,18 @@ public class JSR166TestCase extends Test /** * A CyclicBarrier that uses timed await and fails with - * AssertionFailedErrors instead of throwing checked exceptions. + * AssertionErrors instead of throwing checked exceptions. */ - public class CheckedBarrier extends CyclicBarrier { + public static class CheckedBarrier extends CyclicBarrier { public CheckedBarrier(int parties) { super(parties); } public int await() { try { return super.await(2 * LONG_DELAY_MS, MILLISECONDS); } catch (TimeoutException timedOut) { - throw new AssertionFailedError("timed out"); + throw new AssertionError("timed out"); } catch (Exception fail) { - AssertionFailedError afe = - new AssertionFailedError("Unexpected exception: " + fail); - afe.initCause(fail); - throw afe; + throw new AssertionError("Unexpected exception: " + fail, fail); } } } @@ -1389,7 +1722,7 @@ public class JSR166TestCase extends Test assertEquals(0, q.size()); assertNull(q.peek()); assertNull(q.poll()); - assertNull(q.poll(0, MILLISECONDS)); + assertNull(q.poll(randomExpiredTimeout(), randomTimeUnit())); assertEquals(q.toString(), "[]"); assertTrue(Arrays.equals(q.toArray(), new Object[0])); assertFalse(q.iterator().hasNext()); @@ -1430,12 +1763,22 @@ public class JSR166TestCase extends Test } } + void assertImmutable(final Object o) { + if (o instanceof Collection) { + assertThrows( + UnsupportedOperationException.class, + new Runnable() { public void run() { + ((Collection) o).add(null);}}); + } + } + @SuppressWarnings("unchecked") T serialClone(T o) { try { ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream(serialBytes(o))); T clone = (T) ois.readObject(); + if (o == clone) assertImmutable(o); assertSame(o.getClass(), clone.getClass()); return clone; } catch (Throwable fail) { @@ -1444,6 +1787,46 @@ public class JSR166TestCase extends Test } } + /** + * A version of serialClone that leaves error handling (for + * e.g. NotSerializableException) up to the caller. + */ + @SuppressWarnings("unchecked") + T serialClonePossiblyFailing(T o) + throws ReflectiveOperationException, java.io.IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(o); + oos.flush(); + oos.close(); + ObjectInputStream ois = new ObjectInputStream + (new ByteArrayInputStream(bos.toByteArray())); + T clone = (T) ois.readObject(); + if (o == clone) assertImmutable(o); + assertSame(o.getClass(), clone.getClass()); + return clone; + } + + /** + * If o implements Cloneable and has a public clone method, + * returns a clone of o, else null. + */ + @SuppressWarnings("unchecked") + T cloneableClone(T o) { + if (!(o instanceof Cloneable)) return null; + final T clone; + try { + clone = (T) o.getClass().getMethod("clone").invoke(o); + } catch (NoSuchMethodException ok) { + return null; + } catch (ReflectiveOperationException unexpected) { + throw new Error(unexpected); + } + assertNotSame(o, clone); // not 100% guaranteed by spec + assertSame(o.getClass(), clone.getClass()); + return clone; + } + public void assertThrows(Class expectedExceptionClass, Runnable... throwingActions) { for (Runnable throwingAction : throwingActions) { @@ -1451,14 +1834,11 @@ public class JSR166TestCase extends Test try { throwingAction.run(); } catch (Throwable t) { threw = true; - if (!expectedExceptionClass.isInstance(t)) { - AssertionFailedError afe = - new AssertionFailedError - ("Expected " + expectedExceptionClass.getName() + - ", got " + t.getClass().getName()); - afe.initCause(t); - threadUnexpectedException(afe); - } + if (!expectedExceptionClass.isInstance(t)) + throw new AssertionError( + "Expected " + expectedExceptionClass.getName() + + ", got " + t.getClass().getName(), + t); } if (!threw) shouldThrow(expectedExceptionClass.getName()); @@ -1472,4 +1852,244 @@ public class JSR166TestCase extends Test } catch (NoSuchElementException success) {} assertFalse(it.hasNext()); } + + public Callable callableThrowing(final Exception ex) { + return new Callable() { public T call() throws Exception { throw ex; }}; + } + + public Runnable runnableThrowing(final RuntimeException ex) { + return new Runnable() { public void run() { throw ex; }}; + } + + /** A reusable thread pool to be shared by tests. */ + static final ExecutorService cachedThreadPool = + new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 1000L, MILLISECONDS, + new SynchronousQueue()); + + static void shuffle(T[] array) { + Collections.shuffle(Arrays.asList(array), ThreadLocalRandom.current()); + } + + /** + * Returns the same String as would be returned by {@link + * Object#toString}, whether or not the given object's class + * overrides toString(). + * + * @see System#identityHashCode + */ + static String identityString(Object x) { + return x.getClass().getName() + + "@" + Integer.toHexString(System.identityHashCode(x)); + } + + // --- Shared assertions for Executor tests --- + + /** + * Returns maximum number of tasks that can be submitted to given + * pool (with bounded queue) before saturation (when submission + * throws RejectedExecutionException). + */ + static final int saturatedSize(ThreadPoolExecutor pool) { + BlockingQueue q = pool.getQueue(); + return pool.getMaximumPoolSize() + q.size() + q.remainingCapacity(); + } + + @SuppressWarnings("FutureReturnValueIgnored") + void assertNullTaskSubmissionThrowsNullPointerException(Executor e) { + try { + e.execute((Runnable) null); + shouldThrow(); + } catch (NullPointerException success) {} + + if (! (e instanceof ExecutorService)) return; + ExecutorService es = (ExecutorService) e; + try { + es.submit((Runnable) null); + shouldThrow(); + } catch (NullPointerException success) {} + try { + es.submit((Runnable) null, Boolean.TRUE); + shouldThrow(); + } catch (NullPointerException success) {} + try { + es.submit((Callable) null); + shouldThrow(); + } catch (NullPointerException success) {} + + if (! (e instanceof ScheduledExecutorService)) return; + ScheduledExecutorService ses = (ScheduledExecutorService) e; + try { + ses.schedule((Runnable) null, + randomTimeout(), randomTimeUnit()); + shouldThrow(); + } catch (NullPointerException success) {} + try { + ses.schedule((Callable) null, + randomTimeout(), randomTimeUnit()); + shouldThrow(); + } catch (NullPointerException success) {} + try { + ses.scheduleAtFixedRate((Runnable) null, + randomTimeout(), LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + try { + ses.scheduleWithFixedDelay((Runnable) null, + randomTimeout(), LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + + void setRejectedExecutionHandler( + ThreadPoolExecutor p, RejectedExecutionHandler handler) { + p.setRejectedExecutionHandler(handler); + assertSame(handler, p.getRejectedExecutionHandler()); + } + + void assertTaskSubmissionsAreRejected(ThreadPoolExecutor p) { + final RejectedExecutionHandler savedHandler = p.getRejectedExecutionHandler(); + final long savedTaskCount = p.getTaskCount(); + final long savedCompletedTaskCount = p.getCompletedTaskCount(); + final int savedQueueSize = p.getQueue().size(); + final boolean stock = (p.getClass().getClassLoader() == null); + + Runnable r = () -> {}; + Callable c = () -> Boolean.TRUE; + + class Recorder implements RejectedExecutionHandler { + public volatile Runnable r = null; + public volatile ThreadPoolExecutor p = null; + public void reset() { r = null; p = null; } + public void rejectedExecution(Runnable r, ThreadPoolExecutor p) { + assertNull(this.r); + assertNull(this.p); + this.r = r; + this.p = p; + } + } + + // check custom handler is invoked exactly once per task + Recorder recorder = new Recorder(); + setRejectedExecutionHandler(p, recorder); + for (int i = 2; i--> 0; ) { + recorder.reset(); + p.execute(r); + if (stock && p.getClass() == ThreadPoolExecutor.class) + assertSame(r, recorder.r); + assertSame(p, recorder.p); + + recorder.reset(); + assertFalse(p.submit(r).isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + + recorder.reset(); + assertFalse(p.submit(r, Boolean.TRUE).isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + + recorder.reset(); + assertFalse(p.submit(c).isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + + if (p instanceof ScheduledExecutorService) { + ScheduledExecutorService s = (ScheduledExecutorService) p; + ScheduledFuture future; + + recorder.reset(); + future = s.schedule(r, randomTimeout(), randomTimeUnit()); + assertFalse(future.isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + + recorder.reset(); + future = s.schedule(c, randomTimeout(), randomTimeUnit()); + assertFalse(future.isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + + recorder.reset(); + future = s.scheduleAtFixedRate(r, randomTimeout(), LONG_DELAY_MS, MILLISECONDS); + assertFalse(future.isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + + recorder.reset(); + future = s.scheduleWithFixedDelay(r, randomTimeout(), LONG_DELAY_MS, MILLISECONDS); + assertFalse(future.isDone()); + if (stock) assertTrue(!((FutureTask) recorder.r).isDone()); + assertSame(p, recorder.p); + } + } + + // Checking our custom handler above should be sufficient, but + // we add some integration tests of standard handlers. + final AtomicReference thread = new AtomicReference<>(); + final Runnable setThread = () -> thread.set(Thread.currentThread()); + + setRejectedExecutionHandler(p, new ThreadPoolExecutor.AbortPolicy()); + try { + p.execute(setThread); + shouldThrow(); + } catch (RejectedExecutionException success) {} + assertNull(thread.get()); + + setRejectedExecutionHandler(p, new ThreadPoolExecutor.DiscardPolicy()); + p.execute(setThread); + assertNull(thread.get()); + + setRejectedExecutionHandler(p, new ThreadPoolExecutor.CallerRunsPolicy()); + p.execute(setThread); + if (p.isShutdown()) + assertNull(thread.get()); + else + assertSame(Thread.currentThread(), thread.get()); + + setRejectedExecutionHandler(p, savedHandler); + + // check that pool was not perturbed by handlers + assertEquals(savedTaskCount, p.getTaskCount()); + assertEquals(savedCompletedTaskCount, p.getCompletedTaskCount()); + assertEquals(savedQueueSize, p.getQueue().size()); + } + + void assertCollectionsEquals(Collection x, Collection y) { + assertEquals(x, y); + assertEquals(y, x); + assertEquals(x.isEmpty(), y.isEmpty()); + assertEquals(x.size(), y.size()); + if (x instanceof List) { + assertEquals(x.toString(), y.toString()); + } + if (x instanceof List || x instanceof Set) { + assertEquals(x.hashCode(), y.hashCode()); + } + if (x instanceof List || x instanceof Deque) { + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + assertTrue(Arrays.equals(x.toArray(new Object[0]), + y.toArray(new Object[0]))); + } + } + + /** + * A weaker form of assertCollectionsEquals which does not insist + * that the two collections satisfy Object#equals(Object), since + * they may use identity semantics as Deques do. + */ + void assertCollectionsEquivalent(Collection x, Collection y) { + if (x instanceof List || x instanceof Set) + assertCollectionsEquals(x, y); + else { + assertEquals(x.isEmpty(), y.isEmpty()); + assertEquals(x.size(), y.size()); + assertEquals(new HashSet(x), new HashSet(y)); + if (x instanceof Deque) { + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + assertTrue(Arrays.equals(x.toArray(new Object[0]), + y.toArray(new Object[0]))); + } + } + } }