1 /* <lambda>null2 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 @file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 6 7 package kotlinx.coroutines 8 9 import kotlinx.coroutines.channels.* 10 import kotlin.coroutines.* 11 import kotlin.test.* 12 13 /** 14 * Systematically tests that various builders cancel parent on failure. 15 */ 16 class ParentCancellationTest : TestBase() { 17 @Test 18 fun testJobChild() = runTest { 19 testParentCancellation(expectUnhandled = false) { fail -> 20 val child = Job(coroutineContext[Job]) 21 CoroutineScope(coroutineContext + child).fail() 22 } 23 } 24 25 @Test 26 fun testSupervisorJobChild() = runTest { 27 testParentCancellation(expectParentActive = true, expectUnhandled = true) { fail -> 28 val child = SupervisorJob(coroutineContext[Job]) 29 CoroutineScope(coroutineContext + child).fail() 30 } 31 } 32 33 @Test 34 fun testCompletableDeferredChild() = runTest { 35 testParentCancellation { fail -> 36 val child = CompletableDeferred<Unit>(coroutineContext[Job]) 37 CoroutineScope(coroutineContext + child).fail() 38 } 39 } 40 41 @Test 42 fun testLaunchChild() = runTest { 43 testParentCancellation(runsInScopeContext = true) { fail -> 44 launch { fail() } 45 } 46 } 47 48 @Test 49 fun testAsyncChild() = runTest { 50 testParentCancellation(runsInScopeContext = true) { fail -> 51 async { fail() } 52 } 53 } 54 55 @Test 56 fun testProduceChild() = runTest { 57 testParentCancellation(runsInScopeContext = true) { fail -> 58 produce<Unit> { fail() } 59 } 60 } 61 62 @Test 63 fun testBroadcastChild() = runTest { 64 testParentCancellation(runsInScopeContext = true) { fail -> 65 broadcast<Unit> { fail() }.openSubscription() 66 } 67 } 68 69 @Test 70 fun testSupervisorChild() = runTest { 71 testParentCancellation(expectParentActive = true, expectUnhandled = true, runsInScopeContext = true) { fail -> 72 supervisorScope { fail() } 73 } 74 } 75 76 @Test 77 fun testCoroutineScopeChild() = runTest { 78 testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail -> 79 coroutineScope { fail() } 80 } 81 } 82 83 @Test 84 fun testWithContextChild() = runTest { 85 testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail -> 86 withContext(CoroutineName("fail")) { fail() } 87 } 88 } 89 90 @Test 91 fun testWithTimeoutChild() = runTest { 92 testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail -> 93 withTimeout(1000) { fail() } 94 } 95 } 96 97 private suspend fun CoroutineScope.testParentCancellation( 98 expectParentActive: Boolean = false, 99 expectRethrows: Boolean = false, 100 expectUnhandled: Boolean = false, 101 runsInScopeContext: Boolean = false, 102 child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit 103 ) { 104 testWithException( 105 expectParentActive, 106 expectRethrows, 107 expectUnhandled, 108 runsInScopeContext, 109 TestException(), 110 child 111 ) 112 testWithException( 113 true, 114 expectRethrows, 115 false, 116 runsInScopeContext, 117 CancellationException("Test"), 118 child 119 ) 120 } 121 122 private suspend fun CoroutineScope.testWithException( 123 expectParentActive: Boolean, 124 expectRethrows: Boolean, 125 expectUnhandled: Boolean, 126 runsInScopeContext: Boolean, 127 throwException: Throwable, 128 child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit 129 ) { 130 reset() 131 expect(1) 132 val parent = CompletableDeferred<Unit>() // parent that handles exception (!) 133 val scope = CoroutineScope(coroutineContext + parent) 134 try { 135 scope.child { 136 // launch failing grandchild 137 var unhandledException: Throwable? = null 138 val handler = CoroutineExceptionHandler { _, e -> unhandledException = e } 139 val grandchild = launch(handler) { 140 throw throwException 141 } 142 grandchild.join() 143 when { 144 !expectParentActive && runsInScopeContext -> expectUnreached() 145 expectUnhandled -> assertSame(throwException, unhandledException) 146 else -> assertNull(unhandledException) 147 } 148 } 149 if (expectRethrows && throwException !is CancellationException) { 150 expectUnreached() 151 } else { 152 expect(2) 153 } 154 } catch (e: Throwable) { 155 if (expectRethrows) { 156 expect(2) 157 assertSame(throwException, e) 158 } else { 159 expectUnreached() 160 } 161 } 162 if (expectParentActive) { 163 assertTrue(parent.isActive) 164 } else { 165 parent.join() 166 assertFalse(parent.isActive) 167 assertTrue(parent.isCancelled) 168 } 169 finish(3) 170 } 171 }