diff options
Diffstat (limited to 'kotlinx-coroutines-core/common/test/ParentCancellationTest.kt')
-rw-r--r-- | kotlinx-coroutines-core/common/test/ParentCancellationTest.kt | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt b/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt new file mode 100644 index 00000000..96c5cf3f --- /dev/null +++ b/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt @@ -0,0 +1,171 @@ +/* + * 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<Unit>(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<Unit> { fail() } + } + } + + @Test + fun testBroadcastChild() = runTest { + testParentCancellation(runsInScopeContext = true) { fail -> + broadcast<Unit> { 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<Unit>() // 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) + } +}
\ No newline at end of file |