@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 package kotlinx.coroutines import kotlinx.coroutines.testing.* 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 { 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(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 { expect(3) val j2 = launch { expect(5) } expect(4) j2.join() expect(6) } expect(2) j1.join() finish(7) } @Test fun testWaitChild() = runTest { expect(1) launch { 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 { expect(3) yield() expectUnreached() } expect(2) yield() expect(4) job.cancel() finish(5) } @Test fun testCancelChildWithFinally() = runTest { expect(1) val job = launch { 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 { expect(3) launch { 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 TestException }) { expect(1) launch { 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 TestException }) { expect(1) launch { expect(3) launch { 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 { 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.cancel() // cancels job expect(5) // still here assertTrue(!job.isActive && !job.isCompleted) expect(6) // we're still here job.join() // join the job, let job complete its "finally" section expect(8) assertTrue(!job.isActive && job.isCompleted) finish(9) } @Test fun testCancelAndJoin() = runTest { expect(1) val job = launch(start = 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 TestException }) { expect(1) val job = launch(start = 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 CancellationException } catch (e: Throwable) { e as CancellationException // type assertion assertIs(e.cause) 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(parent, CoroutineStart.UNDISPATCHED) { expect(2) try { yield() // to be cancelled } finally { expect(5) } expectUnreached() } expect(3) parent.cancelChildren() expect(4) parent.children.forEach { it.join() } // 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(Job()) { expect(4) throw TestException("Crashed") } val child = launch(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) // make sure is cancelled assertTrue(child.isCancelled) } @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(NonCancellable, start = CoroutineStart.ATOMIC) { finish(4) throwTestException() // will throw expectUnreached() } expect(2) // now cancel with some other exception d.cancel(TestCancellationException()) // now await to see how it got crashed -- TestCancellationException should have been suppressed by TestException expect(3) d.await() } private fun throwTestException() { throw TestException() } }