--- jsr166/src/test/tck/JSR166TestCase.java 2016/11/07 01:59:17 1.210 +++ jsr166/src/test/tck/JSR166TestCase.java 2017/08/16 17:18:34 1.236 @@ -1,6 +1,7 @@ /* - * 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. @@ -8,12 +9,35 @@ /* * @test - * @summary JSR-166 tck tests - * @modules java.management + * @summary JSR-166 tck tests, in a number of variations. + * The first is the conformance testing variant, + * while others also test implementation details. * @build * - * @run junit/othervm/timeout=1000 -Djsr166.testImplementationDetails=true JSR166TestCase - * @run junit/othervm/timeout=1000 -Djava.util.concurrent.ForkJoinPool.common.parallelism=0 -Djsr166.testImplementationDetails=true JSR166TestCase - * @run junit/othervm/timeout=1000 -Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -Djava.util.secureRandomSeed=true JSR166TestCase + * @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; @@ -30,8 +54,6 @@ import java.lang.management.ThreadMXBean import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.nio.file.Files; -import java.nio.file.Paths; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; @@ -54,22 +76,27 @@ 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.Matcher; import java.util.regex.Pattern; import junit.framework.AssertionFailedError; @@ -279,9 +306,11 @@ public class JSR166TestCase extends Test // public static String cpuModel() { // try { -// Matcher matcher = Pattern.compile("model name\\s*: (.*)") +// java.util.regex.Matcher matcher +// = Pattern.compile("model name\\s*: (.*)") // .matcher(new String( -// Files.readAllBytes(Paths.get("/proc/cpuinfo")), "UTF-8")); +// 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; } @@ -515,6 +544,8 @@ public class JSR166TestCase extends Test "DoubleAdderTest", "ForkJoinPool8Test", "ForkJoinTask8Test", + "LinkedBlockingDeque8Test", + "LinkedBlockingQueue8Test", "LongAccumulatorTest", "LongAdderTest", "SplittableRandomTest", @@ -537,6 +568,7 @@ public class JSR166TestCase extends Test "AtomicReference9Test", "AtomicReferenceArray9Test", "ExecutorCompletionService9Test", + "ForkJoinPool9Test", }; addNamedTestClasses(suite, java9TestClassNames); } @@ -547,7 +579,7 @@ public class JSR166TestCase extends Test /** 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); + ArrayList names = new ArrayList<>(methods.length); for (Method method : methods) { if (method.getName().startsWith("test") && Modifier.isPublic(method.getModifiers()) @@ -612,6 +644,33 @@ 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 can be scaled up for * slow machines using the jsr166.delay.factor system property, @@ -632,12 +691,17 @@ 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; } /** @@ -653,7 +717,7 @@ public class JSR166TestCase extends Test * 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 @@ -1032,46 +1096,23 @@ public class JSR166TestCase extends Test } /** - * Checks that thread does not terminate within the default - * millisecond delay of {@code timeoutMillis()}. - */ - void assertThreadStaysAlive(Thread thread) { - assertThreadStaysAlive(thread, timeoutMillis()); - } - - /** - * Checks that thread does not terminate within the given millisecond delay. - */ - 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) { - threadFail("Unexpected InterruptedException"); - } - } - - /** - * Checks that the threads do not terminate within the default - * millisecond delay of {@code timeoutMillis()}. - */ - void assertThreadsStayAlive(Thread... threads) { - assertThreadsStayAlive(timeoutMillis(), threads); - } - - /** - * 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) { - threadFail("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) { + fail("Unexpected InterruptedException"); + } + 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); } /** @@ -1112,6 +1153,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; @@ -1215,7 +1262,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; @@ -1274,21 +1321,61 @@ public class JSR166TestCase extends Test 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()}. @@ -1540,6 +1627,14 @@ public class JSR166TestCase extends Test } } + public void await(CyclicBarrier barrier) { + try { + barrier.await(LONG_DELAY_MS, MILLISECONDS); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + // /** // * Spin-waits up to LONG_DELAY_MS until flag becomes true. // */ @@ -1563,28 +1658,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 { @@ -1593,25 +1666,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() { @@ -1621,22 +1675,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 */ @@ -1650,59 +1688,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() { @@ -1710,17 +1695,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 */ @@ -1787,7 +1761,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()); @@ -1853,6 +1827,26 @@ 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. */ @@ -1918,4 +1912,188 @@ public class JSR166TestCase extends Test 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()); + } }