package kotlinx.coroutines.exceptions import kotlinx.coroutines.testing.* import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineStart.* import kotlinx.coroutines.testing.exceptions.* 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(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(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(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(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) } checkException(exception) } @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) } assertIs(exception) assertNull(exception.cause) val suppressed = exception.suppressed assertEquals(2, suppressed.size) assertIs(suppressed[0]) assertIs(suppressed[1]) } @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) } assertIs(exception) val suppressed = exception.suppressed assertEquals(2, suppressed.size) assertIs(suppressed[0]) assertIs(suppressed[1]) } @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() } } }