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