package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.testing.* import kotlin.test.* import kotlin.test.assertFailsWith /** Copy of [RunTestTest], but for [runBlockingTestOnTestScope], where applicable. */ @Suppress("DEPRECATION") class RunBlockingTestOnTestScopeTest { @Test fun testRunTestWithIllegalContext() { for (ctx in TestScopeTest.invalidContexts) { assertFailsWith { runBlockingTestOnTestScope(ctx) { } } } } @Test fun testThrowingInRunTestBody() { assertFailsWith { runBlockingTestOnTestScope { throw RuntimeException() } } } @Test fun testThrowingInRunTestPendingTask() { assertFailsWith { runBlockingTestOnTestScope { launch { delay(SLOW) throw RuntimeException() } } } } @Test fun reproducer2405() = runBlockingTestOnTestScope { val dispatcher = StandardTestDispatcher(testScheduler) var collectedError = false withContext(dispatcher) { flow { emit(1) } .combine( flow { throw IllegalArgumentException() } ) { int, string -> int.toString() + string } .catch { emit("error") } .collect { assertEquals("error", it) collectedError = true } } assertTrue(collectedError) } @Test fun testChildrenCancellationOnTestBodyFailure() { var job: Job? = null assertFailsWith { runBlockingTestOnTestScope { job = launch { while (true) { delay(1000) } } throw AssertionError() } } assertTrue(job!!.isCancelled) } @Test fun testTimeout() { assertFailsWith { runBlockingTestOnTestScope { withTimeout(50) { launch { delay(1000) } } } } } @Test fun testRunTestThrowsRootCause() { assertFailsWith { runBlockingTestOnTestScope { launch { throw TestException() } } } } @Test fun testCompletesOwnJob() { var handlerCalled = false runBlockingTestOnTestScope { coroutineContext.job.invokeOnCompletion { handlerCalled = true } } assertTrue(handlerCalled) } @Test fun testDoesNotCompleteGivenJob() { var handlerCalled = false val job = Job() job.invokeOnCompletion { handlerCalled = true } runBlockingTestOnTestScope(job) { assertTrue(coroutineContext.job in job.children) } assertFalse(handlerCalled) assertEquals(0, job.children.filter { it.isActive }.count()) } @Test fun testSuppressedExceptions() { try { runBlockingTestOnTestScope { launch(SupervisorJob()) { throw TestException("x") } launch(SupervisorJob()) { throw TestException("y") } launch(SupervisorJob()) { throw TestException("z") } throw TestException("w") } fail("should not be reached") } catch (e: TestException) { assertEquals("w", e.message) val suppressed = e.suppressedExceptions + (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList()) assertEquals(3, suppressed.size) assertEquals("x", suppressed[0].message) assertEquals("y", suppressed[1].message) assertEquals("z", suppressed[2].message) } } @Test fun testScopeRunTestExceptionHandler(): TestResult { val scope = TestCoroutineScope() return testResultMap({ try { it() fail("should not be reached") } catch (e: TestException) { // expected } }) { scope.runTest { launch(SupervisorJob()) { throw TestException("x") } } } } @Test fun testBackgroundWorkBeingRun() = runBlockingTestOnTestScope { var i = 0 var j = 0 backgroundScope.launch { yield() ++i } backgroundScope.launch { yield() delay(10) ++j } assertEquals(0, i) assertEquals(0, j) delay(1) assertEquals(1, i) assertEquals(0, j) delay(10) assertEquals(1, i) assertEquals(1, j) } @Test fun testBackgroundWorkCancelled() { var cancelled = false runBlockingTestOnTestScope { var i = 0 backgroundScope.launch { yield() try { while (isActive) { ++i yield() } } catch (e: CancellationException) { cancelled = true } } repeat(5) { assertEquals(i, it) yield() } } assertTrue(cancelled) } @Test fun testBackgroundWorkTimeControl(): TestResult = runBlockingTestOnTestScope { var i = 0 var j = 0 backgroundScope.launch { yield() while (true) { ++i delay(100) } } backgroundScope.launch { yield() while (true) { ++j delay(50) } } advanceUntilIdle() // should do nothing, as only background work is left. assertEquals(0, i) assertEquals(0, j) val job = launch { delay(1) // the background work scheduled for earlier gets executed before the normal work scheduled for later does assertEquals(1, i) assertEquals(1, j) } job.join() advanceTimeBy(199) // should work the same for the background tasks assertEquals(2, i) assertEquals(4, j) advanceUntilIdle() // once again, should do nothing assertEquals(2, i) assertEquals(4, j) runCurrent() // should behave the same way as for the normal work assertEquals(3, i) assertEquals(5, j) launch { delay(1001) assertEquals(13, i) assertEquals(25, j) } advanceUntilIdle() // should execute the normal work, and with that, the background one, too } @Test fun testBackgroundWorkErrorReporting() { var testFinished = false val exception = RuntimeException("x") try { runBlockingTestOnTestScope { backgroundScope.launch { throw exception } delay(1000) testFinished = true } fail("unreached") } catch (e: Throwable) { assertSame(e, exception) assertTrue(testFinished) } } @Test fun testBackgroundWorkFinalizing() { var taskEnded = 0 val nTasks = 10 try { runBlockingTestOnTestScope { repeat(nTasks) { backgroundScope.launch { try { while (true) { delay(1) } } finally { ++taskEnded if (taskEnded <= 2) throw TestException() } } } delay(100) throw TestException() } fail("unreached") } catch (e: TestException) { assertEquals(2, e.suppressedExceptions.size) assertEquals(nTasks, taskEnded) } } @Test fun testExampleBackgroundJob1() = runBlockingTestOnTestScope { val myFlow = flow { yield() var i = 0 while (true) { emit(++i) delay(1) } } val stateFlow = myFlow.stateIn(backgroundScope, SharingStarted.Eagerly, 0) var j = 0 repeat(100) { assertEquals(j++, stateFlow.value) delay(1) } } @Test fun testExampleBackgroundJob2() = runBlockingTestOnTestScope { val channel = Channel() backgroundScope.launch { var i = 0 while (true) { channel.send(i++) } } repeat(100) { assertEquals(it, channel.receive()) } } @Test fun testAsyncFailureInBackgroundReported() = try { runBlockingTestOnTestScope { backgroundScope.async { throw TestException("x") } backgroundScope.produce { throw TestException("y") } delay(1) throw TestException("z") } fail("unreached") } catch (e: TestException) { assertEquals("z", e.message) assertEquals(setOf("x", "y"), e.suppressedExceptions.map { it.message }.toSet()) } @Test fun testNoDuplicateExceptions() = try { runBlockingTestOnTestScope { backgroundScope.launch { throw TestException("x") } delay(1) throw TestException("y") } fail("unreached") } catch (e: TestException) { assertEquals("y", e.message) assertEquals(listOf("x"), e.suppressedExceptions.map { it.message }) } }