aboutsummaryrefslogtreecommitdiffstats
path: root/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'kotlinx-coroutines-core/common/test/ParentCancellationTest.kt')
-rw-r--r--kotlinx-coroutines-core/common/test/ParentCancellationTest.kt171
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