| /* |
| * 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 |
| |
| 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 |
| assertTrue(e.cause is TestException) |
| 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() } |
| } |