1 /*
  2  * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /**
 25  * @test
 26  * @summary Unit test for Thread.Builder
 27  * @enablePreview
 28  * @run junit BuilderTest
 29  */
 30 
 31 import java.util.concurrent.*;
 32 import java.util.concurrent.atomic.AtomicBoolean;
 33 import java.util.concurrent.atomic.AtomicReference;
 34 import java.util.concurrent.locks.LockSupport;
 35 
 36 import org.junit.jupiter.api.Test;
 37 import static org.junit.jupiter.api.Assertions.*;
 38 import static org.junit.jupiter.api.Assumptions.*;
 39 
 40 class BuilderTest {
 41 
 42     /**
 43      * Test Thread.ofPlatform to create platform threads.
 44      */
 45     @Test
 46     void testPlatformThread() throws Exception {
 47         Thread parent = Thread.currentThread();
 48         Thread.Builder.OfPlatform builder = Thread.ofPlatform();
 49 
 50         // unstarted
 51         AtomicBoolean done1 = new AtomicBoolean();
 52         Thread thread1 = builder.unstarted(() -> done1.set(true));
 53         assertFalse(thread1.isVirtual());
 54         assertTrue(thread1.getState() == Thread.State.NEW);
 55         assertFalse(thread1.getName().isEmpty());
 56         assertTrue(thread1.getThreadGroup() == parent.getThreadGroup());
 57         assertTrue(thread1.isDaemon() == parent.isDaemon());
 58         assertTrue(thread1.getPriority() == parent.getPriority());
 59         assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
 60         thread1.start();
 61         thread1.join();
 62         assertTrue(done1.get());
 63 
 64         // start
 65         AtomicBoolean done2 = new AtomicBoolean();
 66         Thread thread2 = builder.start(() -> done2.set(true));
 67         assertFalse(thread2.isVirtual());
 68         assertTrue(thread2.getState() != Thread.State.NEW);
 69         assertFalse(thread2.getName().isEmpty());
 70         ThreadGroup group2 = thread2.getThreadGroup();
 71         assertTrue(group2 == parent.getThreadGroup() || group2 == null);
 72         assertTrue(thread2.isDaemon() == parent.isDaemon());
 73         assertTrue(thread2.getPriority() == parent.getPriority());
 74         assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
 75         thread2.join();
 76         assertTrue(done2.get());
 77 
 78         // factory
 79         AtomicBoolean done3 = new AtomicBoolean();
 80         Thread thread3 = builder.factory().newThread(() -> done3.set(true));
 81         assertFalse(thread3.isVirtual());
 82         assertTrue(thread3.getState() == Thread.State.NEW);
 83         assertFalse(thread3.getName().isEmpty());
 84         assertTrue(thread3.getThreadGroup() == parent.getThreadGroup());
 85         assertTrue(thread3.isDaemon() == parent.isDaemon());
 86         assertTrue(thread3.getPriority() == parent.getPriority());
 87         assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
 88         thread3.start();
 89         thread3.join();
 90         assertTrue(done3.get());
 91     }
 92 
 93     /**
 94      * Test Thread.ofVirtual to create virtual threads.
 95      */
 96     @Test
 97     void testVirtualThread() throws Exception {
 98         Thread parent = Thread.currentThread();
 99         Thread.Builder.OfVirtual builder = Thread.ofVirtual();
100 
101         // unstarted
102         AtomicBoolean done1 = new AtomicBoolean();
103         Thread thread1 = builder.unstarted(() -> done1.set(true));
104         assertTrue(thread1.isVirtual());
105         assertTrue(thread1.getState() == Thread.State.NEW);
106         assertTrue(thread1.getName().isEmpty());
107         assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
108         assertTrue(thread1.isDaemon());
109         assertTrue(thread1.getPriority() == Thread.NORM_PRIORITY);
110         thread1.start();
111         thread1.join();
112         assertTrue(done1.get());
113 
114         // start
115         AtomicBoolean done2 = new AtomicBoolean();
116         Thread thread2 = builder.start(() -> done2.set(true));
117         assertTrue(thread2.isVirtual());
118         assertTrue(thread2.getState() != Thread.State.NEW);
119         assertTrue(thread2.getName().isEmpty());
120         assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
121         assertTrue(thread2.isDaemon());
122         assertTrue(thread2.getPriority() == Thread.NORM_PRIORITY);
123         thread2.join();
124         assertTrue(done2.get());
125 
126         // factory
127         AtomicBoolean done3 = new AtomicBoolean();
128         Thread thread3 = builder.factory().newThread(() -> done3.set(true));
129         assertTrue(thread3.isVirtual());
130         assertTrue(thread3.getState() == Thread.State.NEW);
131         assertTrue(thread3.getName().isEmpty());
132         assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
133         assertTrue(thread3.isDaemon());
134         assertTrue(thread3.getPriority() == Thread.NORM_PRIORITY);
135         thread3.start();
136         thread3.join();
137         assertTrue(done3.get());
138     }
139 
140     /**
141      * Test Thread.Builder.name.
142      */
143     @Test
144     void testName1() {
145         Thread.Builder builder = Thread.ofPlatform().name("duke");
146 
147         Thread thread1 = builder.unstarted(() -> { });
148         Thread thread2 = builder.start(() -> { });
149         Thread thread3 = builder.factory().newThread(() -> { });
150 
151         assertTrue(thread1.getName().equals("duke"));
152         assertTrue(thread2.getName().equals("duke"));
153         assertTrue(thread3.getName().equals("duke"));
154     }
155 
156     @Test
157     void testName2() {
158         Thread.Builder builder = Thread.ofVirtual().name("duke");
159 
160         Thread thread1 = builder.unstarted(() -> { });
161         Thread thread2 = builder.start(() -> { });
162         Thread thread3 = builder.factory().newThread(() -> { });
163 
164         assertTrue(thread1.getName().equals("duke"));
165         assertTrue(thread2.getName().equals("duke"));
166         assertTrue(thread3.getName().equals("duke"));
167     }
168 
169     @Test
170     void testName3() {
171         Thread.Builder builder = Thread.ofPlatform().name("duke-", 100);
172 
173         Thread thread1 = builder.unstarted(() -> { });
174         Thread thread2 = builder.unstarted(() -> { });
175         Thread thread3 = builder.unstarted(() -> { });
176 
177         assertTrue(thread1.getName().equals("duke-100"));
178         assertTrue(thread2.getName().equals("duke-101"));
179         assertTrue(thread3.getName().equals("duke-102"));
180 
181         ThreadFactory factory = builder.factory();
182         Thread thread4 = factory.newThread(() -> { });
183         Thread thread5 = factory.newThread(() -> { });
184         Thread thread6 = factory.newThread(() -> { });
185 
186         assertTrue(thread4.getName().equals("duke-103"));
187         assertTrue(thread5.getName().equals("duke-104"));
188         assertTrue(thread6.getName().equals("duke-105"));
189     }
190 
191     @Test
192     void testName4() {
193         Thread.Builder builder = Thread.ofVirtual().name("duke-", 100);
194 
195         Thread thread1 = builder.unstarted(() -> { });
196         Thread thread2 = builder.unstarted(() -> { });
197         Thread thread3 = builder.unstarted(() -> { });
198 
199         assertTrue(thread1.getName().equals("duke-100"));
200         assertTrue(thread2.getName().equals("duke-101"));
201         assertTrue(thread3.getName().equals("duke-102"));
202 
203         ThreadFactory factory = builder.factory();
204         Thread thread4 = factory.newThread(() -> { });
205         Thread thread5 = factory.newThread(() -> { });
206         Thread thread6 = factory.newThread(() -> { });
207 
208         assertTrue(thread4.getName().equals("duke-103"));
209         assertTrue(thread5.getName().equals("duke-104"));
210         assertTrue(thread6.getName().equals("duke-105"));
211     }
212 
213     /**
214      * Test Thread.Builder.OfPlatform.group.
215      */
216     @Test
217     void testThreadGroup1() {
218         ThreadGroup group = new ThreadGroup("groupies");
219         Thread.Builder builder = Thread.ofPlatform().group(group);
220 
221         Thread thread1 = builder.unstarted(() -> { });
222 
223         AtomicBoolean done = new AtomicBoolean();
224         Thread thread2 = builder.start(() -> {
225             while (!done.get()) {
226                 LockSupport.park();
227             }
228         });
229 
230         Thread thread3 = builder.factory().newThread(() -> { });
231 
232         try {
233             assertTrue(thread1.getThreadGroup() == group);
234             assertTrue(thread2.getThreadGroup() == group);
235             assertTrue(thread3.getThreadGroup() == group);
236         } finally {
237             done.set(true);
238             LockSupport.unpark(thread2);
239         }
240     }
241 
242     @Test
243     void testThreadGroup2() {
244         ThreadGroup vgroup = Thread.ofVirtual().unstarted(() -> { }).getThreadGroup();
245         assertEquals("VirtualThreads", vgroup.getName());
246 
247         Thread thread1 = Thread.ofVirtual().unstarted(() -> { });
248         Thread thread2 = Thread.ofVirtual().start(LockSupport::park);
249         Thread thread3 = Thread.ofVirtual().factory().newThread(() -> { });
250 
251         try {
252             assertTrue(thread1.getThreadGroup() == vgroup);
253             assertTrue(thread2.getThreadGroup() == vgroup);
254             assertTrue(thread3.getThreadGroup() == vgroup);
255         } finally {
256             LockSupport.unpark(thread2);
257         }
258     }
259 
260     /**
261      * Test Thread.Builder.OfPlatform.priority.
262      */
263     @Test
264     void testPriority1() {
265         int priority = Thread.currentThread().getPriority();
266 
267         Thread.Builder builder = Thread.ofPlatform();
268         Thread thread1 = builder.unstarted(() -> { });
269         Thread thread2 = builder.start(() -> { });
270         Thread thread3 = builder.factory().newThread(() -> { });
271 
272         assertTrue(thread1.getPriority() == priority);
273         assertTrue(thread2.getPriority() == priority);
274         assertTrue(thread3.getPriority() == priority);
275     }
276 
277     @Test
278     void testPriority2() {
279         int priority = Thread.MIN_PRIORITY;
280 
281         Thread.Builder builder = Thread.ofPlatform().priority(priority);
282         Thread thread1 = builder.unstarted(() -> { });
283         Thread thread2 = builder.start(() -> { });
284         Thread thread3 = builder.factory().newThread(() -> { });
285 
286         assertTrue(thread1.getPriority() == priority);
287         assertTrue(thread2.getPriority() == priority);
288         assertTrue(thread3.getPriority() == priority);
289     }
290 
291     @Test
292     void testPriority3() {
293         Thread currentThread = Thread.currentThread();
294         assumeFalse(currentThread.isVirtual(), "Main thread is a virtual thread");
295 
296         int maxPriority = currentThread.getThreadGroup().getMaxPriority();
297         int priority = Math.min(maxPriority + 1, Thread.MAX_PRIORITY);
298 
299         Thread.Builder builder = Thread.ofPlatform().priority(priority);
300         Thread thread1 = builder.unstarted(() -> { });
301         Thread thread2 = builder.start(() -> { });
302         Thread thread3 = builder.factory().newThread(() -> { });
303 
304         assertTrue(thread1.getPriority() == priority);
305         assertTrue(thread2.getPriority() == priority);
306         assertTrue(thread3.getPriority() == priority);
307     }
308 
309     @Test
310     void testPriority4() {
311         var builder = Thread.ofPlatform();
312         assertThrows(IllegalArgumentException.class,
313                      () -> builder.priority(Thread.MIN_PRIORITY - 1));
314     }
315 
316     @Test
317     void testPriority5() {
318         var builder = Thread.ofPlatform();
319         assertThrows(IllegalArgumentException.class,
320                      () -> builder.priority(Thread.MAX_PRIORITY + 1));
321     }
322 
323     /**
324      * Test Thread.Builder.OfPlatform.daemon.
325      */
326     @Test
327     void testDaemon1() {
328         Thread.Builder builder = Thread.ofPlatform().daemon(false);
329 
330         Thread thread1 = builder.unstarted(() -> { });
331         Thread thread2 = builder.start(() -> { });
332         Thread thread3 = builder.factory().newThread(() -> { });
333 
334         assertFalse(thread1.isDaemon());
335         assertFalse(thread2.isDaemon());
336         assertFalse(thread3.isDaemon());
337     }
338 
339     @Test
340     void testDaemon2() {
341         Thread.Builder builder = Thread.ofPlatform().daemon(true);
342 
343         Thread thread1 = builder.unstarted(() -> { });
344         Thread thread2 = builder.start(() -> { });
345         Thread thread3 = builder.factory().newThread(() -> { });
346 
347         assertTrue(thread1.isDaemon());
348         assertTrue(thread2.isDaemon());
349         assertTrue(thread3.isDaemon());
350     }
351 
352     @Test
353     void testDaemon3() {
354         Thread.Builder builder = Thread.ofPlatform().daemon();
355 
356         Thread thread1 = builder.unstarted(() -> { });
357         Thread thread2 = builder.start(() -> { });
358         Thread thread3 = builder.factory().newThread(() -> { });
359 
360         assertTrue(thread1.isDaemon());
361         assertTrue(thread2.isDaemon());
362         assertTrue(thread3.isDaemon());
363     }
364 
365     @Test
366     void testDaemon4() {
367         Thread.Builder builder = Thread.ofPlatform();
368 
369         Thread thread1 = builder.unstarted(() -> { });
370         Thread thread2 = builder.start(() -> { });
371         Thread thread3 = builder.factory().newThread(() -> { });
372 
373         // daemon status should be inherited
374         boolean d = Thread.currentThread().isDaemon();
375         assertTrue(thread1.isDaemon() == d);
376         assertTrue(thread2.isDaemon() == d);
377         assertTrue(thread3.isDaemon() == d);
378     }
379 
380     /**
381      * Test Thread.ofVirtual creates daemon threads.
382      */
383     @Test
384     void testDaemon5() {
385         Thread.Builder builder = Thread.ofVirtual();
386 
387         Thread thread1 = builder.unstarted(() -> { });
388         Thread thread2 = builder.start(() -> { });
389         Thread thread3 = builder.factory().newThread(() -> { });
390 
391         // daemon status should always be true
392         assertTrue(thread1.isDaemon());
393         assertTrue(thread2.isDaemon());
394         assertTrue(thread3.isDaemon());
395     }
396 
397     /**
398      * Test Thread.Builder.OfPlatform.stackSize.
399      */
400     @Test
401     void testStackSize1() {
402         Thread.Builder builder = Thread.ofPlatform().stackSize(1024*1024);
403         Thread thread1 = builder.unstarted(() -> { });
404         Thread thread2 = builder.start(() -> { });
405         Thread thread3 = builder.factory().newThread(() -> { });
406     }
407 
408     @Test
409     void testStackSize2() {
410         Thread.Builder builder = Thread.ofPlatform().stackSize(0);
411         Thread thread1 = builder.unstarted(() -> { });
412         Thread thread2 = builder.start(() -> { });
413         Thread thread3 = builder.factory().newThread(() -> { });
414     }
415 
416     @Test
417     void testStackSize3() {
418         var builder = Thread.ofPlatform();
419         assertThrows(IllegalArgumentException.class, () -> builder.stackSize(-1));
420     }
421 
422     /**
423      * Test Thread.Builder.uncaughtExceptionHandler.
424      */
425     @Test
426     void testUncaughtExceptionHandler1() throws Exception {
427         class FooException extends RuntimeException { }
428         AtomicReference<Thread> threadRef = new AtomicReference<>();
429         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
430         Thread thread = Thread.ofPlatform()
431                 .uncaughtExceptionHandler((t, e) -> {
432                     assertTrue(t == Thread.currentThread());
433                     threadRef.set(t);
434                     exceptionRef.set(e);
435                 })
436                 .start(() -> { throw new FooException(); });
437         thread.join();
438         assertTrue(threadRef.get() == thread);
439         assertTrue(exceptionRef.get() instanceof FooException);
440     }
441 
442     @Test
443     void testUncaughtExceptionHandler2() throws Exception {
444         class FooException extends RuntimeException { }
445         AtomicReference<Thread> threadRef = new AtomicReference<>();
446         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
447         Thread thread = Thread.ofVirtual()
448                 .uncaughtExceptionHandler((t, e) -> {
449                     assertTrue(t == Thread.currentThread());
450                     threadRef.set(t);
451                     exceptionRef.set(e);
452                 })
453                 .start(() -> { throw new FooException(); });
454         thread.join();
455         assertTrue(threadRef.get() == thread);
456         assertTrue(exceptionRef.get() instanceof FooException);
457     }
458 
459     @Test
460     void testUncaughtExceptionHandler3() throws Exception {
461         class FooException extends RuntimeException { }
462         AtomicReference<Thread> threadRef = new AtomicReference<>();
463         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
464         Thread thread = Thread.ofPlatform()
465                 .uncaughtExceptionHandler((t, e) -> {
466                     assertTrue(t == Thread.currentThread());
467                     threadRef.set(t);
468                     exceptionRef.set(e);
469                 })
470                 .factory()
471                 .newThread(() -> { throw new FooException(); });
472         thread.start();
473         thread.join();
474         assertTrue(threadRef.get() == thread);
475         assertTrue(exceptionRef.get() instanceof FooException);
476     }
477 
478     @Test
479     void testUncaughtExceptionHandler4() throws Exception {
480         class FooException extends RuntimeException { }
481         AtomicReference<Thread> threadRef = new AtomicReference<>();
482         AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
483         Thread thread = Thread.ofPlatform()
484                 .uncaughtExceptionHandler((t, e) -> {
485                     assertTrue(t == Thread.currentThread());
486                     threadRef.set(t);
487                     exceptionRef.set(e);
488                 })
489                 .factory()
490                 .newThread(() -> { throw new FooException(); });
491         thread.start();
492         thread.join();
493         assertTrue(threadRef.get() == thread);
494         assertTrue(exceptionRef.get() instanceof FooException);
495     }
496 
497     static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
498     static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();
499 
500     /**
501      * Tests that a builder creates threads that support thread locals
502      */
503     private void testThreadLocals(Thread.Builder builder) throws Exception {
504         AtomicBoolean done = new AtomicBoolean();
505         Runnable task = () -> {
506             Object value = new Object();
507             LOCAL.set(value);
508             assertTrue(LOCAL.get() == value);
509             done.set(true);
510         };
511 
512         done.set(false);
513         Thread thread1 = builder.unstarted(task);
514         thread1.start();
515         thread1.join();
516         assertTrue(done.get());
517 
518         done.set(false);
519         Thread thread2 = builder.start(task);
520         thread2.join();
521         assertTrue(done.get());
522 
523         done.set(false);
524         Thread thread3 = builder.factory().newThread(task);
525         thread3.start();
526         thread3.join();
527         assertTrue(done.get());
528     }
529 
530     /**
531      * Tests that a builder creates threads that do not support thread locals
532      */
533     private void testNoThreadLocals(Thread.Builder builder) throws Exception {
534         AtomicBoolean done = new AtomicBoolean();
535         Runnable task = () -> {
536             try {
537                 LOCAL.set(new Object());
538             } catch (UnsupportedOperationException expected) {
539                 done.set(true);
540             }
541         };
542 
543         done.set(false);
544         Thread thread1 = builder.unstarted(task);
545         thread1.start();
546         thread1.join();
547         assertTrue(done.get());
548 
549         done.set(false);
550         Thread thread2 = builder.start(task);
551         thread2.join();
552         assertTrue(done.get());
553 
554         done.set(false);
555         Thread thread3 = builder.factory().newThread(task);
556         thread3.start();
557         thread3.join();
558         assertTrue(done.get());
559     }
560 
561     /**
562      * Test Thread.Builder creates threads that allow thread locals.
563      */
564     @Test
565     void testThreadLocals1() throws Exception {
566         Thread.Builder builder = Thread.ofPlatform();
567         testThreadLocals(builder);
568     }
569 
570     @Test
571     void testThreadLocals2() throws Exception {
572         Thread.Builder builder = Thread.ofVirtual();
573         testThreadLocals(builder);
574     }
575 
576     /**
577      * Test Thread.Builder creating threads that disallow or allow
578      * thread locals.
579      */
580     @Test
581     void testThreadLocals3() throws Exception {
582         Thread.Builder builder = Thread.ofPlatform();
583 
584         // disallow
585         builder.allowSetThreadLocals(false);
586         testNoThreadLocals(builder);
587 
588         // allow
589         builder.allowSetThreadLocals(true);
590         testThreadLocals(builder);
591     }
592 
593     @Test
594     void testThreadLocals4() throws Exception {
595         Thread.Builder builder = Thread.ofVirtual();
596 
597         // disallow
598         builder.allowSetThreadLocals(false);
599         testNoThreadLocals(builder);
600 
601         // allow
602         builder.allowSetThreadLocals(true);
603         testThreadLocals(builder);
604     }
605 
606     /**
607      * Tests that a builder creates threads that inherits the initial values of
608      * inheritable thread locals.
609      */
610     private void testInheritedThreadLocals(Thread.Builder builder) throws Exception {
611         Object value = new Object();
612         INHERITED_LOCAL.set(value);
613 
614         AtomicBoolean done = new AtomicBoolean();
615         Runnable task = () -> {
616             assertTrue(INHERITED_LOCAL.get() == value);
617             done.set(true);
618         };
619 
620         done.set(false);
621         Thread thread1 = builder.unstarted(task);
622         thread1.start();
623         thread1.join();
624         assertTrue(done.get());
625 
626         done.set(false);
627         Thread thread2 = builder.start(task);
628         thread2.join();
629         assertTrue(done.get());
630 
631         done.set(false);
632         Thread thread3 = builder.factory().newThread(task);
633         thread3.start();
634         thread3.join();
635         assertTrue(done.get());
636     }
637 
638     /**
639      * Tests that a builder creates threads that do not inherit the initial values
640      * of inheritable thread locals.
641      */
642     private void testNoInheritedThreadLocals(Thread.Builder builder) throws Exception {
643         Object value = new Object();
644         INHERITED_LOCAL.set(value);
645 
646         AtomicBoolean done = new AtomicBoolean();
647         Runnable task = () -> {
648             assertNull(INHERITED_LOCAL.get());
649             done.set(true);
650         };
651 
652         done.set(false);
653         Thread thread1 = builder.unstarted(task);
654         thread1.start();
655         thread1.join();
656         assertTrue(done.get());
657 
658         done.set(false);
659         Thread thread2 = builder.start(task);
660         thread2.join();
661         assertTrue(done.get());
662 
663         done.set(false);
664         Thread thread3 = builder.factory().newThread(task);
665         thread3.start();
666         thread3.join();
667         assertTrue(done.get());
668     }
669 
670     /**
671      * Test Thread.Builder creating threads that inherit or do not inherit
672      * the initial values of inheritable thread locals.
673      */
674     @Test
675     void testInheritedThreadLocals1() throws Exception {
676         Thread.Builder builder = Thread.ofPlatform();
677         testInheritedThreadLocals(builder); // default
678 
679         // do no inherit
680         builder.inheritInheritableThreadLocals(false);
681         testNoInheritedThreadLocals(builder);
682 
683         // inherit
684         builder.inheritInheritableThreadLocals(true);
685         testInheritedThreadLocals(builder);
686     }
687 
688     @Test
689     void testInheritedThreadLocals2() throws Exception {
690         Thread.Builder builder = Thread.ofVirtual();
691         testInheritedThreadLocals(builder); // default
692 
693         // do no inherit
694         builder.inheritInheritableThreadLocals(false);
695         testNoInheritedThreadLocals(builder);
696 
697         // inherit
698         builder.inheritInheritableThreadLocals(true);
699         testInheritedThreadLocals(builder);
700     }
701 
702     @Test
703     void testInheritedThreadLocals3() throws Exception {
704         Thread.Builder builder = Thread.ofPlatform();
705 
706         // thread locals not allowed
707         builder.allowSetThreadLocals(false);
708         testNoInheritedThreadLocals(builder);
709         builder.inheritInheritableThreadLocals(false);
710         testNoInheritedThreadLocals(builder);
711         builder.inheritInheritableThreadLocals(true);
712         testNoInheritedThreadLocals(builder);
713 
714         // thread locals allowed
715         builder.allowSetThreadLocals(true);
716         builder.inheritInheritableThreadLocals(false);
717         testNoInheritedThreadLocals(builder);
718         builder.inheritInheritableThreadLocals(true);
719         testInheritedThreadLocals(builder);
720     }
721 
722     @Test
723     void testInheritedThreadLocals4() throws Exception {
724         Thread.Builder builder = Thread.ofVirtual();
725 
726         // thread locals not allowed
727         builder.allowSetThreadLocals(false);
728         testNoInheritedThreadLocals(builder);
729         builder.inheritInheritableThreadLocals(false);
730         testNoInheritedThreadLocals(builder);
731         builder.inheritInheritableThreadLocals(true);
732         testNoInheritedThreadLocals(builder);
733 
734         // thread locals allowed
735         builder.allowSetThreadLocals(true);
736         builder.inheritInheritableThreadLocals(false);
737         testNoInheritedThreadLocals(builder);
738         builder.inheritInheritableThreadLocals(true);
739         testInheritedThreadLocals(builder);
740     }
741 
742     /**
743      * Tests a builder creates threads that inherit the context class loader.
744      */
745     private void testInheritContextClassLoader(Thread.Builder builder) throws Exception {
746         ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
747         try {
748             ClassLoader loader = new ClassLoader() { };
749             Thread.currentThread().setContextClassLoader(loader);
750 
751             var ref = new AtomicReference<ClassLoader>();
752             Runnable task = () -> {
753                 ref.set(Thread.currentThread().getContextClassLoader());
754             };
755 
756             // unstarted
757             Thread thread1 = builder.unstarted(task);
758             assertTrue(thread1.getContextClassLoader() == loader);
759 
760             // started
761             ref.set(null);
762             thread1.start();
763             thread1.join();
764             assertTrue(ref.get() == loader);
765 
766             // factory
767             Thread thread2 = builder.factory().newThread(task);
768             assertTrue(thread2.getContextClassLoader() == loader);
769 
770             // started
771             ref.set(null);
772             thread2.start();
773             thread2.join();
774             assertTrue(ref.get() == loader);
775 
776         } finally {
777             Thread.currentThread().setContextClassLoader(savedCCL);
778         }
779     }
780 
781     /**
782      * Tests a builder creates threads does not inherit the context class loader.
783      */
784     private void testNoInheritContextClassLoader(Thread.Builder builder) throws Exception {
785         ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
786         try {
787             Thread.currentThread().setContextClassLoader( new ClassLoader() { });
788 
789             ClassLoader scl = ClassLoader.getSystemClassLoader();
790 
791             var ref = new AtomicReference<ClassLoader>();
792             Runnable task = () -> {
793                 ref.set(Thread.currentThread().getContextClassLoader());
794             };
795 
796             // unstarted
797             Thread thread1 = builder.unstarted(task);
798             assertTrue(thread1.getContextClassLoader() == scl);
799 
800             // started
801             ref.set(null);
802             thread1.start();
803             thread1.join();
804             assertTrue(ref.get() == scl);
805 
806             // factory
807             Thread thread2 = builder.factory().newThread(task);
808             assertTrue(thread2.getContextClassLoader() == scl);
809 
810             // started
811             ref.set(null);
812             thread2.start();
813             thread2.join();
814             assertTrue(ref.get() == scl);
815 
816         } finally {
817             Thread.currentThread().setContextClassLoader(savedCCL);
818         }
819     }
820 
821     /**
822      * Test Thread.Builder creating threads that inherit or do not inherit
823      * the thread context class loader.
824      */
825     @Test
826     void testContextClassLoader1() throws Exception {
827         Thread.Builder builder = Thread.ofPlatform();
828         testInheritContextClassLoader(builder); // default
829 
830         // do no inherit
831         builder.inheritInheritableThreadLocals(false);
832         testNoInheritContextClassLoader(builder);
833 
834         // inherit
835         builder.inheritInheritableThreadLocals(true);
836         testInheritContextClassLoader(builder);
837     }
838 
839     @Test
840     void testContextClassLoader2() throws Exception {
841         Thread.Builder builder = Thread.ofVirtual();
842         testInheritContextClassLoader(builder); // default
843 
844         // do no inherit
845         builder.inheritInheritableThreadLocals(false);
846         testNoInheritContextClassLoader(builder);
847 
848         // inherit
849         builder.inheritInheritableThreadLocals(true);
850         testInheritContextClassLoader(builder);
851     }
852 
853     @Test
854     void testContextClassLoader3() throws Exception {
855         Thread.Builder builder = Thread.ofPlatform();
856 
857         // thread locals not allowed
858         builder.allowSetThreadLocals(false);
859         testNoInheritContextClassLoader(builder);
860         builder.inheritInheritableThreadLocals(false);
861         testNoInheritContextClassLoader(builder);
862         builder.inheritInheritableThreadLocals(true);
863         testNoInheritContextClassLoader(builder);
864 
865         // thread locals allowed
866         builder.allowSetThreadLocals(true);
867         builder.inheritInheritableThreadLocals(false);
868         testNoInheritContextClassLoader(builder);
869         builder.inheritInheritableThreadLocals(true);
870         testInheritContextClassLoader(builder);
871     }
872 
873     @Test
874     void testContextClassLoader4() throws Exception {
875         Thread.Builder builder = Thread.ofVirtual();
876 
877         // thread locals not allowed
878         builder.allowSetThreadLocals(false);
879         testNoInheritContextClassLoader(builder);
880         builder.inheritInheritableThreadLocals(false);
881         testNoInheritContextClassLoader(builder);
882         builder.inheritInheritableThreadLocals(true);
883         testNoInheritContextClassLoader(builder);
884 
885         // thread locals allowed
886         builder.allowSetThreadLocals(true);
887         builder.inheritInheritableThreadLocals(false);
888         testNoInheritContextClassLoader(builder);
889         builder.inheritInheritableThreadLocals(true);
890         testInheritContextClassLoader(builder);
891     }
892 
893     /**
894      * Test NullPointerException.
895      */
896     @Test
897     void testNulls1() {
898         Thread.Builder.OfPlatform builder = Thread.ofPlatform();
899         assertThrows(NullPointerException.class, () -> builder.group(null));
900         assertThrows(NullPointerException.class, () -> builder.name(null));
901         assertThrows(NullPointerException.class, () -> builder.name(null, 0));
902         assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
903         assertThrows(NullPointerException.class, () -> builder.unstarted(null));
904         assertThrows(NullPointerException.class, () -> builder.start(null));
905     }
906 
907     @Test
908     void testNulls2() {
909         Thread.Builder builder = Thread.ofVirtual();
910         assertThrows(NullPointerException.class, () -> builder.name(null));
911         assertThrows(NullPointerException.class, () -> builder.name(null, 0));
912         assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
913         assertThrows(NullPointerException.class, () -> builder.unstarted(null));
914         assertThrows(NullPointerException.class, () -> builder.start(null));
915     }
916 }