/*
 * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/**
 * @test
 * @summary Unit test for Thread.Builder
 * @run junit BuilderTest
 */

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;

class BuilderTest {

    /**
     * Test Thread.ofPlatform to create platform threads.
     */
    @Test
    void testPlatformThread() throws Exception {
        Thread parent = Thread.currentThread();
        Thread.Builder.OfPlatform builder = Thread.ofPlatform();

        // unstarted
        AtomicBoolean done1 = new AtomicBoolean();
        Thread thread1 = builder.unstarted(() -> done1.set(true));
        assertFalse(thread1.isVirtual());
        assertTrue(thread1.getState() == Thread.State.NEW);
        assertFalse(thread1.getName().isEmpty());
        assertTrue(thread1.getThreadGroup() == parent.getThreadGroup());
        assertTrue(thread1.isDaemon() == parent.isDaemon());
        assertTrue(thread1.getPriority() == parent.getPriority());
        assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
        thread1.start();
        thread1.join();
        assertTrue(done1.get());

        // start
        AtomicBoolean done2 = new AtomicBoolean();
        Thread thread2 = builder.start(() -> done2.set(true));
        assertFalse(thread2.isVirtual());
        assertTrue(thread2.getState() != Thread.State.NEW);
        assertFalse(thread2.getName().isEmpty());
        ThreadGroup group2 = thread2.getThreadGroup();
        assertTrue(group2 == parent.getThreadGroup() || group2 == null);
        assertTrue(thread2.isDaemon() == parent.isDaemon());
        assertTrue(thread2.getPriority() == parent.getPriority());
        assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
        thread2.join();
        assertTrue(done2.get());

        // factory
        AtomicBoolean done3 = new AtomicBoolean();
        Thread thread3 = builder.factory().newThread(() -> done3.set(true));
        assertFalse(thread3.isVirtual());
        assertTrue(thread3.getState() == Thread.State.NEW);
        assertFalse(thread3.getName().isEmpty());
        assertTrue(thread3.getThreadGroup() == parent.getThreadGroup());
        assertTrue(thread3.isDaemon() == parent.isDaemon());
        assertTrue(thread3.getPriority() == parent.getPriority());
        assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
        thread3.start();
        thread3.join();
        assertTrue(done3.get());
    }

    /**
     * Test Thread.ofVirtual to create virtual threads.
     */
    @Test
    void testVirtualThread() throws Exception {
        Thread parent = Thread.currentThread();
        Thread.Builder.OfVirtual builder = Thread.ofVirtual();

        // unstarted
        AtomicBoolean done1 = new AtomicBoolean();
        Thread thread1 = builder.unstarted(() -> done1.set(true));
        assertTrue(thread1.isVirtual());
        assertTrue(thread1.getState() == Thread.State.NEW);
        assertTrue(thread1.getName().isEmpty());
        assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
        assertTrue(thread1.isDaemon());
        assertTrue(thread1.getPriority() == Thread.NORM_PRIORITY);
        thread1.start();
        thread1.join();
        assertTrue(done1.get());

        // start
        AtomicBoolean done2 = new AtomicBoolean();
        Thread thread2 = builder.start(() -> done2.set(true));
        assertTrue(thread2.isVirtual());
        assertTrue(thread2.getState() != Thread.State.NEW);
        assertTrue(thread2.getName().isEmpty());
        assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
        assertTrue(thread2.isDaemon());
        assertTrue(thread2.getPriority() == Thread.NORM_PRIORITY);
        thread2.join();
        assertTrue(done2.get());

        // factory
        AtomicBoolean done3 = new AtomicBoolean();
        Thread thread3 = builder.factory().newThread(() -> done3.set(true));
        assertTrue(thread3.isVirtual());
        assertTrue(thread3.getState() == Thread.State.NEW);
        assertTrue(thread3.getName().isEmpty());
        assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
        assertTrue(thread3.isDaemon());
        assertTrue(thread3.getPriority() == Thread.NORM_PRIORITY);
        thread3.start();
        thread3.join();
        assertTrue(done3.get());
    }

    /**
     * Test Thread.Builder.name.
     */
    @Test
    void testName1() {
        Thread.Builder builder = Thread.ofPlatform().name("duke");

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.getName().equals("duke"));
        assertTrue(thread2.getName().equals("duke"));
        assertTrue(thread3.getName().equals("duke"));
    }

    @Test
    void testName2() {
        Thread.Builder builder = Thread.ofVirtual().name("duke");

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.getName().equals("duke"));
        assertTrue(thread2.getName().equals("duke"));
        assertTrue(thread3.getName().equals("duke"));
    }

    @Test
    void testName3() {
        Thread.Builder builder = Thread.ofPlatform().name("duke-", 100);

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.unstarted(() -> { });
        Thread thread3 = builder.unstarted(() -> { });

        assertTrue(thread1.getName().equals("duke-100"));
        assertTrue(thread2.getName().equals("duke-101"));
        assertTrue(thread3.getName().equals("duke-102"));

        ThreadFactory factory = builder.factory();
        Thread thread4 = factory.newThread(() -> { });
        Thread thread5 = factory.newThread(() -> { });
        Thread thread6 = factory.newThread(() -> { });

        assertTrue(thread4.getName().equals("duke-103"));
        assertTrue(thread5.getName().equals("duke-104"));
        assertTrue(thread6.getName().equals("duke-105"));
    }

    @Test
    void testName4() {
        Thread.Builder builder = Thread.ofVirtual().name("duke-", 100);

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.unstarted(() -> { });
        Thread thread3 = builder.unstarted(() -> { });

        assertTrue(thread1.getName().equals("duke-100"));
        assertTrue(thread2.getName().equals("duke-101"));
        assertTrue(thread3.getName().equals("duke-102"));

        ThreadFactory factory = builder.factory();
        Thread thread4 = factory.newThread(() -> { });
        Thread thread5 = factory.newThread(() -> { });
        Thread thread6 = factory.newThread(() -> { });

        assertTrue(thread4.getName().equals("duke-103"));
        assertTrue(thread5.getName().equals("duke-104"));
        assertTrue(thread6.getName().equals("duke-105"));
    }

    /**
     * Test Thread.Builder.OfPlatform.group.
     */
    @Test
    void testThreadGroup1() {
        ThreadGroup group = new ThreadGroup("groupies");
        Thread.Builder builder = Thread.ofPlatform().group(group);

        Thread thread1 = builder.unstarted(() -> { });

        AtomicBoolean done = new AtomicBoolean();
        Thread thread2 = builder.start(() -> {
            while (!done.get()) {
                LockSupport.park();
            }
        });

        Thread thread3 = builder.factory().newThread(() -> { });

        try {
            assertTrue(thread1.getThreadGroup() == group);
            assertTrue(thread2.getThreadGroup() == group);
            assertTrue(thread3.getThreadGroup() == group);
        } finally {
            done.set(true);
            LockSupport.unpark(thread2);
        }
    }

    @Test
    void testThreadGroup2() {
        ThreadGroup vgroup = Thread.ofVirtual().unstarted(() -> { }).getThreadGroup();
        assertEquals("VirtualThreads", vgroup.getName());

        Thread thread1 = Thread.ofVirtual().unstarted(() -> { });
        Thread thread2 = Thread.ofVirtual().start(LockSupport::park);
        Thread thread3 = Thread.ofVirtual().factory().newThread(() -> { });

        try {
            assertTrue(thread1.getThreadGroup() == vgroup);
            assertTrue(thread2.getThreadGroup() == vgroup);
            assertTrue(thread3.getThreadGroup() == vgroup);
        } finally {
            LockSupport.unpark(thread2);
        }
    }

    /**
     * Test Thread.Builder.OfPlatform.priority.
     */
    @Test
    void testPriority1() {
        int priority = Thread.currentThread().getPriority();

        Thread.Builder builder = Thread.ofPlatform();
        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.getPriority() == priority);
        assertTrue(thread2.getPriority() == priority);
        assertTrue(thread3.getPriority() == priority);
    }

    @Test
    void testPriority2() {
        int priority = Thread.MIN_PRIORITY;

        Thread.Builder builder = Thread.ofPlatform().priority(priority);
        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.getPriority() == priority);
        assertTrue(thread2.getPriority() == priority);
        assertTrue(thread3.getPriority() == priority);
    }

    @Test
    void testPriority3() {
        Thread currentThread = Thread.currentThread();
        assumeFalse(currentThread.isVirtual(), "Main thread is a virtual thread");

        int maxPriority = currentThread.getThreadGroup().getMaxPriority();
        int priority = Math.min(maxPriority + 1, Thread.MAX_PRIORITY);

        Thread.Builder builder = Thread.ofPlatform().priority(priority);
        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.getPriority() == priority);
        assertTrue(thread2.getPriority() == priority);
        assertTrue(thread3.getPriority() == priority);
    }

    @Test
    void testPriority4() {
        var builder = Thread.ofPlatform();
        assertThrows(IllegalArgumentException.class,
                     () -> builder.priority(Thread.MIN_PRIORITY - 1));
    }

    @Test
    void testPriority5() {
        var builder = Thread.ofPlatform();
        assertThrows(IllegalArgumentException.class,
                     () -> builder.priority(Thread.MAX_PRIORITY + 1));
    }

    /**
     * Test Thread.Builder.OfPlatform.daemon.
     */
    @Test
    void testDaemon1() {
        Thread.Builder builder = Thread.ofPlatform().daemon(false);

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertFalse(thread1.isDaemon());
        assertFalse(thread2.isDaemon());
        assertFalse(thread3.isDaemon());
    }

    @Test
    void testDaemon2() {
        Thread.Builder builder = Thread.ofPlatform().daemon(true);

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.isDaemon());
        assertTrue(thread2.isDaemon());
        assertTrue(thread3.isDaemon());
    }

    @Test
    void testDaemon3() {
        Thread.Builder builder = Thread.ofPlatform().daemon();

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        assertTrue(thread1.isDaemon());
        assertTrue(thread2.isDaemon());
        assertTrue(thread3.isDaemon());
    }

    @Test
    void testDaemon4() {
        Thread.Builder builder = Thread.ofPlatform();

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        // daemon status should be inherited
        boolean d = Thread.currentThread().isDaemon();
        assertTrue(thread1.isDaemon() == d);
        assertTrue(thread2.isDaemon() == d);
        assertTrue(thread3.isDaemon() == d);
    }

    /**
     * Test Thread.ofVirtual creates daemon threads.
     */
    @Test
    void testDaemon5() {
        Thread.Builder builder = Thread.ofVirtual();

        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });

        // daemon status should always be true
        assertTrue(thread1.isDaemon());
        assertTrue(thread2.isDaemon());
        assertTrue(thread3.isDaemon());
    }

    /**
     * Test Thread.Builder.OfPlatform.stackSize.
     */
    @Test
    void testStackSize1() {
        Thread.Builder builder = Thread.ofPlatform().stackSize(1024*1024);
        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });
    }

    @Test
    void testStackSize2() {
        Thread.Builder builder = Thread.ofPlatform().stackSize(0);
        Thread thread1 = builder.unstarted(() -> { });
        Thread thread2 = builder.start(() -> { });
        Thread thread3 = builder.factory().newThread(() -> { });
    }

    @Test
    void testStackSize3() {
        var builder = Thread.ofPlatform();
        assertThrows(IllegalArgumentException.class, () -> builder.stackSize(-1));
    }

    /**
     * Test Thread.Builder.uncaughtExceptionHandler.
     */
    @Test
    void testUncaughtExceptionHandler1() throws Exception {
        class FooException extends RuntimeException { }
        AtomicReference<Thread> threadRef = new AtomicReference<>();
        AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
        Thread thread = Thread.ofPlatform()
                .uncaughtExceptionHandler((t, e) -> {
                    assertTrue(t == Thread.currentThread());
                    threadRef.set(t);
                    exceptionRef.set(e);
                })
                .start(() -> { throw new FooException(); });
        thread.join();
        assertTrue(threadRef.get() == thread);
        assertTrue(exceptionRef.get() instanceof FooException);
    }

    @Test
    void testUncaughtExceptionHandler2() throws Exception {
        class FooException extends RuntimeException { }
        AtomicReference<Thread> threadRef = new AtomicReference<>();
        AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
        Thread thread = Thread.ofVirtual()
                .uncaughtExceptionHandler((t, e) -> {
                    assertTrue(t == Thread.currentThread());
                    threadRef.set(t);
                    exceptionRef.set(e);
                })
                .start(() -> { throw new FooException(); });
        thread.join();
        assertTrue(threadRef.get() == thread);
        assertTrue(exceptionRef.get() instanceof FooException);
    }

    @Test
    void testUncaughtExceptionHandler3() throws Exception {
        class FooException extends RuntimeException { }
        AtomicReference<Thread> threadRef = new AtomicReference<>();
        AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
        Thread thread = Thread.ofPlatform()
                .uncaughtExceptionHandler((t, e) -> {
                    assertTrue(t == Thread.currentThread());
                    threadRef.set(t);
                    exceptionRef.set(e);
                })
                .factory()
                .newThread(() -> { throw new FooException(); });
        thread.start();
        thread.join();
        assertTrue(threadRef.get() == thread);
        assertTrue(exceptionRef.get() instanceof FooException);
    }

    @Test
    void testUncaughtExceptionHandler4() throws Exception {
        class FooException extends RuntimeException { }
        AtomicReference<Thread> threadRef = new AtomicReference<>();
        AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
        Thread thread = Thread.ofPlatform()
                .uncaughtExceptionHandler((t, e) -> {
                    assertTrue(t == Thread.currentThread());
                    threadRef.set(t);
                    exceptionRef.set(e);
                })
                .factory()
                .newThread(() -> { throw new FooException(); });
        thread.start();
        thread.join();
        assertTrue(threadRef.get() == thread);
        assertTrue(exceptionRef.get() instanceof FooException);
    }

    static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
    static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();

    /**
     * Tests that a builder creates threads that support thread locals
     */
    private void testThreadLocals(Thread.Builder builder) throws Exception {
        AtomicBoolean done = new AtomicBoolean();
        Runnable task = () -> {
            Object value = new Object();
            LOCAL.set(value);
            assertTrue(LOCAL.get() == value);
            done.set(true);
        };

        done.set(false);
        Thread thread1 = builder.unstarted(task);
        thread1.start();
        thread1.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread2 = builder.start(task);
        thread2.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread3 = builder.factory().newThread(task);
        thread3.start();
        thread3.join();
        assertTrue(done.get());
    }

    /**
     * Tests that a builder creates threads that do not support thread locals
     */
    private void testNoThreadLocals(Thread.Builder builder) throws Exception {
        AtomicBoolean done = new AtomicBoolean();
        Runnable task = () -> {
            try {
                LOCAL.set(new Object());
            } catch (UnsupportedOperationException expected) {
                done.set(true);
            }
        };

        done.set(false);
        Thread thread1 = builder.unstarted(task);
        thread1.start();
        thread1.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread2 = builder.start(task);
        thread2.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread3 = builder.factory().newThread(task);
        thread3.start();
        thread3.join();
        assertTrue(done.get());
    }

    /**
     * Test Thread.Builder creates threads that allow thread locals.
     */
    @Test
    void testThreadLocals1() throws Exception {
        Thread.Builder builder = Thread.ofPlatform();
        testThreadLocals(builder);
    }

    @Test
    void testThreadLocals2() throws Exception {
        Thread.Builder builder = Thread.ofVirtual();
        testThreadLocals(builder);
    }

    /**
     * Tests that a builder creates threads that inherits the initial values of
     * inheritable thread locals.
     */
    private void testInheritedThreadLocals(Thread.Builder builder) throws Exception {
        Object value = new Object();
        INHERITED_LOCAL.set(value);

        AtomicBoolean done = new AtomicBoolean();
        Runnable task = () -> {
            assertTrue(INHERITED_LOCAL.get() == value);
            done.set(true);
        };

        done.set(false);
        Thread thread1 = builder.unstarted(task);
        thread1.start();
        thread1.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread2 = builder.start(task);
        thread2.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread3 = builder.factory().newThread(task);
        thread3.start();
        thread3.join();
        assertTrue(done.get());
    }

    /**
     * Tests that a builder creates threads that do not inherit the initial values
     * of inheritable thread locals.
     */
    private void testNoInheritedThreadLocals(Thread.Builder builder) throws Exception {
        Object value = new Object();
        INHERITED_LOCAL.set(value);

        AtomicBoolean done = new AtomicBoolean();
        Runnable task = () -> {
            assertNull(INHERITED_LOCAL.get());
            done.set(true);
        };

        done.set(false);
        Thread thread1 = builder.unstarted(task);
        thread1.start();
        thread1.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread2 = builder.start(task);
        thread2.join();
        assertTrue(done.get());

        done.set(false);
        Thread thread3 = builder.factory().newThread(task);
        thread3.start();
        thread3.join();
        assertTrue(done.get());
    }

    /**
     * Test Thread.Builder creating threads that inherit or do not inherit
     * the initial values of inheritable thread locals.
     */
    @Test
    void testInheritedThreadLocals1() throws Exception {
        Thread.Builder builder = Thread.ofPlatform();
        testInheritedThreadLocals(builder); // default

        // do no inherit
        builder.inheritInheritableThreadLocals(false);
        testNoInheritedThreadLocals(builder);

        // inherit
        builder.inheritInheritableThreadLocals(true);
        testInheritedThreadLocals(builder);
    }

    @Test
    void testInheritedThreadLocals2() throws Exception {
        Thread.Builder builder = Thread.ofVirtual();
        testInheritedThreadLocals(builder); // default

        // do no inherit
        builder.inheritInheritableThreadLocals(false);
        testNoInheritedThreadLocals(builder);

        // inherit
        builder.inheritInheritableThreadLocals(true);
        testInheritedThreadLocals(builder);
    }

    /**
     * Tests a builder creates threads that inherit the context class loader.
     */
    private void testInheritContextClassLoader(Thread.Builder builder) throws Exception {
        ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader loader = new ClassLoader() { };
            Thread.currentThread().setContextClassLoader(loader);

            var ref = new AtomicReference<ClassLoader>();
            Runnable task = () -> {
                ref.set(Thread.currentThread().getContextClassLoader());
            };

            // unstarted
            Thread thread1 = builder.unstarted(task);
            assertTrue(thread1.getContextClassLoader() == loader);

            // started
            ref.set(null);
            thread1.start();
            thread1.join();
            assertTrue(ref.get() == loader);

            // factory
            Thread thread2 = builder.factory().newThread(task);
            assertTrue(thread2.getContextClassLoader() == loader);

            // started
            ref.set(null);
            thread2.start();
            thread2.join();
            assertTrue(ref.get() == loader);

        } finally {
            Thread.currentThread().setContextClassLoader(savedCCL);
        }
    }

    /**
     * Tests a builder creates threads does not inherit the context class loader.
     */
    private void testNoInheritContextClassLoader(Thread.Builder builder) throws Exception {
        ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader( new ClassLoader() { });

            ClassLoader scl = ClassLoader.getSystemClassLoader();

            var ref = new AtomicReference<ClassLoader>();
            Runnable task = () -> {
                ref.set(Thread.currentThread().getContextClassLoader());
            };

            // unstarted
            Thread thread1 = builder.unstarted(task);
            assertTrue(thread1.getContextClassLoader() == scl);

            // started
            ref.set(null);
            thread1.start();
            thread1.join();
            assertTrue(ref.get() == scl);

            // factory
            Thread thread2 = builder.factory().newThread(task);
            assertTrue(thread2.getContextClassLoader() == scl);

            // started
            ref.set(null);
            thread2.start();
            thread2.join();
            assertTrue(ref.get() == scl);

        } finally {
            Thread.currentThread().setContextClassLoader(savedCCL);
        }
    }

    /**
     * Test Thread.Builder creating threads that inherit or do not inherit
     * the thread context class loader.
     */
    @Test
    void testContextClassLoader1() throws Exception {
        Thread.Builder builder = Thread.ofPlatform();
        testInheritContextClassLoader(builder); // default

        // do no inherit
        builder.inheritInheritableThreadLocals(false);
        testNoInheritContextClassLoader(builder);

        // inherit
        builder.inheritInheritableThreadLocals(true);
        testInheritContextClassLoader(builder);
    }

    @Test
    void testContextClassLoader2() throws Exception {
        Thread.Builder builder = Thread.ofVirtual();
        testInheritContextClassLoader(builder); // default

        // do no inherit
        builder.inheritInheritableThreadLocals(false);
        testNoInheritContextClassLoader(builder);

        // inherit
        builder.inheritInheritableThreadLocals(true);
        testInheritContextClassLoader(builder);
    }

    /**
     * Test NullPointerException.
     */
    @Test
    void testNulls1() {
        Thread.Builder.OfPlatform builder = Thread.ofPlatform();
        assertThrows(NullPointerException.class, () -> builder.group(null));
        assertThrows(NullPointerException.class, () -> builder.name(null));
        assertThrows(NullPointerException.class, () -> builder.name(null, 0));
        assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
        assertThrows(NullPointerException.class, () -> builder.unstarted(null));
        assertThrows(NullPointerException.class, () -> builder.start(null));
    }

    @Test
    void testNulls2() {
        Thread.Builder builder = Thread.ofVirtual();
        assertThrows(NullPointerException.class, () -> builder.name(null));
        assertThrows(NullPointerException.class, () -> builder.name(null, 0));
        assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
        assertThrows(NullPointerException.class, () -> builder.unstarted(null));
        assertThrows(NullPointerException.class, () -> builder.start(null));
    }
}