blob: aeb0f358824b42502b328709289bbcd55686803e [file] [log] [blame]
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.test
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
/**
* Access uncaught coroutine exceptions captured during test execution.
*/
@Deprecated(
"Deprecated for removal without a replacement. " +
"Consider whether the default mechanism of handling uncaught exceptions is sufficient. " +
"If not, try writing your own `CoroutineExceptionHandler` and " +
"please report your use case at https://github.com/Kotlin/kotlinx.coroutines/issues.",
level = DeprecationLevel.WARNING
)
// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
public interface UncaughtExceptionCaptor {
/**
* List of uncaught coroutine exceptions.
*
* The returned list is a copy of the currently caught exceptions.
* During [cleanupTestCoroutines] the first element of this list is rethrown if it is not empty.
*/
public val uncaughtExceptions: List<Throwable>
/**
* Call after the test completes to ensure that there were no uncaught exceptions.
*
* The first exception in uncaughtExceptions is rethrown. All other exceptions are
* printed using [Throwable.printStackTrace].
*
* @throws Throwable the first uncaught exception, if there are any uncaught exceptions.
*/
public fun cleanupTestCoroutines()
}
/**
* An exception handler that captures uncaught exceptions in tests.
*/
@Suppress("DEPRECATION")
@Deprecated(
"Deprecated for removal without a replacement. " +
"It may be to define one's own `CoroutineExceptionHandler` if you just need to handle '" +
"uncaught exceptions without a special `TestCoroutineScope` integration.", level = DeprecationLevel.WARNING
)
// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
public class TestCoroutineExceptionHandler :
AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, UncaughtExceptionCaptor {
private val _exceptions = mutableListOf<Throwable>()
private val _lock = SynchronizedObject()
private var _coroutinesCleanedUp = false
@Suppress("INVISIBLE_MEMBER")
override fun handleException(context: CoroutineContext, exception: Throwable) {
synchronized(_lock) {
if (_coroutinesCleanedUp) {
handleCoroutineExceptionImpl(context, exception)
}
_exceptions += exception
}
}
public override val uncaughtExceptions: List<Throwable>
get() = synchronized(_lock) { _exceptions.toList() }
public override fun cleanupTestCoroutines() {
synchronized(_lock) {
_coroutinesCleanedUp = true
val exception = _exceptions.firstOrNull() ?: return
// log the rest
_exceptions.drop(1).forEach { it.printStackTrace() }
throw exception
}
}
}