• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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