blob: 2d27ee35c2f6e661df340c7a3a14a2b19abb950a [file] [log] [blame]
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
package kotlinx.coroutines.experimental
import kotlin.coroutines.experimental.*
import kotlin.test.*
class CoroutinesTest : TestBase() {
@Test
fun testSimple() = runTest {
expect(1)
finish(2)
}
@Test
fun testYield() = runTest {
expect(1)
yield() // effectively does nothing, as we don't have other coroutines
finish(2)
}
@Test
fun testLaunchAndYieldJoin() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
yield()
expect(4)
}
expect(2)
assertTrue(job.isActive && !job.isCompleted)
job.join()
assertTrue(!job.isActive && job.isCompleted)
finish(5)
}
@Test
fun testLaunchUndispatched() = runTest {
expect(1)
val job = launch(coroutineContext, start = CoroutineStart.UNDISPATCHED) {
expect(2)
yield()
expect(4)
}
expect(3)
assertTrue(job.isActive && !job.isCompleted)
job.join()
assertTrue(!job.isActive && job.isCompleted)
finish(5)
}
@Test
fun testNested() = runTest {
expect(1)
val j1 = launch(coroutineContext) {
expect(3)
val j2 = launch(coroutineContext) {
expect(5)
}
expect(4)
j2.join()
expect(6)
}
expect(2)
j1.join()
finish(7)
}
@Test
fun testWaitChild() = runTest {
expect(1)
launch(coroutineContext) {
expect(3)
yield() // to parent
finish(5)
}
expect(2)
yield()
expect(4)
// parent waits for child's completion
}
@Test
fun testCancelChildExplicit() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
yield()
expectUnreached()
}
expect(2)
yield()
expect(4)
job.cancel()
finish(5)
}
@Test
fun testCancelChildWithFinally() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
try {
yield()
} finally {
finish(6) // cancelled child will still execute finally
}
expectUnreached()
}
expect(2)
yield()
expect(4)
job.cancel()
expect(5)
}
@Test
fun testWaitNestedChild() = runTest {
expect(1)
launch(coroutineContext) {
expect(3)
launch(coroutineContext) {
expect(6)
yield() // to parent
expect(9)
}
expect(4)
yield()
expect(7)
yield() // to parent
finish(10) // the last one to complete
}
expect(2)
yield()
expect(5)
yield()
expect(8)
// parent waits for child
}
@Test
fun testExceptionPropagation() = runTest(
expected = { it is TestException }
) {
finish(1)
throw TestException()
}
@Test
fun testCancelParentOnChildException() = runTest(
expected = { it is JobCancellationException && it.cause is TestException },
unhandled = listOf({ it -> it is TestException })
) {
expect(1)
launch(coroutineContext) {
finish(3)
throwTestException() // does not propagate exception to launch, but cancels parent (!)
expectUnreached()
}
expect(2)
yield()
expectUnreached() // because of exception in child
}
@Test
fun testCancelParentOnNestedException() = runTest(
expected = { it is JobCancellationException && it.cause is TestException },
unhandled = listOf(
{ it -> it is TestException },
{ it -> it is TestException }
)
) {
expect(1)
launch(coroutineContext) {
expect(3)
launch(coroutineContext) {
finish(6)
throwTestException() // unhandled exception kills all parents
expectUnreached()
}
expect(4)
yield()
expectUnreached() // because of exception in child
}
expect(2)
yield()
expect(5)
yield()
expectUnreached() // because of exception in child
}
@Test
fun testJoinWithFinally() = runTest {
expect(1)
val job = launch(coroutineContext) {
expect(3)
try {
yield() // to main, will cancel us
} finally {
expect(7) // join is waiting
}
}
expect(2)
yield() // to job
expect(4)
assertTrue(job.isActive && !job.isCompleted && !job.isCancelled)
assertTrue(job.cancel()) // cancels job
expect(5) // still here
assertTrue(!job.isActive && !job.isCompleted && job.isCancelled)
assertTrue(!job.cancel()) // second attempt returns false
expect(6) // we're still here
job.join() // join the job, let job complete its "finally" section
expect(8)
assertTrue(!job.isActive && job.isCompleted && job.isCancelled)
finish(9)
}
@Test
fun testCancelAndJoin() = runTest {
expect(1)
val job = launch(coroutineContext, CoroutineStart.UNDISPATCHED) {
try {
expect(2)
yield()
expectUnreached() // will get cancelled
} finally {
expect(4)
}
}
expect(3)
job.cancelAndJoin()
finish(5)
}
@Test
fun testCancelAndJoinChildCrash() = runTest(
expected = { it is JobCancellationException && it.cause is TestException },
unhandled = listOf({it -> it is TestException })
) {
expect(1)
val job = launch(coroutineContext, CoroutineStart.UNDISPATCHED) {
expect(2)
throwTestException()
expectUnreached()
}
// now we have a failed job with TestException
finish(3)
try {
job.cancelAndJoin() // join should crash on child's exception but it will be wrapped into JobCancellationException
} catch (e: Throwable) {
e as JobCancellationException // type assertion
assertTrue(e.cause is TestException)
assertTrue(e.job === coroutineContext[Job])
throw e
}
expectUnreached()
}
@Test
fun testYieldInFinally() = runTest(
expected = { it is TestException }
) {
expect(1)
try {
expect(2)
throwTestException()
} finally {
expect(3)
yield()
finish(4)
}
expectUnreached()
}
@Test
fun testCancelAndJoinChildren() = runTest {
expect(1)
val parent = Job()
launch(coroutineContext, CoroutineStart.UNDISPATCHED, parent = parent) {
expect(2)
try {
yield() // to be cancelled
} finally {
expect(5)
}
expectUnreached()
}
expect(3)
parent.cancelChildren()
expect(4)
parent.joinChildren() // will yield to child
assertTrue(parent.isActive) // make sure it did not cancel parent
finish(6)
}
@Test
fun testParentCrashCancelsChildren() = runTest(
unhandled = listOf({ it -> it is TestException })
) {
expect(1)
val parent = launch(coroutineContext + Job()) {
expect(4)
throw TestException("Crashed")
}
launch(coroutineContext + parent, CoroutineStart.UNDISPATCHED) {
expect(2)
try {
yield() // to test
} finally {
expect(5)
withContext(NonCancellable) { yield() } // to test
expect(7)
}
expectUnreached() // will get cancelled, because parent crashes
}
expect(3)
yield() // to parent
expect(6)
parent.join() // make sure crashed parent still waits for its child
finish(8)
}
@Test
fun testNotCancellableChildWithExceptionCancelled() = runTest(
expected = { it is TestException }
) {
expect(1)
// CoroutineStart.ATOMIC makes sure it will not get cancelled for it starts executing
val d = async(coroutineContext, start = CoroutineStart.ATOMIC) {
finish(4)
throwTestException() // will throw
expectUnreached()
}
expect(2)
// now cancel with some other exception
d.cancel(IllegalArgumentException())
// now await to see how it got crashed -- IAE should have been suppressed by TestException
expect(3)
d.await()
}
private fun throwTestException() { throw TestException() }
private class TestException : Exception {
constructor(message: String): super(message)
constructor(): super()
}
}