blob: 806592079c9d90275935a934d5694492e3f30de9 [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.channels.*
import kotlinx.coroutines.flow.*
import kotlin.test.*
/** Copy of [RunTestTest], but for [runBlockingTestOnTestScope], where applicable. */
@Suppress("DEPRECATION")
class RunBlockingTestOnTestScopeTest {
@Test
fun testRunTestWithIllegalContext() {
for (ctx in TestScopeTest.invalidContexts) {
assertFailsWith<IllegalArgumentException> {
runBlockingTestOnTestScope(ctx) { }
}
}
}
@Test
fun testThrowingInRunTestBody() {
assertFailsWith<RuntimeException> {
runBlockingTestOnTestScope {
throw RuntimeException()
}
}
}
@Test
fun testThrowingInRunTestPendingTask() {
assertFailsWith<RuntimeException> {
runBlockingTestOnTestScope {
launch {
delay(SLOW)
throw RuntimeException()
}
}
}
}
@Test
fun reproducer2405() = runBlockingTestOnTestScope {
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() {
var job: Job? = null
assertFailsWith<AssertionError> {
runBlockingTestOnTestScope {
job = launch {
while (true) {
delay(1000)
}
}
throw AssertionError()
}
}
assertTrue(job!!.isCancelled)
}
@Test
fun testTimeout() {
assertFailsWith<TimeoutCancellationException> {
runBlockingTestOnTestScope {
withTimeout(50) {
launch {
delay(1000)
}
}
}
}
}
@Test
fun testRunTestThrowsRootCause() {
assertFailsWith<TestException> {
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<Int>()
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<Unit> {
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 })
}
}