1 2 @file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-22237 3 4 package kotlinx.coroutines 5 6 import kotlinx.coroutines.testing.* 7 import kotlin.test.* 8 9 class WithContextTest : TestBase() { 10 11 @Test <lambda>null12 fun testThrowException() = runTest { 13 expect(1) 14 try { 15 withContext<Unit>(coroutineContext) { 16 expect(2) 17 throw AssertionError() 18 } 19 } catch (e: AssertionError) { 20 expect(3) 21 } 22 23 yield() 24 finish(4) 25 } 26 27 @Test <lambda>null28 fun testThrowExceptionFromWrappedContext() = runTest { 29 expect(1) 30 try { 31 withContext<Unit>(wrapperDispatcher(coroutineContext)) { 32 expect(2) 33 throw AssertionError() 34 } 35 } catch (e: AssertionError) { 36 expect(3) 37 } 38 39 yield() 40 finish(4) 41 } 42 43 @Test <lambda>null44 fun testSameContextNoSuspend() = runTest { 45 expect(1) 46 launch(coroutineContext) { // make sure there is not early dispatch here 47 finish(5) // after main exits 48 } 49 expect(2) 50 val result = withContext(coroutineContext) { // same context! 51 expect(3) // still here 52 "OK".wrap() 53 }.unwrap() 54 assertEquals("OK", result) 55 expect(4) 56 // will wait for the first coroutine 57 } 58 59 @Test <lambda>null60 fun testSameContextWithSuspend() = runTest { 61 expect(1) 62 launch(coroutineContext) { // make sure there is not early dispatch here 63 expect(4) 64 } 65 expect(2) 66 val result = withContext(coroutineContext) { // same context! 67 expect(3) // still here 68 yield() // now yields to launch! 69 expect(5) 70 "OK".wrap() 71 }.unwrap() 72 assertEquals("OK", result) 73 finish(6) 74 } 75 76 @Test <lambda>null77 fun testCancelWithJobNoSuspend() = runTest { 78 expect(1) 79 launch(coroutineContext) { // make sure there is not early dispatch to here 80 finish(6) // after main exits 81 } 82 expect(2) 83 val job = Job() 84 try { 85 withContext(coroutineContext + job) { 86 // same context + new job 87 expect(3) // still here 88 job.cancel() // cancel out job! 89 try { 90 yield() // shall throw CancellationException 91 expectUnreached() 92 } catch (e: CancellationException) { 93 expect(4) 94 } 95 "OK".wrap() 96 } 97 98 expectUnreached() 99 } catch (e: CancellationException) { 100 expect(5) 101 // will wait for the first coroutine 102 } 103 } 104 105 @Test testCancelWithJobWithSuspendnull106 fun testCancelWithJobWithSuspend() = runTest( 107 expected = { it is CancellationException } <lambda>null108 ) { 109 expect(1) 110 launch(coroutineContext) { // make sure there is not early dispatch to here 111 expect(4) 112 } 113 expect(2) 114 val job = Job() 115 withContext(coroutineContext + job) { // same context + new job 116 expect(3) // still here 117 yield() // now yields to launch! 118 expect(5) 119 job.cancel() // cancel out job! 120 try { 121 yield() // shall throw CancellationException 122 expectUnreached() 123 } catch (e: CancellationException) { 124 finish(6) 125 } 126 "OK".wrap() 127 } 128 // still fails, because parent job was cancelled 129 expectUnreached() 130 } 131 132 @Test testRunCancellableDefaultnull133 fun testRunCancellableDefault() = runTest( 134 expected = { it is CancellationException } <lambda>null135 ) { 136 val job = Job() 137 job.cancel() // cancel before it has a chance to run 138 withContext(job + wrapperDispatcher(coroutineContext)) { 139 expectUnreached() // will get cancelled 140 } 141 } 142 143 @Test <lambda>null144 fun testRunCancellationUndispatchedVsException() = runTest { 145 expect(1) 146 var job: Job? = null 147 job = launch(start = CoroutineStart.UNDISPATCHED) { 148 expect(2) 149 try { 150 // Same dispatcher, different context 151 withContext<Unit>(CoroutineName("testRunCancellationUndispatchedVsException")) { 152 expect(3) 153 yield() // must suspend 154 expect(5) 155 job!!.cancel() // cancel this job _before_ it throws 156 throw TestException() 157 } 158 } catch (e: TestException) { 159 // must have caught TextException 160 expect(6) 161 } 162 } 163 expect(4) 164 yield() // to coroutineScope 165 finish(7) 166 } 167 168 @Test <lambda>null169 fun testRunCancellationDispatchedVsException() = runTest { 170 expect(1) 171 var job: Job? = null 172 job = launch(start = CoroutineStart.UNDISPATCHED) { 173 expect(2) 174 try { 175 // "Different" dispatcher (schedules execution back and forth) 176 withContext<Unit>(wrapperDispatcher(coroutineContext)) { 177 expect(4) 178 yield() // must suspend 179 expect(6) 180 job!!.cancel() // cancel this job _before_ it throws 181 throw TestException() 182 } 183 } catch (e: TestException) { 184 // must have caught TextException 185 expect(8) 186 } 187 } 188 expect(3) 189 yield() // withContext is next 190 expect(5) 191 yield() // withContext again 192 expect(7) 193 yield() // to catch block 194 finish(9) 195 } 196 197 @Test <lambda>null198 fun testRunSelfCancellationWithException() = runTest { 199 expect(1) 200 var job: Job? = null 201 job = launch(Job()) { 202 try { 203 expect(3) 204 withContext<Unit>(wrapperDispatcher(coroutineContext)) { 205 require(isActive) 206 expect(5) 207 job!!.cancel() 208 require(!isActive) 209 throw TestException() // but throw an exception 210 } 211 } catch (e: Throwable) { 212 expect(7) 213 // make sure TestException, not CancellationException is thrown 214 assertIs<TestException>(e, "Caught $e") 215 } 216 } 217 expect(2) 218 yield() // to the launched job 219 expect(4) 220 yield() // again to the job 221 expect(6) 222 yield() // again to exception handler 223 finish(8) 224 } 225 226 @Test <lambda>null227 fun testRunSelfCancellation() = runTest { 228 expect(1) 229 var job: Job? = null 230 job = launch(Job()) { 231 try { 232 expect(3) 233 withContext(wrapperDispatcher(coroutineContext)) { 234 require(isActive) 235 expect(5) 236 job!!.cancel() // cancel itself 237 require(!isActive) 238 "OK".wrap() 239 } 240 expectUnreached() 241 } catch (e: Throwable) { 242 expect(7) 243 // make sure CancellationException is thrown 244 assertIs<CancellationException>(e, "Caught $e") 245 } 246 } 247 248 expect(2) 249 yield() // to the launched job 250 expect(4) 251 yield() // again to the job 252 expect(6) 253 yield() // again to exception handler 254 finish(8) 255 } 256 257 @Test <lambda>null258 fun testWithContextScopeFailure() = runTest { 259 expect(1) 260 try { 261 withContext(wrapperDispatcher(coroutineContext)) { 262 expect(2) 263 // launch a child that fails 264 launch { 265 expect(4) 266 throw TestException() 267 } 268 expect(3) 269 "OK".wrap() 270 } 271 expectUnreached() 272 } catch (e: TestException) { 273 // ensure that we can catch exception outside of the scope 274 expect(5) 275 } 276 finish(6) 277 } 278 279 @Test <lambda>null280 fun testWithContextChildWaitSameContext() = runTest { 281 expect(1) 282 withContext(coroutineContext) { 283 expect(2) 284 launch { 285 // ^^^ schedules to main thread 286 expect(4) // waits before return 287 } 288 expect(3) 289 "OK".wrap() 290 }.unwrap() 291 finish(5) 292 } 293 294 @Test <lambda>null295 fun testWithContextChildWaitWrappedContext() = runTest { 296 expect(1) 297 withContext(wrapperDispatcher(coroutineContext)) { 298 expect(2) 299 launch { 300 // ^^^ schedules to main thread 301 expect(4) // waits before return 302 } 303 expect(3) 304 "OK".wrap() 305 }.unwrap() 306 finish(5) 307 } 308 309 @Test <lambda>null310 fun testIncompleteWithContextState() = runTest { 311 lateinit var ctxJob: Job 312 withContext(wrapperDispatcher(coroutineContext)) { 313 ctxJob = coroutineContext[Job]!! 314 ctxJob.invokeOnCompletion { } 315 } 316 317 ctxJob.join() 318 assertTrue(ctxJob.isCompleted) 319 assertFalse(ctxJob.isActive) 320 assertFalse(ctxJob.isCancelled) 321 } 322 323 @Test <lambda>null324 fun testWithContextCancelledJob() = runTest { 325 expect(1) 326 val job = Job() 327 job.cancel() 328 try { 329 withContext(job) { 330 expectUnreached() 331 } 332 } catch (e: CancellationException) { 333 expect(2) 334 } 335 finish(3) 336 } 337 338 @Test testWithContextCancelledThisJobnull339 fun testWithContextCancelledThisJob() = runTest( 340 expected = { it is CancellationException } <lambda>null341 ) { 342 coroutineContext.cancel() 343 withContext(wrapperDispatcher(coroutineContext)) { 344 expectUnreached() 345 } 346 expectUnreached() 347 } 348 349 @Test <lambda>null350 fun testSequentialCancellation() = runTest { 351 val job = launch { 352 expect(1) 353 withContext(wrapperDispatcher()) { 354 expect(2) 355 } 356 expectUnreached() 357 } 358 359 yield() 360 val job2 = launch { 361 expect(3) 362 job.cancel() 363 } 364 365 joinAll(job, job2) 366 finish(4) 367 } 368 369 private class Wrapper(val value: String) : Incomplete { 370 override val isActive: Boolean 371 get() = error("") 372 override val list: NodeList? 373 get() = error("") 374 } 375 Stringnull376 private fun String.wrap() = Wrapper(this) 377 private fun Wrapper.unwrap() = value 378 } 379