blob: eaa73bdb50e0a96c203c340beba64cd8dcec9478 [file] [log] [blame]
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.exceptions
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.*
import org.junit.Test
import java.io.*
import kotlin.test.*
@Suppress("DEPRECATION") // cancel(cause)
class JobExceptionHandlingTest : TestBase() {
@Test
fun testChildException() {
/*
* Root parent: JobImpl()
* Child: throws ISE
* Result: ISE in exception handler
*/
val exception = captureExceptionsRun {
val job = Job()
launch(job, start = ATOMIC) {
expect(2)
throw IllegalStateException()
}
expect(1)
job.join()
finish(3)
}
checkException<IllegalStateException>(exception)
}
@Test
fun testAsyncCancellationWithCauseAndParent() = runTest {
val parent = Job()
val deferred = async(parent) {
expect(2)
delay(Long.MAX_VALUE)
}
expect(1)
yield()
parent.completeExceptionally(IOException())
try {
deferred.await()
expectUnreached()
} catch (e: CancellationException) {
assertTrue(e.suppressed.isEmpty())
assertTrue(e.cause?.suppressed?.isEmpty() ?: false)
finish(3)
}
}
@Test
fun testAsyncCancellationWithCauseAndParentDoesNotTriggerHandling() = runTest {
val parent = Job()
val job = launch(parent) {
expect(2)
delay(Long.MAX_VALUE)
}
expect(1)
yield()
parent.completeExceptionally(IOException())
job.join()
finish(3)
}
@Test
fun testExceptionDuringCancellation() {
/*
* Root parent: JobImpl()
* Launcher: cancels job
* Child: throws ISE
* Result: ISE in exception handler
*
* Github issue #354
*/
val exception = captureExceptionsRun {
val job = Job()
val child = launch(job, start = ATOMIC) {
expect(2)
throw IllegalStateException()
}
expect(1)
job.cancelAndJoin()
assert(child.isCompleted && !child.isActive)
finish(3)
}
checkException<IllegalStateException>(exception)
}
@Test
fun testExceptionOnChildCancellation() {
/*
* Root parent: JobImpl()
* Child: launch inner child and cancels parent
* Inner child: throws AE
* Result: AE in exception handler
*/
val exception = captureExceptionsRun {
val job = Job()
launch(job) {
expect(2) // <- child is launched successfully
launch {
expect(3) // <- child's child is launched successfully
try {
yield()
} catch (e: CancellationException) {
throw ArithmeticException()
}
}
yield()
expect(4)
job.cancel()
}
expect(1)
job.join()
finish(5)
}
checkException<ArithmeticException>(exception)
}
@Test
fun testInnerChildException() {
/*
* Root parent: JobImpl()
* Launcher: launch child and cancel root
* Child: launch nested child atomically and yields
* Inner child: throws AE
* Result: AE
*/
val exception = captureExceptionsRun {
val job = Job()
launch(job, start = ATOMIC) {
expect(2)
launch(start = ATOMIC) {
expect(3) // <- child's child is launched successfully
throw ArithmeticException()
}
yield() // will throw cancellation exception
}
expect(1)
job.cancelAndJoin()
finish(4)
}
checkException<ArithmeticException>(exception)
}
@Test
fun testExceptionOnChildCancellationWithCause() {
/*
* Root parent: JobImpl()
* Child: launch inner child and cancels parent with IOE
* Inner child: throws AE
* Result: IOE with suppressed AE
*/
val exception = captureExceptionsRun {
val job = Job()
launch(job) {
expect(2) // <- child is launched successfully
launch {
expect(3) // <- child's child is launched successfully
try {
yield()
} catch (e: CancellationException) {
throw ArithmeticException()
}
}
yield()
expect(4)
job.completeExceptionally(IOException())
}
expect(1)
job.join()
finish(5)
}
assertTrue(exception is ArithmeticException)
assertNull(exception.cause)
assertTrue(exception.suppressed.isEmpty())
}
@Test
fun testMultipleChildrenThrowAtomically() {
/*
* Root parent: JobImpl()
* Launcher: launches child
* Child: launch 3 children, each of them throws an exception (AE, IOE, IAE) and calls delay()
* Result: AE with suppressed IOE and IAE
*/
val exception = captureExceptionsRun {
val job = Job()
launch(job, start = ATOMIC) {
expect(2)
launch(start = ATOMIC) {
expect(3)
throw ArithmeticException()
}
launch(start = ATOMIC) {
expect(4)
throw IOException()
}
launch(start = ATOMIC) {
expect(5)
throw IllegalArgumentException()
}
delay(Long.MAX_VALUE)
}
expect(1)
job.join()
finish(6)
}
assertTrue(exception is ArithmeticException)
val suppressed = exception.suppressed
assertEquals(2, suppressed.size)
assertTrue(suppressed[0] is IOException)
assertTrue(suppressed[1] is IllegalArgumentException)
}
@Test
fun testMultipleChildrenAndParentThrowsAtomic() {
/*
* Root parent: JobImpl()
* Launcher: launches child
* Child: launch 2 children (each of them throws an exception (IOE, IAE)), throws AE
* Result: AE with suppressed IOE and IAE
*/
val exception = captureExceptionsRun {
val job = Job()
launch(job, start = ATOMIC) {
expect(2)
launch(start = ATOMIC) {
expect(3)
throw IOException()
}
launch(start = ATOMIC) {
expect(4)
throw IllegalArgumentException()
}
throw AssertionError()
}
expect(1)
job.join()
finish(5)
}
assertTrue(exception is AssertionError)
val suppressed = exception.suppressed
assertEquals(2, suppressed.size)
assertTrue(suppressed[0] is IOException)
assertTrue(suppressed[1] is IllegalArgumentException)
}
@Test
fun testExceptionIsHandledOnce() = runTest(unhandled = listOf { e -> e is TestException }) {
val job = Job()
val j1 = launch(job) {
expect(1)
delay(Long.MAX_VALUE)
}
val j2 = launch(job) {
expect(2)
throw TestException()
}
joinAll(j1 ,j2)
finish(3)
}
@Test
fun testCancelledParent() = runTest {
expect(1)
val parent = Job()
parent.completeExceptionally(TestException())
launch(parent) {
expectUnreached()
}.join()
finish(2)
}
@Test
fun testExceptionIsNotReported() = runTest {
try {
expect(1)
coroutineScope {
val job = Job(coroutineContext[Job])
launch(job) {
throw TestException()
}
}
expectUnreached()
} catch (e: TestException) {
finish(2)
}
}
@Test
fun testExceptionIsNotReportedTripleChain() = runTest {
try {
expect(1)
coroutineScope {
val job = Job(Job(Job(coroutineContext[Job])))
launch(job) {
throw TestException()
}
}
expectUnreached()
} catch (e: TestException) {
finish(2)
}
}
@Test
fun testAttachToCancelledJob() = runTest(unhandled = listOf({ e -> e is TestException })) {
val parent = launch(Job()) {
throw TestException()
}.apply { join() }
launch(parent) { expectUnreached() }
launch(Job(parent)) { expectUnreached() }
}
@Test
fun testBadException() = runTest(unhandled = listOf({e -> e is BadException})) {
val job = launch(Job()) {
expect(2)
launch {
expect(3)
throw BadException()
}
launch(start = ATOMIC) {
expect(4)
throw BadException()
}
yield()
BadException()
}
expect(1)
yield()
yield()
expect(5)
job.join()
finish(6)
}
private class BadException : Exception() {
override fun hashCode(): Int {
throw AssertionError()
}
}
}