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

Code before:
     * is called.
     *
     * @throws Throwable the first uncaught exception, if there are any uncaught exceptions.
     * @throws UncompletedCoroutinesError if any pending tasks are active.
     * @throws IllegalStateException if called more than once.
     */
    @ExperimentalCoroutinesApi
    public fun cleanupTestCoroutines()

    /**
     * The delay-skipping scheduler used by the test dispatchers running the code in this scope.
     */
    @ExperimentalCoroutinesApi
    public val testScheduler: TestCoroutineScheduler
        get() = coroutineContext[TestCoroutineScheduler]
            ?: throw UnsupportedOperationException("This scope does not have a TestCoroutineScheduler linked to it")

    /**
     * Reports an exception so that it is thrown on [cleanupTestCoroutines].
     *
     * If several exceptions are reported, only the first one will be thrown, and the other ones will be suppressed by
     * it.
     *
     * @throws IllegalStateException with the [Throwable.cause] set to [throwable] if [cleanupTestCoroutines] was
     * already called.
     */
    @ExperimentalCoroutinesApi
    public fun reportException(throwable: Throwable)
}

private class TestCoroutineScopeImpl(
    override val coroutineContext: CoroutineContext,
    private val ownJob: CompletableJob?
): TestCoroutineScope
{
    private val lock = SynchronizedObject()
    private var exceptions = mutableListOf<Throwable>()
    private var cleanedUp = false

    override fun reportException(throwable: Throwable) {
        synchronized(lock) {
            if (cleanedUp)
                throw ExceptionReportAfterCleanup(throwable)
            exceptions.add(throwable)
        }
    }

    override val testScheduler: TestCoroutineScheduler
        get() = coroutineContext[TestCoroutineScheduler]!!

    /** These jobs existed before the coroutine scope was used, so it's alright if they don't get cancelled. */

Review comment (security):
Could you please elaborate on the point of this method?
I believe it can be useful as the implementation detail, but I'm not sure how it is expected to be used by test authors. It looks like a `Job.completeExceptionally` (modulo suppression) for an external exception report (what for?) or like throwing an exception within a test itself.

I may be missing something, could you please show some compelling use-case?

Code after:
     *
     * @throws Throwable the first uncaught exception, if there are any uncaught exceptions.
     * @throws AssertionError if any pending tasks are active.
     * @throws IllegalStateException if called more than once.
     */
    @ExperimentalCoroutinesApi
    public fun cleanupTestCoroutines()

    /**
     * The delay-skipping scheduler used by the test dispatchers running the code in this scope.
     */
    @ExperimentalCoroutinesApi
    public val testScheduler: TestCoroutineScheduler
}

private class TestCoroutineScopeImpl(
    override val coroutineContext: CoroutineContext
) : TestCoroutineScope {
    private val lock = SynchronizedObject()
    private var exceptions = mutableListOf<Throwable>()
    private var cleanedUp = false

    /**
     * Reports an exception so that it is thrown on [cleanupTestCoroutines].
     *
     * If several exceptions are reported, only the first one will be thrown, and the other ones will be suppressed by
     * it.
     *
     * Returns `false` if [cleanupTestCoroutines] was already called.
     */
    fun reportException(throwable: Throwable): Boolean =
        synchronized(lock) {
            if (cleanedUp) {
                false
            } else {
                exceptions.add(throwable)
                true
            }
        }

    override val testScheduler: TestCoroutineScheduler
        get() = coroutineContext[TestCoroutineScheduler]!!

    /** These jobs existed before the coroutine scope was used, so it's alright if they don't get cancelled. */
    private val initialJobs = coroutineContext.activeJobs()

    override fun cleanupTestCoroutines() {
        val delayController = coroutineContext.delayController
        val hasUnfinishedJobs = if (delayController != null) {
            try {
                delayController.cleanupTestCoroutines()