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