1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3  *
4  * This code is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 only, as
6  * published by the Free Software Foundation.
7  *
8  * This code is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * version 2 for more details (a copy is included in the LICENSE file that
12  * accompanied this code).
13  *
14  * You should have received a copy of the GNU General Public License version
15  * 2 along with this work; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
19  * or visit www.oracle.com if you need additional information or have any
20  * questions.
21  */
22 
23 /*
24  * This file is available under and governed by the GNU General Public
25  * License version 2 only, as published by the Free Software Foundation.
26  * However, the following notice accompanied the original version of this
27  * file:
28  *
29  * Written by Doug Lea with assistance from members of JCP JSR-166
30  * Expert Group and released to the public domain, as explained at
31  * http://creativecommons.org/publicdomain/zero/1.0/
32  * Other contributors include Andrew Wright, Jeffrey Hayes,
33  * Pat Fisher, Mike Judd.
34  */
35 
36 import static java.util.concurrent.TimeUnit.MILLISECONDS;
37 
38 import java.security.AccessControlContext;
39 import java.security.AccessControlException;
40 import java.security.AccessController;
41 import java.security.PrivilegedAction;
42 import java.security.PrivilegedExceptionAction;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.concurrent.Callable;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.Executors;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Future;
50 import java.util.concurrent.ScheduledExecutorService;
51 import java.util.concurrent.ThreadPoolExecutor;
52 
53 import junit.framework.Test;
54 import junit.framework.TestSuite;
55 
56 public class ExecutorsTest extends JSR166TestCase {
main(String[] args)57     public static void main(String[] args) {
58         main(suite(), args);
59     }
suite()60     public static Test suite() {
61         return new TestSuite(ExecutorsTest.class);
62     }
63 
64     /**
65      * A newCachedThreadPool can execute runnables
66      */
testNewCachedThreadPool1()67     public void testNewCachedThreadPool1() {
68         final ExecutorService e = Executors.newCachedThreadPool();
69         try (PoolCleaner cleaner = cleaner(e)) {
70             e.execute(new NoOpRunnable());
71             e.execute(new NoOpRunnable());
72             e.execute(new NoOpRunnable());
73         }
74     }
75 
76     /**
77      * A newCachedThreadPool with given ThreadFactory can execute runnables
78      */
testNewCachedThreadPool2()79     public void testNewCachedThreadPool2() {
80         final ExecutorService e = Executors.newCachedThreadPool(new SimpleThreadFactory());
81         try (PoolCleaner cleaner = cleaner(e)) {
82             e.execute(new NoOpRunnable());
83             e.execute(new NoOpRunnable());
84             e.execute(new NoOpRunnable());
85         }
86     }
87 
88     /**
89      * A newCachedThreadPool with null ThreadFactory throws NPE
90      */
testNewCachedThreadPool3()91     public void testNewCachedThreadPool3() {
92         try {
93             ExecutorService e = Executors.newCachedThreadPool(null);
94             shouldThrow();
95         } catch (NullPointerException success) {}
96     }
97 
98     /**
99      * A new SingleThreadExecutor can execute runnables
100      */
testNewSingleThreadExecutor1()101     public void testNewSingleThreadExecutor1() {
102         final ExecutorService e = Executors.newSingleThreadExecutor();
103         try (PoolCleaner cleaner = cleaner(e)) {
104             e.execute(new NoOpRunnable());
105             e.execute(new NoOpRunnable());
106             e.execute(new NoOpRunnable());
107         }
108     }
109 
110     /**
111      * A new SingleThreadExecutor with given ThreadFactory can execute runnables
112      */
testNewSingleThreadExecutor2()113     public void testNewSingleThreadExecutor2() {
114         final ExecutorService e = Executors.newSingleThreadExecutor(new SimpleThreadFactory());
115         try (PoolCleaner cleaner = cleaner(e)) {
116             e.execute(new NoOpRunnable());
117             e.execute(new NoOpRunnable());
118             e.execute(new NoOpRunnable());
119         }
120     }
121 
122     /**
123      * A new SingleThreadExecutor with null ThreadFactory throws NPE
124      */
testNewSingleThreadExecutor3()125     public void testNewSingleThreadExecutor3() {
126         try {
127             ExecutorService e = Executors.newSingleThreadExecutor(null);
128             shouldThrow();
129         } catch (NullPointerException success) {}
130     }
131 
132     /**
133      * A new SingleThreadExecutor cannot be casted to concrete implementation
134      */
testCastNewSingleThreadExecutor()135     public void testCastNewSingleThreadExecutor() {
136         final ExecutorService e = Executors.newSingleThreadExecutor();
137         try (PoolCleaner cleaner = cleaner(e)) {
138             try {
139                 ThreadPoolExecutor tpe = (ThreadPoolExecutor)e;
140                 shouldThrow();
141             } catch (ClassCastException success) {}
142         }
143     }
144 
145     /**
146      * A new newFixedThreadPool can execute runnables
147      */
testNewFixedThreadPool1()148     public void testNewFixedThreadPool1() {
149         final ExecutorService e = Executors.newFixedThreadPool(2);
150         try (PoolCleaner cleaner = cleaner(e)) {
151             e.execute(new NoOpRunnable());
152             e.execute(new NoOpRunnable());
153             e.execute(new NoOpRunnable());
154         }
155     }
156 
157     /**
158      * A new newFixedThreadPool with given ThreadFactory can execute runnables
159      */
testNewFixedThreadPool2()160     public void testNewFixedThreadPool2() {
161         final ExecutorService e = Executors.newFixedThreadPool(2, new SimpleThreadFactory());
162         try (PoolCleaner cleaner = cleaner(e)) {
163             e.execute(new NoOpRunnable());
164             e.execute(new NoOpRunnable());
165             e.execute(new NoOpRunnable());
166         }
167     }
168 
169     /**
170      * A new newFixedThreadPool with null ThreadFactory throws
171      * NullPointerException
172      */
testNewFixedThreadPool3()173     public void testNewFixedThreadPool3() {
174         try {
175             ExecutorService e = Executors.newFixedThreadPool(2, null);
176             shouldThrow();
177         } catch (NullPointerException success) {}
178     }
179 
180     /**
181      * A new newFixedThreadPool with 0 threads throws IllegalArgumentException
182      */
testNewFixedThreadPool4()183     public void testNewFixedThreadPool4() {
184         try {
185             ExecutorService e = Executors.newFixedThreadPool(0);
186             shouldThrow();
187         } catch (IllegalArgumentException success) {}
188     }
189 
190     /**
191      * An unconfigurable newFixedThreadPool can execute runnables
192      */
testUnconfigurableExecutorService()193     public void testUnconfigurableExecutorService() {
194         final ExecutorService e = Executors.unconfigurableExecutorService(Executors.newFixedThreadPool(2));
195         try (PoolCleaner cleaner = cleaner(e)) {
196             e.execute(new NoOpRunnable());
197             e.execute(new NoOpRunnable());
198             e.execute(new NoOpRunnable());
199         }
200     }
201 
202     /**
203      * unconfigurableExecutorService(null) throws NPE
204      */
testUnconfigurableExecutorServiceNPE()205     public void testUnconfigurableExecutorServiceNPE() {
206         try {
207             ExecutorService e = Executors.unconfigurableExecutorService(null);
208             shouldThrow();
209         } catch (NullPointerException success) {}
210     }
211 
212     /**
213      * unconfigurableScheduledExecutorService(null) throws NPE
214      */
testUnconfigurableScheduledExecutorServiceNPE()215     public void testUnconfigurableScheduledExecutorServiceNPE() {
216         try {
217             ExecutorService e = Executors.unconfigurableScheduledExecutorService(null);
218             shouldThrow();
219         } catch (NullPointerException success) {}
220     }
221 
222     /**
223      * a newSingleThreadScheduledExecutor successfully runs delayed task
224      */
testNewSingleThreadScheduledExecutor()225     public void testNewSingleThreadScheduledExecutor() throws Exception {
226         final ScheduledExecutorService p = Executors.newSingleThreadScheduledExecutor();
227         try (PoolCleaner cleaner = cleaner(p)) {
228             final CountDownLatch proceed = new CountDownLatch(1);
229             final Runnable task = new CheckedRunnable() {
230                 public void realRun() {
231                     await(proceed);
232                 }};
233             long startTime = System.nanoTime();
234             Future f = p.schedule(Executors.callable(task, Boolean.TRUE),
235                                   timeoutMillis(), MILLISECONDS);
236             assertFalse(f.isDone());
237             proceed.countDown();
238             assertSame(Boolean.TRUE, f.get(LONG_DELAY_MS, MILLISECONDS));
239             assertSame(Boolean.TRUE, f.get());
240             assertTrue(f.isDone());
241             assertFalse(f.isCancelled());
242             assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
243         }
244     }
245 
246     /**
247      * a newScheduledThreadPool successfully runs delayed task
248      */
testNewScheduledThreadPool()249     public void testNewScheduledThreadPool() throws Exception {
250         final ScheduledExecutorService p = Executors.newScheduledThreadPool(2);
251         try (PoolCleaner cleaner = cleaner(p)) {
252             final CountDownLatch proceed = new CountDownLatch(1);
253             final Runnable task = new CheckedRunnable() {
254                 public void realRun() {
255                     await(proceed);
256                 }};
257             long startTime = System.nanoTime();
258             Future f = p.schedule(Executors.callable(task, Boolean.TRUE),
259                                   timeoutMillis(), MILLISECONDS);
260             assertFalse(f.isDone());
261             proceed.countDown();
262             assertSame(Boolean.TRUE, f.get(LONG_DELAY_MS, MILLISECONDS));
263             assertSame(Boolean.TRUE, f.get());
264             assertTrue(f.isDone());
265             assertFalse(f.isCancelled());
266             assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
267         }
268     }
269 
270     /**
271      * an unconfigurable newScheduledThreadPool successfully runs delayed task
272      */
testUnconfigurableScheduledExecutorService()273     public void testUnconfigurableScheduledExecutorService() throws Exception {
274         final ScheduledExecutorService p =
275             Executors.unconfigurableScheduledExecutorService
276             (Executors.newScheduledThreadPool(2));
277         try (PoolCleaner cleaner = cleaner(p)) {
278             final CountDownLatch proceed = new CountDownLatch(1);
279             final Runnable task = new CheckedRunnable() {
280                 public void realRun() {
281                     await(proceed);
282                 }};
283             long startTime = System.nanoTime();
284             Future f = p.schedule(Executors.callable(task, Boolean.TRUE),
285                                   timeoutMillis(), MILLISECONDS);
286             assertFalse(f.isDone());
287             proceed.countDown();
288             assertSame(Boolean.TRUE, f.get(LONG_DELAY_MS, MILLISECONDS));
289             assertSame(Boolean.TRUE, f.get());
290             assertTrue(f.isDone());
291             assertFalse(f.isCancelled());
292             assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
293         }
294     }
295 
296     /**
297      * Future.get on submitted tasks will time out if they compute too long.
298      */
testTimedCallable()299     public void testTimedCallable() throws Exception {
300         final ExecutorService[] executors = {
301             Executors.newSingleThreadExecutor(),
302             Executors.newCachedThreadPool(),
303             Executors.newFixedThreadPool(2),
304             Executors.newScheduledThreadPool(2),
305         };
306 
307         final Runnable sleeper = new CheckedInterruptedRunnable() {
308             public void realRun() throws InterruptedException {
309                 delay(LONG_DELAY_MS);
310             }};
311 
312         List<Thread> threads = new ArrayList<>();
313         for (final ExecutorService executor : executors) {
314             threads.add(newStartedThread(new CheckedRunnable() {
315                 public void realRun() {
316                     Future future = executor.submit(sleeper);
317                     assertFutureTimesOut(future);
318                 }}));
319         }
320         for (Thread thread : threads)
321             awaitTermination(thread);
322         for (ExecutorService executor : executors)
323             joinPool(executor);
324     }
325 
326     /**
327      * ThreadPoolExecutor using defaultThreadFactory has
328      * specified group, priority, daemon status, and name
329      */
testDefaultThreadFactory()330     public void testDefaultThreadFactory() throws Exception {
331         final ThreadGroup egroup = Thread.currentThread().getThreadGroup();
332         final CountDownLatch done = new CountDownLatch(1);
333         Runnable r = new CheckedRunnable() {
334             public void realRun() {
335                 try {
336                     Thread current = Thread.currentThread();
337                     assertFalse(current.isDaemon());
338                     assertTrue(current.getPriority() <= Thread.NORM_PRIORITY);
339                     SecurityManager s = System.getSecurityManager();
340                     assertSame(current.getThreadGroup(),
341                                (s == null) ? egroup : s.getThreadGroup());
342                     assertTrue(current.getName().endsWith("thread-1"));
343                 } catch (SecurityException ok) {
344                     // Also pass if not allowed to change setting
345                 }
346                 done.countDown();
347             }};
348         ExecutorService e = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
349         try (PoolCleaner cleaner = cleaner(e)) {
350             e.execute(r);
351             await(done);
352         }
353     }
354 
355     /**
356      * ThreadPoolExecutor using privilegedThreadFactory has
357      * specified group, priority, daemon status, name,
358      * access control context and context class loader
359      */
testPrivilegedThreadFactory()360     public void testPrivilegedThreadFactory() throws Exception {
361         final CountDownLatch done = new CountDownLatch(1);
362         Runnable r = new CheckedRunnable() {
363             public void realRun() throws Exception {
364                 final ThreadGroup egroup = Thread.currentThread().getThreadGroup();
365                 final ClassLoader thisccl = Thread.currentThread().getContextClassLoader();
366                 final AccessControlContext thisacc = AccessController.getContext();
367                 Runnable r = new CheckedRunnable() {
368                     public void realRun() {
369                         Thread current = Thread.currentThread();
370                         assertFalse(current.isDaemon());
371                         assertTrue(current.getPriority() <= Thread.NORM_PRIORITY);
372                         SecurityManager s = System.getSecurityManager();
373                         assertSame(current.getThreadGroup(),
374                                    (s == null) ? egroup : s.getThreadGroup());
375                         assertTrue(current.getName().endsWith("thread-1"));
376                         assertSame(thisccl, current.getContextClassLoader());
377                         assertEquals(thisacc, AccessController.getContext());
378                         done.countDown();
379                     }};
380                 ExecutorService e = Executors.newSingleThreadExecutor(Executors.privilegedThreadFactory());
381                 try (PoolCleaner cleaner = cleaner(e)) {
382                     e.execute(r);
383                     await(done);
384                 }
385             }};
386 
387         runWithPermissions(r,
388                            new RuntimePermission("getClassLoader"),
389                            new RuntimePermission("setContextClassLoader"),
390                            new RuntimePermission("modifyThread"));
391     }
392 
haveCCLPermissions()393     boolean haveCCLPermissions() {
394         SecurityManager sm = System.getSecurityManager();
395         if (sm != null) {
396             try {
397                 sm.checkPermission(new RuntimePermission("setContextClassLoader"));
398                 sm.checkPermission(new RuntimePermission("getClassLoader"));
399             } catch (AccessControlException e) {
400                 return false;
401             }
402         }
403         return true;
404     }
405 
checkCCL()406     void checkCCL() {
407         SecurityManager sm = System.getSecurityManager();
408         if (sm != null) {
409             sm.checkPermission(new RuntimePermission("setContextClassLoader"));
410             sm.checkPermission(new RuntimePermission("getClassLoader"));
411         }
412     }
413 
414     class CheckCCL implements Callable<Object> {
call()415         public Object call() {
416             checkCCL();
417             return null;
418         }
419     }
420 
421     /**
422      * Without class loader permissions, creating
423      * privilegedCallableUsingCurrentClassLoader throws ACE
424      */
testCreatePrivilegedCallableUsingCCLWithNoPrivs()425     public void testCreatePrivilegedCallableUsingCCLWithNoPrivs() {
426         Runnable r = new CheckedRunnable() {
427             public void realRun() throws Exception {
428                 if (System.getSecurityManager() == null)
429                     return;
430                 try {
431                     Executors.privilegedCallableUsingCurrentClassLoader(new NoOpCallable());
432                     shouldThrow();
433                 } catch (AccessControlException success) {}
434             }};
435 
436         runWithoutPermissions(r);
437     }
438 
439     /**
440      * With class loader permissions, calling
441      * privilegedCallableUsingCurrentClassLoader does not throw ACE
442      */
testPrivilegedCallableUsingCCLWithPrivs()443     public void testPrivilegedCallableUsingCCLWithPrivs() throws Exception {
444         Runnable r = new CheckedRunnable() {
445             public void realRun() throws Exception {
446                 Executors.privilegedCallableUsingCurrentClassLoader
447                     (new NoOpCallable())
448                     .call();
449             }};
450 
451         runWithPermissions(r,
452                            new RuntimePermission("getClassLoader"),
453                            new RuntimePermission("setContextClassLoader"));
454     }
455 
456     /**
457      * Without permissions, calling privilegedCallable throws ACE
458      */
testPrivilegedCallableWithNoPrivs()459     public void testPrivilegedCallableWithNoPrivs() throws Exception {
460         // Avoid classloader-related SecurityExceptions in swingui.TestRunner
461         Executors.privilegedCallable(new CheckCCL());
462 
463         Runnable r = new CheckedRunnable() {
464             public void realRun() throws Exception {
465                 if (System.getSecurityManager() == null)
466                     return;
467                 Callable task = Executors.privilegedCallable(new CheckCCL());
468                 try {
469                     task.call();
470                     shouldThrow();
471                 } catch (AccessControlException success) {}
472             }};
473 
474         runWithoutPermissions(r);
475 
476         // It seems rather difficult to test that the
477         // AccessControlContext of the privilegedCallable is used
478         // instead of its caller.  Below is a failed attempt to do
479         // that, which does not work because the AccessController
480         // cannot capture the internal state of the current Policy.
481         // It would be much more work to differentiate based on,
482         // e.g. CodeSource.
483 
484 //         final AccessControlContext[] noprivAcc = new AccessControlContext[1];
485 //         final Callable[] task = new Callable[1];
486 
487 //         runWithPermissions
488 //             (new CheckedRunnable() {
489 //                 public void realRun() {
490 //                     if (System.getSecurityManager() == null)
491 //                         return;
492 //                     noprivAcc[0] = AccessController.getContext();
493 //                     task[0] = Executors.privilegedCallable(new CheckCCL());
494 //                     try {
495 //                         AccessController.doPrivileged(new PrivilegedAction<Void>() {
496 //                                                           public Void run() {
497 //                                                               checkCCL();
498 //                                                               return null;
499 //                                                           }}, noprivAcc[0]);
500 //                         shouldThrow();
501 //                     } catch (AccessControlException success) {}
502 //                 }});
503 
504 //         runWithPermissions
505 //             (new CheckedRunnable() {
506 //                 public void realRun() throws Exception {
507 //                     if (System.getSecurityManager() == null)
508 //                         return;
509 //                     // Verify that we have an underprivileged ACC
510 //                     try {
511 //                         AccessController.doPrivileged(new PrivilegedAction<Void>() {
512 //                                                           public Void run() {
513 //                                                               checkCCL();
514 //                                                               return null;
515 //                                                           }}, noprivAcc[0]);
516 //                         shouldThrow();
517 //                     } catch (AccessControlException success) {}
518 
519 //                     try {
520 //                         task[0].call();
521 //                         shouldThrow();
522 //                     } catch (AccessControlException success) {}
523 //                 }},
524 //              new RuntimePermission("getClassLoader"),
525 //              new RuntimePermission("setContextClassLoader"));
526     }
527 
528     /**
529      * With permissions, calling privilegedCallable succeeds
530      */
testPrivilegedCallableWithPrivs()531     public void testPrivilegedCallableWithPrivs() throws Exception {
532         Runnable r = new CheckedRunnable() {
533             public void realRun() throws Exception {
534                 Executors.privilegedCallable(new CheckCCL()).call();
535             }};
536 
537         runWithPermissions(r,
538                            new RuntimePermission("getClassLoader"),
539                            new RuntimePermission("setContextClassLoader"));
540     }
541 
542     /**
543      * callable(Runnable) returns null when called
544      */
testCallable1()545     public void testCallable1() throws Exception {
546         Callable c = Executors.callable(new NoOpRunnable());
547         assertNull(c.call());
548     }
549 
550     /**
551      * callable(Runnable, result) returns result when called
552      */
testCallable2()553     public void testCallable2() throws Exception {
554         Callable c = Executors.callable(new NoOpRunnable(), one);
555         assertSame(one, c.call());
556     }
557 
558     /**
559      * callable(PrivilegedAction) returns its result when called
560      */
testCallable3()561     public void testCallable3() throws Exception {
562         Callable c = Executors.callable(new PrivilegedAction() {
563                 public Object run() { return one; }});
564         assertSame(one, c.call());
565     }
566 
567     /**
568      * callable(PrivilegedExceptionAction) returns its result when called
569      */
testCallable4()570     public void testCallable4() throws Exception {
571         Callable c = Executors.callable(new PrivilegedExceptionAction() {
572                 public Object run() { return one; }});
573         assertSame(one, c.call());
574     }
575 
576     /**
577      * callable(null Runnable) throws NPE
578      */
testCallableNPE1()579     public void testCallableNPE1() {
580         try {
581             Callable c = Executors.callable((Runnable) null);
582             shouldThrow();
583         } catch (NullPointerException success) {}
584     }
585 
586     /**
587      * callable(null, result) throws NPE
588      */
testCallableNPE2()589     public void testCallableNPE2() {
590         try {
591             Callable c = Executors.callable((Runnable) null, one);
592             shouldThrow();
593         } catch (NullPointerException success) {}
594     }
595 
596     /**
597      * callable(null PrivilegedAction) throws NPE
598      */
testCallableNPE3()599     public void testCallableNPE3() {
600         try {
601             Callable c = Executors.callable((PrivilegedAction) null);
602             shouldThrow();
603         } catch (NullPointerException success) {}
604     }
605 
606     /**
607      * callable(null PrivilegedExceptionAction) throws NPE
608      */
testCallableNPE4()609     public void testCallableNPE4() {
610         try {
611             Callable c = Executors.callable((PrivilegedExceptionAction) null);
612             shouldThrow();
613         } catch (NullPointerException success) {}
614     }
615 
616     /**
617      * callable(runnable, x).toString() contains toString of wrapped task
618      */
testCallable_withResult_toString()619     public void testCallable_withResult_toString() {
620         if (testImplementationDetails) {
621             Runnable r = () -> {};
622             Callable<String> c = Executors.callable(r, "");
623             assertEquals(
624                 identityString(c) + "[Wrapped task = " + r.toString() + "]",
625                 c.toString());
626         }
627     }
628 
629     /**
630      * callable(runnable).toString() contains toString of wrapped task
631      */
testCallable_toString()632     public void testCallable_toString() {
633         if (testImplementationDetails) {
634             Runnable r = () -> {};
635             Callable<Object> c = Executors.callable(r);
636             assertEquals(
637                 identityString(c) + "[Wrapped task = " + r.toString() + "]",
638                 c.toString());
639         }
640     }
641 
642     /**
643      * privilegedCallable(callable).toString() contains toString of wrapped task
644      */
testPrivilegedCallable_toString()645     public void testPrivilegedCallable_toString() {
646         if (testImplementationDetails) {
647             Callable<String> c = () -> "";
648             Callable<String> priv = Executors.privilegedCallable(c);
649             assertEquals(
650                 identityString(priv) + "[Wrapped task = " + c.toString() + "]",
651                 priv.toString());
652         }
653     }
654 
655     /**
656      * privilegedCallableUsingCurrentClassLoader(callable).toString()
657      * contains toString of wrapped task
658      */
testPrivilegedCallableUsingCurrentClassLoader_toString()659     public void testPrivilegedCallableUsingCurrentClassLoader_toString() {
660         if (testImplementationDetails) {
661             Callable<String> c = () -> "";
662             Callable<String> priv = Executors.privilegedCallableUsingCurrentClassLoader(c);
663             assertEquals(
664                 identityString(priv) + "[Wrapped task = " + c.toString() + "]",
665                 priv.toString());
666         }
667     }
668 }
669