blob: 96c5cf3f4fb7f357ff66639b9d2adbddfc78f5b2 [file] [log] [blame]
/*
* 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)
}
}