• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2020 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 kotlin.test.*
10 
11 /**
12  * Test for [CancellableContinuation.resume] with `onCancellation` parameter.
13  */
14 class CancellableResumeTest : TestBase() {
15     @Test
16     fun testResumeImmediateNormally() = runTest {
17         expect(1)
18         val ok = suspendCancellableCoroutine<String> { cont ->
19             expect(2)
20             cont.invokeOnCancellation { expectUnreached() }
21             cont.resume("OK") { expectUnreached() }
22             expect(3)
23         }
24         assertEquals("OK", ok)
25         finish(4)
26     }
27 
28     @Test
29     fun testResumeImmediateAfterCancel() = runTest(
30         expected = { it is TestException }
31     ) {
32         expect(1)
33         suspendCancellableCoroutine<String> { cont ->
34             expect(2)
35             cont.invokeOnCancellation { expect(3) }
36             cont.cancel(TestException("FAIL"))
37             expect(4)
38             cont.resume("OK") { cause ->
39                 expect(5)
40                 assertTrue(cause is TestException)
41             }
42             finish(6)
43         }
44         expectUnreached()
45     }
46 
47     @Test
48     fun testResumeImmediateAfterCancelWithHandlerFailure() = runTest(
49         expected = { it is TestException },
50         unhandled = listOf(
51             { it is CompletionHandlerException && it.cause is TestException2 },
52             { it is CompletionHandlerException && it.cause is TestException3 }
53         )
54     ) {
55         expect(1)
56         suspendCancellableCoroutine<String> { cont ->
57             expect(2)
58             cont.invokeOnCancellation {
59                 expect(3)
60                 throw TestException2("FAIL") // invokeOnCancellation handler fails with exception
61             }
62             cont.cancel(TestException("FAIL"))
63             expect(4)
64             cont.resume("OK") { cause ->
65                 expect(5)
66                 assertTrue(cause is TestException)
67                 throw TestException3("FAIL") // onCancellation block fails with exception
68             }
69             finish(6)
70         }
71         expectUnreached()
72     }
73 
74     @Test
75     fun testResumeImmediateAfterIndirectCancel() = runTest(
76         expected = { it is CancellationException }
77     ) {
78         expect(1)
79         val ctx = coroutineContext
80         suspendCancellableCoroutine<String> { cont ->
81             expect(2)
82             cont.invokeOnCancellation { expect(3) }
83             ctx.cancel()
84             expect(4)
85             cont.resume("OK") {
86                 expect(5)
87             }
88             finish(6)
89         }
90         expectUnreached()
91     }
92 
93     @Test
94     fun testResumeImmediateAfterIndirectCancelWithHandlerFailure() = runTest(
95         expected = { it is CancellationException },
96         unhandled = listOf(
97             { it is CompletionHandlerException && it.cause is TestException2 },
98             { it is CompletionHandlerException && it.cause is TestException3 }
99         )
100     ) {
101         expect(1)
102         val ctx = coroutineContext
103         suspendCancellableCoroutine<String> { cont ->
104             expect(2)
105             cont.invokeOnCancellation {
106                 expect(3)
107                 throw TestException2("FAIL") // invokeOnCancellation handler fails with exception
108             }
109             ctx.cancel()
110             expect(4)
111             cont.resume("OK") {
112                 expect(5)
113                 throw TestException3("FAIL") // onCancellation block fails with exception
114             }
115             finish(6)
116         }
117         expectUnreached()
118     }
119 
120     @Test
121     fun testResumeLaterNormally() = runTest {
122         expect(1)
123         lateinit var cc: CancellableContinuation<String>
124         launch(start = CoroutineStart.UNDISPATCHED) {
125             expect(2)
126             val ok = suspendCancellableCoroutine<String> { cont ->
127                 expect(3)
128                 cont.invokeOnCancellation { expectUnreached() }
129                 cc = cont
130             }
131             assertEquals("OK", ok)
132             finish(6)
133         }
134         expect(4)
135         cc.resume("OK") { expectUnreached() }
136         expect(5)
137     }
138 
139     @Test
140     fun testResumeLaterAfterCancel() = runTest {
141         expect(1)
142         lateinit var cc: CancellableContinuation<String>
143         val job = launch(start = CoroutineStart.UNDISPATCHED) {
144             expect(2)
145             try {
146                 suspendCancellableCoroutine<String> { cont ->
147                     expect(3)
148                     cont.invokeOnCancellation { expect(5) }
149                     cc = cont
150                 }
151                 expectUnreached()
152             } catch (e: CancellationException) {
153                 finish(9)
154             }
155         }
156         expect(4)
157         job.cancel(TestCancellationException())
158         expect(6)
159         cc.resume("OK") { cause ->
160             expect(7)
161             assertTrue(cause is TestCancellationException)
162         }
163         expect(8)
164     }
165 
166     @Test
167     fun testResumeLaterAfterCancelWithHandlerFailure() = runTest(
168         unhandled = listOf(
169             { it is CompletionHandlerException && it.cause is TestException2 },
170             { it is CompletionHandlerException && it.cause is TestException3 }
171         )
172     ) {
173         expect(1)
174         lateinit var cc: CancellableContinuation<String>
175         val job = launch(start = CoroutineStart.UNDISPATCHED) {
176             expect(2)
177             try {
178                 suspendCancellableCoroutine<String> { cont ->
179                     expect(3)
180                     cont.invokeOnCancellation {
181                         expect(5)
182                         throw TestException2("FAIL") // invokeOnCancellation handler fails with exception
183                     }
184                     cc = cont
185                 }
186                 expectUnreached()
187             } catch (e: CancellationException) {
188                 finish(9)
189             }
190         }
191         expect(4)
192         job.cancel(TestCancellationException())
193         expect(6)
194         cc.resume("OK") { cause ->
195             expect(7)
196             assertTrue(cause is TestCancellationException)
197             throw TestException3("FAIL") // onCancellation block fails with exception
198         }
199         expect(8)
200     }
201 
202     @Test
203     fun testResumeCancelWhileDispatched() = runTest {
204         expect(1)
205         lateinit var cc: CancellableContinuation<String>
206         val job = launch(start = CoroutineStart.UNDISPATCHED) {
207             expect(2)
208             try {
209                 suspendCancellableCoroutine<String> { cont ->
210                     expect(3)
211                     // resumed first, dispatched, then cancelled, but still got invokeOnCancellation call
212                     cont.invokeOnCancellation { cause ->
213                         // Note: invokeOnCancellation is called before cc.resume(value) { ... } handler
214                         expect(7)
215                         assertTrue(cause is TestCancellationException)
216                     }
217                     cc = cont
218                 }
219                 expectUnreached()
220             } catch (e: CancellationException) {
221                 expect(9)
222             }
223         }
224         expect(4)
225         cc.resume("OK") { cause ->
226             // Note: this handler is called after invokeOnCancellation handler
227             expect(8)
228             assertTrue(cause is TestCancellationException)
229         }
230         expect(5)
231         job.cancel(TestCancellationException()) // cancel while execution is dispatched
232         expect(6)
233         yield() // to coroutine -- throws cancellation exception
234         finish(10)
235     }
236 
237     @Test
238     fun testResumeCancelWhileDispatchedWithHandlerFailure() = runTest(
239         unhandled = listOf(
240             { it is CompletionHandlerException && it.cause is TestException2 },
241             { it is CompletionHandlerException && it.cause is TestException3 }
242         )
243     ) {
244         expect(1)
245         lateinit var cc: CancellableContinuation<String>
246         val job = launch(start = CoroutineStart.UNDISPATCHED) {
247             expect(2)
248             try {
249                 suspendCancellableCoroutine<String> { cont ->
250                     expect(3)
251                     // resumed first, dispatched, then cancelled, but still got invokeOnCancellation call
252                     cont.invokeOnCancellation { cause ->
253                         // Note: invokeOnCancellation is called before cc.resume(value) { ... } handler
254                         expect(7)
255                         assertTrue(cause is TestCancellationException)
256                         throw TestException2("FAIL") // invokeOnCancellation handler fails with exception
257                     }
258                     cc = cont
259                 }
260                 expectUnreached()
261             } catch (e: CancellationException) {
262                 expect(9)
263             }
264         }
265         expect(4)
266         cc.resume("OK") { cause ->
267             // Note: this handler is called after invokeOnCancellation handler
268             expect(8)
269             assertTrue(cause is TestCancellationException)
270             throw TestException3("FAIL") // onCancellation block fails with exception
271         }
272         expect(5)
273         job.cancel(TestCancellationException()) // cancel while execution is dispatched
274         expect(6)
275         yield() // to coroutine -- throws cancellation exception
276         finish(10)
277     }
278 
279     @Test
280     fun testResumeUnconfined() = runTest {
281         val outerScope = this
282         withContext(Dispatchers.Unconfined) {
283             val result = suspendCancellableCoroutine<String> {
284                 outerScope.launch {
285                     it.resume("OK") {
286                         expectUnreached()
287                     }
288                 }
289             }
290             assertEquals("OK", result)
291         }
292     }
293 }
294