Repository: Kotlin/kotlinx.coroutines
File: kotlinx-coroutines-test/common/src/TestBuilders.kt (Kotlin)

Code before:
 * ### Configuration
 *
 * [context] can be used to affect the environment of the code under test. Beside just being passed to the coroutine
 * scope created for the test, [context] also can be used to change how the test is executed.
 * See the [createTestCoroutineScope] documentation for details.
 *
 * @throws IllegalArgumentException if the [context] is invalid. See the [createTestCoroutineScope] docs for details.
 */
@DelicateCoroutinesApi
public fun runTest(
    context: CoroutineContext = EmptyCoroutineContext,
    dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
    testBody: suspend TestCoroutineScope.() -> Unit
): TestResult {
    if (context[RunningInRunTest] != null)
        throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
    val testScope = createTestCoroutineScope(context + RunningInRunTest())
    val scheduler = testScope.testScheduler
    return createTestResult {
        val deferred = testScope.async {
            testScope.testBody()
        }
        var completed = false
        while (!completed) {
            while (scheduler.tryRunNextTask()) {
                if (deferred.isCompleted && deferred.getCompletionExceptionOrNull() != null && testScope.isActive) {
                    /**
                     * Here, we already know how the test will finish: it will throw
                     * [Deferred.getCompletionExceptionOrNull]. Therefore, we won't care if there are uncompleted jobs,
                     * and may as well just exit right here. However, in order to lower the surprise factor, we
                     * cancel the child jobs here and wait for them to finish instead of dropping them: there could be
                     * some cleanup procedures involved, and not having finalizers run could mean leaking resources.
                     *
                     * Another approach to take if this turns out not to be enough and some child jobs still fail is to
                     * only make at most a fixed number of [TestCoroutineScheduler.tryRunNextTask] once we detect the
                     * failure with which the test will finish. This has the downside that there is still some
                     * negligible risk of not running the finalizers.
                     */
                    testScope.cancel()
                }
            }
            if (deferred.isCompleted) {
                /* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
                   non-trivial dispatches. */
                completed = true
                continue
            }
            try {
                withTimeout(dispatchTimeoutMs) {
                    select<Unit> {
                        deferred.onAwait {

Review comment (suggestion):
Should it be fixed that way instead of leveraging structured concurrency and using regular `Job` instead of `SupervisorJob` in `createTestCoroutineScope`? 

Also, I'm not sure whether we have actual use-cases of providing a custom job there

Code after:
 * ### Configuration
 *
 * [context] can be used to affect the environment of the code under test. Beside just being passed to the coroutine
 * scope created for the test, [context] also can be used to change how the test is executed.
 * See the [TestCoroutineScope] constructor documentation for details.
 *
 * @throws IllegalArgumentException if the [context] is invalid. See the [TestCoroutineScope] constructor docs for
 * details.
 */
@DelicateCoroutinesApi
public fun runTest(
    context: CoroutineContext = EmptyCoroutineContext,
    dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
    testBody: suspend TestCoroutineScope.() -> Unit
): TestResult {
    if (context[RunningInRunTest] != null)
        throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
    val testScope = TestBodyCoroutine<Unit>(TestCoroutineScope(context + RunningInRunTest))
    val scheduler = testScope.testScheduler
    return createTestResult {
        /** TODO: moving this [AbstractCoroutine.start] call outside [createTestResult] fails on Native with
         * [TestCoroutineDispatcher], because the event loop is not started. */
        testScope.start(CoroutineStart.DEFAULT, testScope) {
            testBody()
        }
        var completed = false
        while (!completed) {
            scheduler.advanceUntilIdle()
            if (testScope.isCompleted) {
                /* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
                   non-trivial dispatches. */
                completed = true
                continue
            }
            try {
                withTimeout(dispatchTimeoutMs) {
                    select<Unit> {
                        testScope.onJoin {
                            completed = true
                        }
                        scheduler.onDispatchEvent {
                            // we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout
                        }
                    }
                }
            } catch (e: TimeoutCancellationException) {
                try {
                    testScope.cleanupTestCoroutines()
                } catch (e: UncompletedCoroutinesError) {
                    // we expect these and will instead throw a more informative exception just below.
                }