blob: 0c28ed0efb00a5be2df58e900c952d928347470e [file] [log] [blame]
package kotlinx.coroutines.experimental.exceptions
import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.CoroutineStart.*
import org.junit.Test
import java.io.*
import kotlin.coroutines.experimental.*
import kotlin.test.*
class JobExceptionHandlingTest : TestBase() {
@Test
fun testChildException() {
/*
* Root parent: JobImpl()
* Child: throws ISE
* Result: ISE in exception handler
*/
val exception = runBlock {
val job = Job()
launch(coroutineContext, parent = job, start = ATOMIC) {
expect(2)
throw IllegalStateException()
}
expect(1)
job.join()
finish(3)
}
checkException<IllegalStateException>(exception)
}
@Test
fun testExceptionDuringCancellation() {
/*
* Root parent: JobImpl()
* Launcher: cancels job
* Child: throws ISE
* Result: ISE in exception handler
*
* Github issue #354
*/
val exception = runBlock {
val job = Job()
val child = launch(coroutineContext, parent = job, start = ATOMIC) {
expect(2)
throw IllegalStateException()
}
expect(1)
job.cancelAndJoin()
assert(child.isCompleted && !child.isActive)
finish(3)
}
checkException<IllegalStateException>(exception)
}
@Test
fun testConsecutiveCancellation() {
/*
* Root parent: JobImpl()
* Child: throws IOException
* Launcher: cancels child with AE and then cancels it with NPE
* Result: AE with suppressed NPE and IOE
*/
val exception = runBlock {
val job = Job()
val child = launch(coroutineContext, parent = job, start = ATOMIC) {
expect(2)
throw IOException()
}
expect(1)
child.cancel(ArithmeticException())
child.cancel(NullPointerException())
job.join()
finish(3)
}
assertTrue(exception is ArithmeticException)
val suppressed = exception.suppressed()
assertEquals(2, suppressed.size)
checkException<NullPointerException>(suppressed[0])
checkException<IOException>(suppressed[1])
}
@Test
fun testExceptionOnChildCancellation() {
/*
* Root parent: JobImpl()
* Child: launch inner child and cancels parent
* Inner child: throws AE
* Result: AE in exception handler
*/
val exception = runBlock {
val job = Job()
launch(coroutineContext, parent = job) {
expect(2) // <- child is launched successfully
launch(coroutineContext) {
expect(3) // <- child's child is launched successfully
try {
yield()
} catch (e: JobCancellationException) {
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 = runBlock {
val job = Job()
launch(coroutineContext, parent = job, start = ATOMIC) {
expect(2)
launch(coroutineContext, 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 = runBlock {
val job = Job()
launch(coroutineContext, parent = job) {
expect(2) // <- child is launched successfully
launch(coroutineContext) {
expect(3) // <- child's child is launched successfully
try {
yield()
} catch (e: JobCancellationException) {
throw ArithmeticException()
}
}
yield()
expect(4)
job.cancel(IOException())
}
expect(1)
job.join()
finish(5)
}
assertTrue(exception is IOException)
assertNull(exception.cause)
val suppressed = exception.suppressed()
assertEquals(1, suppressed.size)
checkException<ArithmeticException>(suppressed[0])
}
@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 = runBlock {
val job = Job()
launch(coroutineContext, parent = job, start = ATOMIC) {
expect(2)
launch(coroutineContext, start = ATOMIC) {
expect(3)
throw ArithmeticException()
}
launch(coroutineContext, start = ATOMIC) {
expect(4)
throw IOException()
}
launch(coroutineContext, 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 = runBlock {
val job = Job()
launch(coroutineContext, parent = job, start = ATOMIC) {
expect(2)
launch(coroutineContext, start = ATOMIC) {
expect(3)
throw IOException()
}
launch(coroutineContext, 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)
}
}