package kotlinx.coroutines.testing.exceptions import kotlinx.coroutines.* import java.io.* import java.util.* import kotlin.contracts.* import kotlin.coroutines.* import kotlin.test.* inline fun checkException(exception: Throwable) { assertIs(exception) assertTrue(exception.suppressed.isEmpty()) assertNull(exception.cause) } fun checkCycles(t: Throwable) { val sw = StringWriter() t.printStackTrace(PrintWriter(sw)) assertFalse(sw.toString().contains("CIRCULAR REFERENCE")) } class CapturingHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { private var unhandled: ArrayList? = ArrayList() override fun handleException(context: CoroutineContext, exception: Throwable) = synchronized(this) { unhandled!!.add(exception) } fun getException(): Throwable = synchronized(this) { val size = unhandled!!.size assert(size == 1) { "Expected one unhandled exception, but have $size: $unhandled" } return unhandled!![0].also { unhandled = null } } } fun captureExceptionsRun( context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> Unit ): Throwable { val handler = CapturingHandler() runBlocking(context + handler, block = block) return handler.getException() } @OptIn(ExperimentalContracts::class) suspend inline fun assertCallsExceptionHandlerWith( crossinline operation: suspend (CoroutineExceptionHandler) -> Unit): E { contract { callsInPlace(operation, InvocationKind.EXACTLY_ONCE) } val handler = CapturingHandler() return withContext(handler) { operation(handler) assertIs(handler.getException()) } }