From c81dfa11fe45341e0e255ebd852c69a010634af1 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 3 May 2023 11:46:55 +0200 Subject: [PATCH 1/2] Support disabling reporting of uncaught exceptions in tests The solution for #1205 may be undesirable for those who already have their own solution, like setting the default exception handlers. In this case, there's a usability issue without the corresponding benefit: instead of all tests being ran and the exceptions from them being reported, unrelated tests may fail, making looking for problems more difficult. This is probably a very rare issue, so we don't provide public API for that, instead introducing a need-to-know internal variable. --- .../api/kotlinx-coroutines-test.api | 2 ++ kotlinx-coroutines-test/common/src/TestScope.kt | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api index 00d9fb659e..a41502b2e1 100644 --- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api +++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api @@ -121,9 +121,11 @@ public final class kotlinx/coroutines/test/TestScopeKt { public static final fun advanceTimeBy (Lkotlinx/coroutines/test/TestScope;J)V public static final fun advanceTimeBy-HG0u8IE (Lkotlinx/coroutines/test/TestScope;J)V public static final fun advanceUntilIdle (Lkotlinx/coroutines/test/TestScope;)V + public static final fun getCatchNonTestRelatedExceptions ()Z public static final fun getCurrentTime (Lkotlinx/coroutines/test/TestScope;)J public static final fun getTestTimeSource (Lkotlinx/coroutines/test/TestScope;)Lkotlin/time/TimeSource$WithComparableMarks; public static final fun runCurrent (Lkotlinx/coroutines/test/TestScope;)V + public static final fun setCatchNonTestRelatedExceptions (Z)V } public abstract interface class kotlinx/coroutines/test/UncaughtExceptionCaptor { diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index e72f0f14f3..fa6e3930d8 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -233,7 +233,9 @@ internal class TestScopeImpl(context: CoroutineContext) : * after the previous one, and learning about such exceptions as soon is possible is nice. */ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") run { ensurePlatformExceptionHandlerLoaded(ExceptionCollector) } - ExceptionCollector.addOnExceptionCallback(lock, this::reportException) + if (catchNonTestRelatedExceptions) { + ExceptionCollector.addOnExceptionCallback(lock, this::reportException) + } uncaughtExceptions } if (exceptions.isNotEmpty()) { @@ -321,3 +323,12 @@ internal class UncaughtExceptionsBeforeTest : IllegalStateException( */ @ExperimentalCoroutinesApi internal class UncompletedCoroutinesError(message: String) : AssertionError(message) + +/** + * A flag that controls whether [TestScope] should attempt to catch arbitrary exceptions flying through the system. + * If it is enabled, then any exception that is not caught by the user code will be reported as a test failure. + * By default, it is enabled, but some tests may want to disable it to test the behavior of the system when they have + * their own exception handling procedures. + */ +@PublishedApi +internal var catchNonTestRelatedExceptions: Boolean = true From 9939357f9c74da08b426070c927f8c7674e4fc7e Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 3 May 2023 14:43:15 +0200 Subject: [PATCH 2/2] Ignore removals of exception callbacks if none were registered --- .../common/src/internal/ExceptionCollector.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt index 90fa763523..70fcb9487f 100644 --- a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt +++ b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt @@ -43,8 +43,10 @@ internal object ExceptionCollector : AbstractCoroutineContextElement(CoroutineEx * Unregisters the callback associated with [owner]. */ fun removeOnExceptionCallback(owner: Any) = synchronized(lock) { - val existingValue = callbacks.remove(owner) - check(existingValue !== null) + if (enabled) { + val existingValue = callbacks.remove(owner) + check(existingValue !== null) + } } /**