All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonTest.ParentCancellationTest.kt Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
/*
 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913

package kotlinx.coroutines

import kotlinx.coroutines.channels.*
import kotlin.coroutines.*
import kotlin.test.*

/**
 * Systematically tests that various builders cancel parent on failure.
 */
class ParentCancellationTest : TestBase() {
    @Test
    fun testJobChild() = runTest {
        testParentCancellation(expectUnhandled = false) { fail ->
            val child = Job(coroutineContext[Job])
            CoroutineScope(coroutineContext + child).fail()
        }
    }

    @Test
    fun testSupervisorJobChild() = runTest {
        testParentCancellation(expectParentActive = true, expectUnhandled = true) { fail ->
            val child = SupervisorJob(coroutineContext[Job])
            CoroutineScope(coroutineContext + child).fail()
        }
    }

    @Test
    fun testCompletableDeferredChild() = runTest {
        testParentCancellation { fail ->
            val child = CompletableDeferred(coroutineContext[Job])
            CoroutineScope(coroutineContext + child).fail()
        }
    }

    @Test
    fun testLaunchChild() = runTest {
        testParentCancellation(runsInScopeContext = true) { fail ->
            launch { fail() }
        }
    }

    @Test
    fun testAsyncChild() = runTest {
        testParentCancellation(runsInScopeContext = true) { fail ->
            async { fail() }
        }
    }

    @Test
    fun testProduceChild() = runTest {
        testParentCancellation(runsInScopeContext = true) { fail ->
            produce { fail() }
        }
    }

    @Test
    fun testBroadcastChild() = runTest {
        testParentCancellation(runsInScopeContext = true) { fail ->
            broadcast { fail() }.openSubscription()
        }
    }

    @Test
    fun testSupervisorChild() = runTest {
        testParentCancellation(expectParentActive = true, expectUnhandled = true, runsInScopeContext = true) { fail ->
            supervisorScope { fail() }
        }
    }

    @Test
    fun testCoroutineScopeChild() = runTest {
        testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail ->
            coroutineScope { fail() }
        }
    }

    @Test
    fun testWithContextChild() = runTest {
        testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail ->
            withContext(CoroutineName("fail")) { fail() }
        }
    }

    @Test
    fun testWithTimeoutChild() = runTest {
        testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail ->
            withTimeout(1000) { fail() }
        }
    }

    private suspend fun CoroutineScope.testParentCancellation(
        expectParentActive: Boolean = false,
        expectRethrows: Boolean = false,
        expectUnhandled: Boolean = false,
        runsInScopeContext: Boolean = false,
        child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit
    ) {
        testWithException(
            expectParentActive,
            expectRethrows,
            expectUnhandled,
            runsInScopeContext,
            TestException(),
            child
        )
        testWithException(
            true,
            expectRethrows,
            false,
            runsInScopeContext,
            CancellationException("Test"),
            child
        )
    }

    private suspend fun CoroutineScope.testWithException(
        expectParentActive: Boolean,
        expectRethrows: Boolean,
        expectUnhandled: Boolean,
        runsInScopeContext: Boolean,
        throwException: Throwable,
        child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit
    ) {
        reset()
        expect(1)
        val parent = CompletableDeferred() // parent that handles exception (!)
        val scope = CoroutineScope(coroutineContext + parent)
        try {
            scope.child {
                // launch failing grandchild
                var unhandledException: Throwable? = null
                val handler = CoroutineExceptionHandler { _, e -> unhandledException = e }
                val grandchild = launch(handler) {
                    throw throwException
                }
                grandchild.join()
                when {
                    !expectParentActive && runsInScopeContext -> expectUnreached()
                    expectUnhandled -> assertSame(throwException, unhandledException)
                    else -> assertNull(unhandledException)
                }
            }
            if (expectRethrows && throwException !is CancellationException) {
                expectUnreached()
            } else {
                expect(2)
            }
        } catch (e: Throwable) {
            if (expectRethrows) {
                expect(2)
                assertSame(throwException, e)
            } else {
                expectUnreached()
            }
        }
        if (expectParentActive) {
            assertTrue(parent.isActive)
        } else {
            parent.join()
            assertFalse(parent.isActive)
            assertTrue(parent.isCancelled)
        }
        finish(3)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy