1 /* 2 * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.coroutines.test 6 7 import kotlinx.coroutines.* 8 import kotlinx.coroutines.internal.* 9 import kotlin.coroutines.* 10 11 /** 12 * Access uncaught coroutine exceptions captured during test execution. 13 */ 14 @Deprecated( 15 "Deprecated for removal without a replacement. " + 16 "Consider whether the default mechanism of handling uncaught exceptions is sufficient. " + 17 "If not, try writing your own `CoroutineExceptionHandler` and " + 18 "please report your use case at https://github.com/Kotlin/kotlinx.coroutines/issues.", 19 level = DeprecationLevel.ERROR 20 ) 21 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 22 public interface UncaughtExceptionCaptor { 23 /** 24 * List of uncaught coroutine exceptions. 25 * 26 * The returned list is a copy of the currently caught exceptions. 27 * During [cleanupTestCoroutines] the first element of this list is rethrown if it is not empty. 28 */ 29 public val uncaughtExceptions: List<Throwable> 30 31 /** 32 * Call after the test completes to ensure that there were no uncaught exceptions. 33 * 34 * The first exception in uncaughtExceptions is rethrown. All other exceptions are 35 * printed using [Throwable.printStackTrace]. 36 * 37 * @throws Throwable the first uncaught exception, if there are any uncaught exceptions. 38 */ cleanupTestCoroutinesnull39 public fun cleanupTestCoroutines() 40 } 41 42 /** 43 * An exception handler that captures uncaught exceptions in tests. 44 */ 45 @Suppress("DEPRECATION_ERROR") 46 @Deprecated( 47 "Deprecated for removal without a replacement. " + 48 "It may be to define one's own `CoroutineExceptionHandler` if you just need to handle '" + 49 "uncaught exceptions without a special `TestCoroutineScope` integration.", level = DeprecationLevel.ERROR 50 ) 51 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 52 public class TestCoroutineExceptionHandler : 53 AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, UncaughtExceptionCaptor { 54 private val _exceptions = mutableListOf<Throwable>() 55 private val _lock = SynchronizedObject() 56 private var _coroutinesCleanedUp = false 57 58 @Suppress("INVISIBLE_MEMBER") 59 override fun handleException(context: CoroutineContext, exception: Throwable) { 60 synchronized(_lock) { 61 if (_coroutinesCleanedUp) { 62 handleUncaughtCoroutineException(context, exception) 63 } 64 _exceptions += exception 65 } 66 } 67 68 public override val uncaughtExceptions: List<Throwable> 69 get() = synchronized(_lock) { _exceptions.toList() } 70 71 public override fun cleanupTestCoroutines() { 72 synchronized(_lock) { 73 _coroutinesCleanedUp = true 74 val exception = _exceptions.firstOrNull() ?: return 75 // log the rest 76 _exceptions.drop(1).forEach { it.printStackTrace() } 77 throw exception 78 } 79 } 80 } 81