• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }