| 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) |
| } |
| } |