blob: 4d54950df153c6e76981f074fc7629216ae3f8d2 [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 org.junit.runner.*
import org.junit.runners.*
import java.io.*
import kotlin.coroutines.experimental.*
import kotlin.test.*
@RunWith(Parameterized::class)
class WithContextExceptionHandlingTest(private val withContext: Boolean) : TestBase() {
companion object {
@Parameterized.Parameters(name = "withContext={0}")
@JvmStatic
fun params(): Collection<Array<Any>> = listOf<Array<Any>>(arrayOf(true), arrayOf(false))
}
@Test
fun testCancellation() = runTest {
/*
* context cancelled without cause
* code itself throws ISE
* Result: ISE
*/
runCancellation(null, IllegalStateException()) { e ->
assertTrue(e is IllegalStateException)
assertNull(e.cause)
val suppressed = e.suppressed
assertTrue(suppressed.isEmpty())
}
}
@Test
fun testCancellationWithException() = runTest {
/*
* context cancelled with IOE
* block itself throws ISE
* Result: IOE with suppressed ISE
*/
val cancellationCause = IOException()
runCancellation(cancellationCause, IllegalStateException()) { e ->
assertTrue(e is IOException)
assertNull(e.cause)
val suppressed = e.suppressed
assertEquals(suppressed.size, 1)
assertTrue(suppressed[0] is IllegalStateException)
}
}
@Test
fun testSameException() = runTest {
/*
* context cancelled with ISE
* block itself throws the same ISE
* Result: ISE
*/
val cancellationCause = IllegalStateException()
runCancellation(cancellationCause, cancellationCause) { e ->
assertTrue(e is IllegalStateException)
assertNull(e.cause)
val suppressed = e.suppressed
assertTrue(suppressed.isEmpty())
}
}
@Test
fun testSameCancellation() = runTest {
/*
* context cancelled with CancellationException
* block itself throws the same CE
* Result: CE
*/
val cancellationCause = CancellationException()
runCancellation(cancellationCause, cancellationCause) { e ->
assertSame(e, cancellationCause)
assertNull(e.cause)
val suppressed = e.suppressed
assertTrue(suppressed.isEmpty())
}
}
@Test
fun testSameCancellationWithException() = runTest {
/*
* context cancelled with CancellationException(IOE)
* block itself throws the same IOE
* Result: IOE
*/
val cancellationCause = CancellationException()
val exception = IOException()
cancellationCause.initCause(exception)
runCancellation(cancellationCause, exception) { e ->
assertSame(exception, e)
assertNull(e.cause)
assertTrue(e.suppressed.isEmpty())
}
}
@Test
fun testConflictingCancellation() = runTest {
/*
* context cancelled with ISE
* block itself throws CE(IOE)
* Result: ISE suppressed IOE
*/
val cancellationCause = IllegalStateException()
val thrown = CancellationException()
thrown.initCause(IOException())
runCancellation(cancellationCause, thrown) { e ->
assertSame(cancellationCause, e)
val suppressed = e.suppressed
assertEquals(1, suppressed.size)
assertTrue(suppressed[0] is IOException)
}
}
@Test
fun testConflictingCancellation2() = runTest {
/*
* context cancelled with ISE
* block itself throws CE
* Result: ISE
*/
val cancellationCause = IllegalStateException()
val thrown = CancellationException()
runCancellation(cancellationCause, thrown) { e ->
assertSame(cancellationCause, e)
val suppressed = e.suppressed
assertTrue(suppressed.isEmpty())
}
}
@Test
fun testConflictingCancellation3() = runTest {
/*
* context cancelled with CE
* block itself throws CE
* Result: CE
*/
val cancellationCause = CancellationException()
val thrown = CancellationException()
runCancellation(cancellationCause, thrown) { e ->
assertSame(cancellationCause, e)
assertNull(e.cause)
assertTrue(e.suppressed.isEmpty())
}
}
@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 ->
checkException<IOException>(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)
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 fun runCancellation(
cancellationCause: Exception?,
thrownException: Throwable,
exceptionChecker: (Throwable) -> Unit
) {
expect(1)
try {
withCtx(wrapperDispatcher(coroutineContext)) { job ->
require(isActive) // not cancelled yet
job.cancel(cancellationCause)
require(!isActive) // now cancelled
expect(2)
throw thrownException
}
} catch (e: Throwable) {
exceptionChecker(e)
finish(3)
return
}
fail()
}
private suspend fun runThrowing(
thrownException: Throwable,
exceptionChecker: (Throwable) -> Unit
) {
expect(1)
try {
withCtx(wrapperDispatcher(coroutineContext).minusKey(Job)) {
require(isActive)
expect(2)
throw thrownException
}
} catch (e: Throwable) {
exceptionChecker(e)
finish(3)
return
}
fail()
}
private suspend fun withCtx(context: CoroutineContext, job: Job = Job(), block: suspend CoroutineScope.(Job) -> Nothing) {
if (withContext) {
withContext(context + job) {
block(job)
}
} else {
CoroutineScope(coroutineContext).async(context + job) {
block(job)
}.await()
}
}
private suspend fun runOnlyCancellation(
cancellationCause: Throwable?,
exceptionChecker: (Throwable) -> Unit
) {
expect(1)
val job = Job()
try {
withContext(wrapperDispatcher(coroutineContext) + job) {
require(isActive) // still active
job.cancel(cancellationCause)
require(!isActive) // is already cancelled
expect(2)
}
} catch (e: Throwable) {
exceptionChecker(e)
finish(3)
return
}
fail()
}
}