diff options
Diffstat (limited to 'kotlinx-coroutines-core/common/test/AsyncTest.kt')
-rw-r--r-- | kotlinx-coroutines-core/common/test/AsyncTest.kt | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/kotlinx-coroutines-core/common/test/AsyncTest.kt b/kotlinx-coroutines-core/common/test/AsyncTest.kt new file mode 100644 index 00000000..6fd4ebbe --- /dev/null +++ b/kotlinx-coroutines-core/common/test/AsyncTest.kt @@ -0,0 +1,275 @@ +/* + * 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", "UNREACHABLE_CODE", "USELESS_IS_CHECK") // KT-21913 + +package kotlinx.coroutines + +import kotlin.test.* + +@Suppress("DEPRECATION") // cancel(cause) +class AsyncTest : TestBase() { + + @Test + fun testSimple() = runTest { + expect(1) + val d = async { + expect(3) + 42 + } + expect(2) + assertTrue(d.isActive) + assertEquals(d.await(), 42) + assertTrue(!d.isActive) + expect(4) + assertEquals(d.await(), 42) // second await -- same result + finish(5) + } + + @Test + fun testUndispatched() = runTest { + expect(1) + val d = async(start = CoroutineStart.UNDISPATCHED) { + expect(2) + 42 + } + expect(3) + assertTrue(!d.isActive) + assertEquals(d.await(), 42) + finish(4) + } + + @Test + fun testSimpleException() = runTest(expected = { it is TestException }) { + expect(1) + val d = async { + finish(3) + throw TestException() + } + expect(2) + d.await() // will throw TestException + } + + @Test + fun testCancellationWithCause() = runTest { + expect(1) + val d = async(NonCancellable, start = CoroutineStart.ATOMIC) { + expect(3) + yield() + } + expect(2) + d.cancel(TestCancellationException("TEST")) + try { + d.await() + } catch (e: TestCancellationException) { + finish(4) + assertEquals("TEST", e.message) + } + } + + @Test + fun testLostException() = runTest { + expect(1) + val deferred = async(Job()) { + expect(2) + throw Exception() + } + + // Exception is not consumed -> nothing is reported + deferred.join() + finish(3) + } + + @Test + fun testParallelDecompositionCaughtException() = runTest { + val deferred = async(NonCancellable) { + val decomposed = async(NonCancellable) { + throw TestException() + 1 + } + try { + decomposed.await() + } catch (e: TestException) { + 42 + } + } + assertEquals(42, deferred.await()) + } + + @Test + fun testParallelDecompositionCaughtExceptionWithInheritedParent() = runTest { + expect(1) + val deferred = async(NonCancellable) { + expect(2) + val decomposed = async { // inherits parent job! + expect(3) + throw TestException() + 1 + } + try { + decomposed.await() + } catch (e: TestException) { + expect(4) // Should catch this exception, but parent is already cancelled + 42 + } + } + try { + // This will fail + assertEquals(42, deferred.await()) + } catch (e: TestException) { + finish(5) + } + } + + @Test + fun testParallelDecompositionUncaughtExceptionWithInheritedParent() = runTest(expected = { it is TestException }) { + val deferred = async(NonCancellable) { + val decomposed = async { + throw TestException() + 1 + } + + decomposed.await() + } + + deferred.await() + expectUnreached() + } + + @Test + fun testParallelDecompositionUncaughtException() = runTest(expected = { it is TestException }) { + val deferred = async(NonCancellable) { + val decomposed = async { + throw TestException() + 1 + } + + decomposed.await() + } + + deferred.await() + expectUnreached() + } + + @Test + fun testCancellationTransparency() = runTest { + val deferred = async(NonCancellable, start = CoroutineStart.ATOMIC) { + expect(2) + throw TestException() + } + expect(1) + deferred.cancel() + try { + deferred.await() + } catch (e: TestException) { + finish(3) + } + } + + @Test + fun testDeferAndYieldException() = runTest(expected = { it is TestException }) { + expect(1) + val d = async { + expect(3) + yield() // no effect, parent waiting + finish(4) + throw TestException() + } + expect(2) + d.await() // will throw IOException + } + + @Test + fun testDeferWithTwoWaiters() = runTest { + expect(1) + val d = async { + expect(5) + yield() + expect(9) + 42 + } + expect(2) + launch { + expect(6) + assertEquals(d.await(), 42) + expect(11) + } + expect(3) + launch { + expect(7) + assertEquals(d.await(), 42) + expect(12) + } + expect(4) + yield() // this actually yields control to async, which produces results and resumes both waiters (in order) + expect(8) + yield() // yield again to "d", which completes + expect(10) + yield() // yield to both waiters + finish(13) + } + + class BadClass { + override fun equals(other: Any?): Boolean = error("equals") + override fun hashCode(): Int = error("hashCode") + override fun toString(): String = error("toString") + } + + @Test + fun testDeferBadClass() = runTest { + val bad = BadClass() + val d = async { + expect(1) + bad + } + assertSame(d.await(), bad) + finish(2) + } + + @Test + fun testOverriddenParent() = runTest { + val parent = Job() + val deferred = async(parent, CoroutineStart.ATOMIC) { + expect(2) + delay(Long.MAX_VALUE) + } + + parent.cancel() + try { + expect(1) + deferred.await() + } catch (e: CancellationException) { + finish(3) + } + } + + @Test + fun testIncompleteAsyncState() = runTest { + val deferred = async { + coroutineContext[Job]!!.invokeOnCompletion { } + } + + deferred.await().dispose() + assertTrue(deferred.getCompleted() is DisposableHandle) + assertNull(deferred.getCompletionExceptionOrNull()) + assertTrue(deferred.isCompleted) + assertFalse(deferred.isActive) + assertFalse(deferred.isCancelled) + } + + @Test + fun testIncompleteAsyncFastPath() = runTest { + val deferred = async(Dispatchers.Unconfined) { + coroutineContext[Job]!!.invokeOnCompletion { } + } + + deferred.await().dispose() + assertTrue(deferred.getCompleted() is DisposableHandle) + assertNull(deferred.getCompletionExceptionOrNull()) + assertTrue(deferred.isCompleted) + assertFalse(deferred.isActive) + assertFalse(deferred.isCancelled) + } + +} |