blob: ecdfde8bd11a6eff98dc35c529d562e7c94e7ec8 [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.experimental.exceptions
import kotlinx.coroutines.experimental.*
import org.junit.Test
import java.io.*
import kotlin.coroutines.experimental.*
import kotlin.test.*
class WithContextExceptionHandlingTest : TestBase() {
@Test
fun testCancellation() = runTest {
/*
* Continuation cancelled without cause
* Continuation itself throws ISE
* Result: ISE with suppressed CancellationException
*/
runCancellation(null, IllegalStateException()) { e ->
assertNull(e.cause)
val suppressed = e.suppressed()
assertTrue(suppressed.size == 1)
val cancellation = suppressed[0] as CancellationException
assertNull(cancellation.cause)
assertTrue(cancellation.suppressed().isEmpty())
}
}
@Test
fun testCancellationWithException() = runTest {
/*
* Continuation cancelled with IOE
* Continuation itself throws ISE
* Result: ISE with suppressed CancellationException(IOE)
*/
val cancellationCause = IOException()
runCancellation(cancellationCause, IllegalStateException()) { e ->
assertNull(e.cause)
val suppressed = e.suppressed()
assertTrue(suppressed.size == 1)
val cancellation = suppressed[0] as CancellationException
assertSame(cancellation.cause, cancellationCause)
assertTrue(cancellationCause.suppressed().isEmpty())
}
}
@Test
fun testSameException() = runTest {
/*
* Continuation cancelled with ISE
* Continuation itself throws the same ISE
* Result: ISE
*/
val cancellationCause = IllegalStateException()
runCancellation(cancellationCause, cancellationCause) { e ->
assertNull(e.cause)
val suppressed = e.suppressed()
assertTrue(suppressed.isEmpty())
}
}
@Test
fun testSameCancellation() = runTest {
/*
* Continuation cancelled with CancellationException
* Continuation itself throws the same CE
* Result: CE
*/
val cancellationCause = CancellationException()
runCancellation(cancellationCause, cancellationCause) { e ->
assertNull(e.cause)
assertSame(e, cancellationCause)
val suppressed = e.suppressed()
assertTrue(suppressed.isEmpty())
}
}
@Test
fun testSameCancellationWithException() = runTest {
/*
* Continuation cancelled with CancellationException(IOE)
* Continuation itself throws the same IOE
* Result: IOE
*/
val cancellationCause = CancellationException()
val exception = IOException()
cancellationCause.initCause(exception)
runCancellation(cancellationCause, exception) { e ->
assertNull(e.cause)
assertSame(exception, e)
assertTrue(e.suppressed().isEmpty())
}
}
@Test
fun testConflictingCancellation() = runTest {
/*
* Continuation cancelled with ISE
* Continuation itself throws CE(IOE)
* Result: CE(IOE) with suppressed JCE(ISE)
*/
val cancellationCause = IllegalStateException()
val thrown = CancellationException()
thrown.initCause(IOException())
runCancellation(cancellationCause, thrown) { e ->
assertSame(thrown, e)
assertEquals(1, thrown.suppressed().size)
val suppressed = thrown.suppressed()[0]
assertTrue(suppressed is JobCancellationException)
assertTrue(suppressed.cause is IllegalStateException)
}
}
@Test
fun testConflictingCancellation2() = runTest {
/*
* Continuation cancelled with ISE
* Continuation itself throws CE
* Result: CE with suppressed JCE(ISE)
*/
val cancellationCause = IllegalStateException()
val thrown = CancellationException()
runCancellation(cancellationCause, thrown) { e ->
assertSame(thrown, e)
assertEquals(1, thrown.suppressed().size)
val suppressed = thrown.suppressed()[0]
assertTrue(suppressed is JobCancellationException)
assertTrue(suppressed.cause is IllegalStateException)
}
}
@Test
fun testConflictingCancellation3() = runTest {
/*
* Continuation cancelled with CE
* Continuation itself throws CE
* Result: CE
*/
val cancellationCause = CancellationException()
val thrown = CancellationException()
runCancellation(cancellationCause, thrown) { e ->
assertSame(thrown, e)
assertNull(e.cause)
assertTrue(e.suppressed().isEmpty())
}
}
@Test
fun testConflictingCancellationWithSameException() = runTest {
val cancellationCause = IllegalStateException()
val thrown = CancellationException()
/*
* Continuation cancelled with ISE
* Continuation itself throws CE with the same ISE as a cause
* Result: CE(ISE)
*/
thrown.initCause(cancellationCause)
runCancellation(cancellationCause, thrown) { e ->
assertSame(thrown, e)
assertSame(cancellationCause, e.cause)
assertEquals(0, thrown.suppressed().size)
}
}
@Test
fun testThrowingCancellation() = runTest {
val thrown = CancellationException()
runThrowing(thrown) { e ->
assertSame(thrown, e)
}
}
@Test
fun testThrowingCancellationWithCause() = runTest {
val thrown = CancellationException()
thrown.initCause(IOException())
runThrowing(thrown) { e ->
assertSame(thrown, e)
}
}
@Test
fun testCancel() = runTest {
runOnlyCancellation(null) { e ->
assertNull(e.cause)
assertTrue(e.suppressed().isEmpty())
}
}
@Test
fun testCancelWithCause() = runTest {
val cause = IOException()
runOnlyCancellation(cause) { e ->
assertSame(cause, e.cause)
assertTrue(e.suppressed().isEmpty())
}
}
@Test
fun testCancelWithCancellationException() = runTest {
val cause = CancellationException()
runThrowing(cause) { e ->
assertSame(cause, e)
assertNull(e.cause)
assertTrue(e.suppressed().isEmpty())
}
}
private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
return object : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatcher.dispatch(context, block)
}
}
}
private suspend inline fun <reified T : Exception> runCancellation(
cancellationCause: Exception?,
thrownException: T,
exceptionChecker: (T) -> Unit
) {
expect(1)
val job = Job()
job.cancel(cancellationCause)
try {
withContext(wrapperDispatcher(coroutineContext) + job, CoroutineStart.ATOMIC) {
require(!isActive) // already cancelled
expect(2)
throw thrownException
}
} catch (e: Exception) {
assertTrue(e is T)
exceptionChecker(e as T)
finish(3)
return
}
fail()
}
private suspend inline fun <reified T : Exception> runThrowing(
thrownException: T,
exceptionChecker: (T) -> Unit
) {
expect(1)
try {
withContext(wrapperDispatcher(coroutineContext), CoroutineStart.ATOMIC) {
require(isActive)
expect(2)
throw thrownException
}
} catch (e: Exception) {
assertTrue(e is T)
exceptionChecker(e as T)
finish(3)
return
}
fail()
}
private suspend inline fun runOnlyCancellation(
cancellationCause: Exception?,
exceptionChecker: (CancellationException) -> Unit
) {
expect(1)
val job = Job()
job.cancel(cancellationCause)
try {
withContext(wrapperDispatcher(coroutineContext) + job, CoroutineStart.ATOMIC) {
require(!isActive) // is already cancelled
expect(2)
}
} catch (e: Exception) {
assertTrue(e is CancellationException)
exceptionChecker(e as CancellationException)
finish(3)
return
}
fail()
}
}