blob: 7f1dd00963e6b8f6096728f8a66ad1ab65ec1926 [file] [log] [blame]
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
import kotlin.test.*
/** 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<Int> {
it.resume(42)
}
assertEquals(42, answer)
}
@Test
fun testNestedRunTestForbidden() = runTestWithLegacyScope {
assertFailsWith<IllegalStateException> {
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 testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn ->
assertFailsWith<UncompletedCoroutinesError> { fn() }
}) {
runTestWithLegacyScope(dispatchTimeoutMs = 0) {
withContext(Dispatchers.Default) {
delay(10)
3
}
fail("shouldn't be reached")
}
}
@Test
fun testRunTestWithSmallTimeout() = testResultMap({ fn ->
assertFailsWith<UncompletedCoroutinesError> { 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")
val suppressed = unwrap(e).suppressedExceptions
assertEquals(1, suppressed.size)
assertIs<TestException>(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<IllegalArgumentException> {
runTestWithLegacyScope(ctx) { }
}
}
}
@Test
fun testThrowingInRunTestBody() = testResultMap({
assertFailsWith<RuntimeException> { it() }
}) {
runTestWithLegacyScope {
throw RuntimeException()
}
}
@Test
fun testThrowingInRunTestPendingTask() = testResultMap({
assertFailsWith<RuntimeException> { 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<String> { 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<AssertionError> { it() }
assertTrue(job!!.isCancelled)
}) {
runTestWithLegacyScope {
job = launch {
while (true) {
delay(1000)
}
}
throw AssertionError()
}
}
}
@Test
fun testTimeout() = testResultMap({
assertFailsWith<TimeoutCancellationException> { it() }
}) {
runTestWithLegacyScope {
withTimeout(50) {
launch {
delay(1000)
}
}
}
}
@Test
fun testRunTestThrowsRootCause() = testResultMap({
assertFailsWith<TestException> { 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") }
}
}
}
}