package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.internal.* import kotlinx.coroutines.testing.* import kotlin.coroutines.* import kotlin.test.* import kotlin.test.assertFailsWith /** Copy of [RunTestTest], but for [TestCoroutineScope] */ @Suppress("DEPRECATION") class RunTestLegacyScopeTest { @Test fun testWithContextDispatching() = runTestWithLegacyScope { var counter = 0 withContext(Dispatchers.Default) { counter += 1 } assertEquals(counter, 1) } @Test fun testJoiningForkedJob() = runTestWithLegacyScope { var counter = 0 val job = GlobalScope.launch { counter += 1 } job.join() assertEquals(counter, 1) } @Test fun testSuspendCoroutine() = runTestWithLegacyScope { val answer = suspendCoroutine { it.resume(42) } assertEquals(42, answer) } @Test fun testNestedRunTestForbidden() = runTestWithLegacyScope { assertFailsWith { runTest { } } } @Test fun testRunTestWithZeroTimeoutWithControlledDispatches() = runTestWithLegacyScope(dispatchTimeoutMs = 0) { // below is some arbitrary concurrent code where all dispatches go through the same scheduler. launch { delay(2000) } val deferred = async { val job = launch(StandardTestDispatcher(testScheduler)) { launch { delay(500) } delay(1000) } job.join() } deferred.await() } @Test fun testRunTestWithSmallTimeout() = testResultMap({ fn -> assertFailsWith { fn() } }) { runTestWithLegacyScope(dispatchTimeoutMs = 100) { withContext(Dispatchers.Default) { delay(10000) 3 } fail("shouldn't be reached") } } @Test fun testRunTestWithLargeTimeout() = runTestWithLegacyScope(dispatchTimeoutMs = 5000) { withContext(Dispatchers.Default) { delay(50) } } @Test fun testRunTestTimingOutAndThrowing() = testResultMap({ fn -> try { fn() fail("unreached") } catch (e: UncompletedCoroutinesError) { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val suppressed = unwrap(e).suppressedExceptions assertEquals(1, suppressed.size) assertIs(suppressed[0]).also { assertEquals("A", it.message) } } }) { runTestWithLegacyScope(dispatchTimeoutMs = 1) { coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A")) withContext(Dispatchers.Default) { delay(10000) 3 } fail("shouldn't be reached") } } @Test fun testRunTestWithIllegalContext() { for (ctx in TestScopeTest.invalidContexts) { assertFailsWith { runTestWithLegacyScope(ctx) { } } } } @Test fun testThrowingInRunTestBody() = testResultMap({ assertFailsWith { it() } }) { runTestWithLegacyScope { throw RuntimeException() } } @Test fun testThrowingInRunTestPendingTask() = testResultMap({ assertFailsWith { it() } }) { runTestWithLegacyScope { launch { delay(SLOW) throw RuntimeException() } } } @Test fun reproducer2405() = runTestWithLegacyScope { 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(): TestResult { var job: Job? = null return testResultMap({ assertFailsWith { it() } assertTrue(job!!.isCancelled) }) { runTestWithLegacyScope { job = launch { while (true) { delay(1000) } } throw AssertionError() } } } @Test fun testTimeout() = testResultMap({ assertFailsWith { it() } }) { runTestWithLegacyScope { withTimeout(50) { launch { delay(1000) } } } } @Test fun testRunTestThrowsRootCause() = testResultMap({ assertFailsWith { it() } }) { runTestWithLegacyScope { launch { throw TestException() } } } @Test fun testCompletesOwnJob(): TestResult { var handlerCalled = false return testResultMap({ it() assertTrue(handlerCalled) }) { runTestWithLegacyScope { coroutineContext.job.invokeOnCompletion { handlerCalled = true } } } } @Test fun testDoesNotCompleteGivenJob(): TestResult { var handlerCalled = false val job = Job() job.invokeOnCompletion { handlerCalled = true } return testResultMap({ it() assertFalse(handlerCalled) assertEquals(0, job.children.filter { it.isActive }.count()) }) { runTestWithLegacyScope(job) { assertTrue(coroutineContext.job in job.children) } } } @Test fun testSuppressedExceptions() = testResultMap({ try { it() 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) } }) { runTestWithLegacyScope { launch(SupervisorJob()) { throw TestException("x") } launch(SupervisorJob()) { throw TestException("y") } launch(SupervisorJob()) { throw TestException("z") } throw TestException("w") } } @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") } } } } }