1 /* <lambda>null2 * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.coroutines.test 6 7 import kotlinx.coroutines.* 8 import kotlinx.coroutines.channels.* 9 import kotlinx.coroutines.flow.* 10 import kotlin.coroutines.* 11 import kotlin.test.* 12 import kotlin.time.Duration.Companion.milliseconds 13 14 class TestScopeTest { 15 /** Tests failing to create a [TestScope] with incorrect contexts. */ 16 @Test 17 fun testCreateThrowsOnInvalidArguments() { 18 for (ctx in invalidContexts) { 19 assertFailsWith<IllegalArgumentException> { 20 TestScope(ctx) 21 } 22 } 23 } 24 25 /** Tests that a newly-created [TestScope] provides the correct scheduler. */ 26 @Test 27 fun testCreateProvidesScheduler() { 28 // Creates a new scheduler. 29 run { 30 val scope = TestScope() 31 assertNotNull(scope.coroutineContext[TestCoroutineScheduler]) 32 } 33 // Reuses the scheduler that the dispatcher is linked to. 34 run { 35 val dispatcher = StandardTestDispatcher() 36 val scope = TestScope(dispatcher) 37 assertSame(dispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler]) 38 } 39 // Uses the scheduler passed to it. 40 run { 41 val scheduler = TestCoroutineScheduler() 42 val scope = TestScope(scheduler) 43 assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler]) 44 assertSame(scheduler, (scope.coroutineContext[ContinuationInterceptor] as TestDispatcher).scheduler) 45 } 46 // Doesn't touch the passed dispatcher and the scheduler if they match. 47 run { 48 val scheduler = TestCoroutineScheduler() 49 val dispatcher = StandardTestDispatcher(scheduler) 50 val scope = TestScope(scheduler + dispatcher) 51 assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler]) 52 assertSame(dispatcher, scope.coroutineContext[ContinuationInterceptor]) 53 } 54 } 55 56 /** Part of [testCreateProvidesScheduler], disabled for Native */ 57 @Test 58 fun testCreateReusesScheduler() { 59 // Reuses the scheduler of `Dispatchers.Main` 60 run { 61 val scheduler = TestCoroutineScheduler() 62 val mainDispatcher = StandardTestDispatcher(scheduler) 63 Dispatchers.setMain(mainDispatcher) 64 try { 65 val scope = TestScope() 66 assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler]) 67 assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor]) 68 } finally { 69 Dispatchers.resetMain() 70 } 71 } 72 // Does not reuse the scheduler of `Dispatchers.Main` if one is explicitly passed 73 run { 74 val mainDispatcher = StandardTestDispatcher() 75 Dispatchers.setMain(mainDispatcher) 76 try { 77 val scheduler = TestCoroutineScheduler() 78 val scope = TestScope(scheduler) 79 assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler]) 80 assertNotSame(mainDispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler]) 81 assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor]) 82 } finally { 83 Dispatchers.resetMain() 84 } 85 } 86 } 87 88 /** Tests that the cleanup procedure throws if there were uncompleted delays by the end. */ 89 @Test 90 fun testPresentDelaysThrowing() { 91 val scope = TestScope() 92 var result = false 93 scope.launch { 94 delay(5) 95 result = true 96 } 97 assertFalse(result) 98 scope.asSpecificImplementation().enter() 99 assertFailsWith<UncompletedCoroutinesError> { scope.asSpecificImplementation().legacyLeave() } 100 assertFalse(result) 101 } 102 103 /** Tests that the cleanup procedure throws if there were active jobs by the end. */ 104 @Test 105 fun testActiveJobsThrowing() { 106 val scope = TestScope() 107 var result = false 108 val deferred = CompletableDeferred<String>() 109 scope.launch { 110 deferred.await() 111 result = true 112 } 113 assertFalse(result) 114 scope.asSpecificImplementation().enter() 115 assertFailsWith<UncompletedCoroutinesError> { scope.asSpecificImplementation().legacyLeave() } 116 assertFalse(result) 117 } 118 119 /** Tests that the cleanup procedure throws even if it detects that the job is already cancelled. */ 120 @Test 121 fun testCancelledDelaysThrowing() { 122 val scope = TestScope() 123 var result = false 124 val deferred = CompletableDeferred<String>() 125 val job = scope.launch { 126 deferred.await() 127 result = true 128 } 129 job.cancel() 130 assertFalse(result) 131 scope.asSpecificImplementation().enter() 132 assertFailsWith<UncompletedCoroutinesError> { scope.asSpecificImplementation().legacyLeave() } 133 assertFalse(result) 134 } 135 136 /** Tests that uncaught exceptions are thrown at the cleanup. */ 137 @Test 138 fun testGetsCancelledOnChildFailure(): TestResult { 139 val scope = TestScope() 140 val exception = TestException("test") 141 scope.launch { 142 throw exception 143 } 144 return testResultMap({ 145 try { 146 it() 147 fail("should not reach") 148 } catch (e: TestException) { 149 // expected 150 } 151 }) { 152 scope.runTest { 153 } 154 } 155 } 156 157 /** Tests that, when reporting several exceptions, the first one is thrown, with the rest suppressed. */ 158 @Test 159 fun testSuppressedExceptions() { 160 TestScope().apply { 161 asSpecificImplementation().enter() 162 launch(SupervisorJob()) { throw TestException("x") } 163 launch(SupervisorJob()) { throw TestException("y") } 164 launch(SupervisorJob()) { throw TestException("z") } 165 runCurrent() 166 val e = asSpecificImplementation().legacyLeave() 167 assertEquals(3, e.size) 168 assertEquals("x", e[0].message) 169 assertEquals("y", e[1].message) 170 assertEquals("z", e[2].message) 171 } 172 } 173 174 /** Tests that the background work is being run at all. */ 175 @Test 176 fun testBackgroundWorkBeingRun(): TestResult = runTest { 177 var i = 0 178 var j = 0 179 backgroundScope.launch { 180 ++i 181 } 182 backgroundScope.launch { 183 delay(10) 184 ++j 185 } 186 assertEquals(0, i) 187 assertEquals(0, j) 188 delay(1) 189 assertEquals(1, i) 190 assertEquals(0, j) 191 delay(10) 192 assertEquals(1, i) 193 assertEquals(1, j) 194 } 195 196 /** 197 * Tests that the background work gets cancelled after the test body finishes. 198 */ 199 @Test 200 fun testBackgroundWorkCancelled(): TestResult { 201 var cancelled = false 202 return testResultMap({ 203 it() 204 assertTrue(cancelled) 205 }) { 206 runTest { 207 var i = 0 208 backgroundScope.launch { 209 try { 210 while (isActive) { 211 ++i 212 yield() 213 } 214 } catch (e: CancellationException) { 215 cancelled = true 216 } 217 } 218 repeat(5) { 219 assertEquals(i, it) 220 yield() 221 } 222 } 223 } 224 } 225 226 /** Tests the interactions between the time-control commands and the background work. */ 227 @Test 228 fun testBackgroundWorkTimeControl(): TestResult = runTest { 229 var i = 0 230 var j = 0 231 backgroundScope.launch { 232 while (true) { 233 ++i 234 delay(100) 235 } 236 } 237 backgroundScope.launch { 238 while (true) { 239 ++j 240 delay(50) 241 } 242 } 243 advanceUntilIdle() // should do nothing, as only background work is left. 244 assertEquals(0, i) 245 assertEquals(0, j) 246 val job = launch { 247 delay(1) 248 // the background work scheduled for earlier gets executed before the normal work scheduled for later does 249 assertEquals(1, i) 250 assertEquals(1, j) 251 } 252 job.join() 253 advanceTimeBy(199.milliseconds) // should work the same for the background tasks 254 assertEquals(2, i) 255 assertEquals(4, j) 256 advanceUntilIdle() // once again, should do nothing 257 assertEquals(2, i) 258 assertEquals(4, j) 259 runCurrent() // should behave the same way as for the normal work 260 assertEquals(3, i) 261 assertEquals(5, j) 262 launch { 263 delay(1001) 264 assertEquals(13, i) 265 assertEquals(25, j) 266 } 267 advanceUntilIdle() // should execute the normal work, and with that, the background one, too 268 } 269 270 /** 271 * Tests that an error in a background coroutine does not cancel the test, but is reported at the end. 272 */ 273 @Test 274 fun testBackgroundWorkErrorReporting(): TestResult { 275 var testFinished = false 276 val exception = RuntimeException("x") 277 return testResultMap({ 278 try { 279 it() 280 fail("unreached") 281 } catch (e: Throwable) { 282 assertSame(e, exception) 283 assertTrue(testFinished) 284 } 285 }) { 286 runTest { 287 backgroundScope.launch { 288 throw exception 289 } 290 delay(1000) 291 testFinished = true 292 } 293 } 294 } 295 296 /** 297 * Tests that the background work gets to finish what it's doing after the test is completed. 298 */ 299 @Test 300 fun testBackgroundWorkFinalizing(): TestResult { 301 var taskEnded = 0 302 val nTasks = 10 303 return testResultMap({ 304 try { 305 it() 306 fail("unreached") 307 } catch (e: TestException) { 308 assertEquals(2, e.suppressedExceptions.size) 309 assertEquals(nTasks, taskEnded) 310 } 311 }) { 312 runTest { 313 repeat(nTasks) { 314 backgroundScope.launch { 315 try { 316 while (true) { 317 delay(1) 318 } 319 } finally { 320 ++taskEnded 321 if (taskEnded <= 2) 322 throw TestException() 323 } 324 } 325 } 326 delay(100) 327 throw TestException() 328 } 329 } 330 } 331 332 /** 333 * Tests using [Flow.stateIn] as a background job. 334 */ 335 @Test 336 fun testExampleBackgroundJob1() = runTest { 337 val myFlow = flow { 338 var i = 0 339 while (true) { 340 emit(++i) 341 delay(1) 342 } 343 } 344 val stateFlow = myFlow.stateIn(backgroundScope, SharingStarted.Eagerly, 0) 345 var j = 0 346 repeat(100) { 347 assertEquals(j++, stateFlow.value) 348 delay(1) 349 } 350 } 351 352 /** 353 * A test from the documentation of [TestScope.backgroundScope]. 354 */ 355 @Test 356 fun testExampleBackgroundJob2() = runTest { 357 val channel = Channel<Int>() 358 backgroundScope.launch { 359 var i = 0 360 while (true) { 361 channel.send(i++) 362 } 363 } 364 repeat(100) { 365 assertEquals(it, channel.receive()) 366 } 367 } 368 369 /** 370 * Tests that the test will timeout due to idleness even if some background tasks are running. 371 */ 372 @Test 373 fun testBackgroundWorkNotPreventingTimeout(): TestResult = testResultMap({ 374 try { 375 it() 376 fail("unreached") 377 } catch (_: UncompletedCoroutinesError) { 378 379 } 380 }) { 381 runTest(timeout = 100.milliseconds) { 382 backgroundScope.launch { 383 while (true) { 384 yield() 385 } 386 } 387 backgroundScope.launch { 388 while (true) { 389 delay(1) 390 } 391 } 392 val deferred = CompletableDeferred<Unit>() 393 deferred.await() 394 } 395 396 } 397 398 /** 399 * Tests that the background work will not prevent the test from timing out even in some cases 400 * when the unconfined dispatcher is used. 401 */ 402 @Test 403 fun testUnconfinedBackgroundWorkNotPreventingTimeout(): TestResult = testResultMap({ 404 try { 405 it() 406 fail("unreached") 407 } catch (_: UncompletedCoroutinesError) { 408 409 } 410 }) { 411 runTest(UnconfinedTestDispatcher(), timeout = 100.milliseconds) { 412 /** 413 * Having a coroutine like this will still cause the test to hang: 414 backgroundScope.launch { 415 while (true) { 416 yield() 417 } 418 } 419 * The reason is that even the initial [advanceUntilIdle] will never return in this case. 420 */ 421 backgroundScope.launch { 422 while (true) { 423 delay(1) 424 } 425 } 426 val deferred = CompletableDeferred<Unit>() 427 deferred.await() 428 } 429 } 430 431 /** 432 * Tests that even the exceptions in the background scope that don't typically get reported and need to be queried 433 * (like failures in [async]) will still surface in some simple scenarios. 434 */ 435 @Test 436 fun testAsyncFailureInBackgroundReported() = testResultMap({ 437 try { 438 it() 439 fail("unreached") 440 } catch (e: TestException) { 441 assertEquals("z", e.message) 442 assertEquals(setOf("x", "y"), e.suppressedExceptions.map { it.message }.toSet()) 443 } 444 }) { 445 runTest { 446 backgroundScope.async { 447 throw TestException("x") 448 } 449 backgroundScope.produce<Unit> { 450 throw TestException("y") 451 } 452 delay(1) 453 throw TestException("z") 454 } 455 } 456 457 /** 458 * Tests that, if an exception reaches the [TestScope] exception reporting mechanism via several 459 * channels, it will only be reported once. 460 */ 461 @Test 462 fun testNoDuplicateExceptions() = testResultMap({ 463 try { 464 it() 465 fail("unreached") 466 } catch (e: TestException) { 467 assertEquals("y", e.message) 468 assertEquals(listOf("x"), e.suppressedExceptions.map { it.message }) 469 } 470 }) { 471 runTest { 472 backgroundScope.launch { 473 throw TestException("x") 474 } 475 delay(1) 476 throw TestException("y") 477 } 478 } 479 480 /** 481 * Tests that [TestScope.withTimeout] notifies the programmer about using the virtual time. 482 */ 483 @Test 484 fun testTimingOutWithVirtualTimeMessage() = runTest { 485 try { 486 withTimeout(1_000_000) { 487 Channel<Unit>().receive() 488 } 489 } catch (e: TimeoutCancellationException) { 490 assertContains(e.message!!, "virtual") 491 } 492 } 493 494 /* 495 * Tests that the [TestScope] exception reporting mechanism will report the exceptions that happen between 496 * different tests. 497 * 498 * This test must be ran manually, because such exceptions still go through the global exception handler 499 * (as there's no guarantee that another test will happen), and the global exception handler will 500 * log the exceptions or, on Native, crash the test suite. 501 */ 502 @Test 503 @Ignore 504 fun testReportingStrayUncaughtExceptionsBetweenTests() { 505 val thrown = TestException("x") 506 testResultChain({ 507 // register a handler for uncaught exceptions 508 runTest { } 509 }, { 510 GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { 511 throw thrown 512 } 513 runTest { 514 fail("unreached") 515 } 516 }, { 517 // this `runTest` will not report the exception 518 runTest { 519 when (val exception = it.exceptionOrNull()) { 520 is UncaughtExceptionsBeforeTest -> { 521 assertEquals(1, exception.suppressedExceptions.size) 522 assertSame(exception.suppressedExceptions[0], thrown) 523 } 524 else -> fail("unexpected exception: $exception") 525 } 526 } 527 }) 528 } 529 530 /** 531 * Tests that the uncaught exceptions that happen during the test are reported. 532 */ 533 @Test 534 fun testReportingStrayUncaughtExceptionsDuringTest(): TestResult { 535 val thrown = TestException("x") 536 return testResultChain({ _ -> 537 runTest { 538 val job = launch(Dispatchers.Default + NonCancellable) { 539 throw thrown 540 } 541 job.join() 542 } 543 }, { 544 runTest { 545 assertEquals(thrown, it.exceptionOrNull()) 546 } 547 }) 548 } 549 550 companion object { 551 internal val invalidContexts = listOf( 552 Dispatchers.Default, // not a [TestDispatcher] 553 CoroutineExceptionHandler { _, _ -> }, // exception handlers can't be overridden 554 StandardTestDispatcher() + TestCoroutineScheduler(), // the dispatcher is not linked to the scheduler 555 ) 556 } 557 } 558