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.WARNING 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 @Deprecated( 46 "Deprecated for removal without a replacement. " + 47 "It may be to define one's own `CoroutineExceptionHandler` if you just need to handle '" + 48 "uncaught exceptions without a special `TestCoroutineScope` integration.", level = DeprecationLevel.WARNING 49 ) 50 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 51 public class TestCoroutineExceptionHandler : 52 AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, UncaughtExceptionCaptor { 53 private val _exceptions = mutableListOf<Throwable>() 54 private val _lock = SynchronizedObject() 55 private var _coroutinesCleanedUp = false 56 57 @Suppress("INVISIBLE_MEMBER") 58 override fun handleException(context: CoroutineContext, exception: Throwable) { 59 synchronized(_lock) { 60 if (_coroutinesCleanedUp) { 61 handleCoroutineExceptionImpl(context, exception) 62 } 63 _exceptions += exception 64 } 65 } 66 67 public override val uncaughtExceptions: List<Throwable> 68 get() = synchronized(_lock) { _exceptions.toList() } 69 70 public override fun cleanupTestCoroutines() { 71 synchronized(_lock) { 72 _coroutinesCleanedUp = true 73 val exception = _exceptions.firstOrNull() ?: return 74 // log the rest 75 _exceptions.drop(1).forEach { it.printStackTrace() } 76 throw exception 77 } 78 } 79 } 80