33
44package kotlinx.coroutines.test
55
6+ import kotlinx.atomicfu.atomic
67import kotlinx.coroutines.*
7- import kotlinx.coroutines.flow.*
88import kotlinx.coroutines.selects.*
99import kotlin.coroutines.*
1010import kotlin.jvm.*
@@ -308,12 +308,17 @@ public fun TestScope.runTest(
308308): TestResult = asSpecificImplementation().let { scope ->
309309 scope.enter()
310310 createTestResult {
311+ val testBodyFinished = AtomicBoolean (false )
311312 /* * TODO: moving this [AbstractCoroutine.start] call outside [createTestResult] fails on JS. */
312313 scope.start(CoroutineStart .UNDISPATCHED , scope) {
313314 /* we're using `UNDISPATCHED` to avoid the event loop, but we do want to set up the timeout machinery
314315 before any code executes, so we have to park here. */
315316 yield ()
316- testBody()
317+ try {
318+ testBody()
319+ } finally {
320+ testBodyFinished.value = true
321+ }
317322 }
318323 var timeoutError: Throwable ? = null
319324 var cancellationException: CancellationException ? = null
@@ -336,17 +341,15 @@ public fun TestScope.runTest(
336341 if (exception is TimeoutCancellationException ) {
337342 dumpCoroutines()
338343 val activeChildren = scope.children.filter(Job ::isActive).toList()
339- val completionCause = if (scope.isCancelled) scope.tryGetCompletionCause() else null
340- var message = " After waiting for $timeout "
341- if (completionCause == null )
342- message + = " , the test coroutine is not completing"
343- if (activeChildren.isNotEmpty())
344- message + = " , there were active child jobs: $activeChildren "
345- if (completionCause != null && activeChildren.isEmpty()) {
346- message + = if (scope.isCompleted)
347- " , the test coroutine completed"
348- else
349- " , the test coroutine was not completed"
344+ val message = " After waiting for $timeout , " + when {
345+ testBodyFinished.value && activeChildren.isNotEmpty() ->
346+ " there were active child jobs: $activeChildren . " +
347+ " Use `TestScope.backgroundScope` " +
348+ " to launch the coroutines that need to be cancelled when the test body finishes"
349+ testBodyFinished.value ->
350+ " the test completed, but only after the timeout"
351+ else ->
352+ " the test body did not run to completion"
350353 }
351354 timeoutError = UncompletedCoroutinesError (message)
352355 cancellationException = CancellationException (" The test timed out" )
@@ -603,3 +606,11 @@ public fun TestScope.runTestLegacy(
603606 marker : Int ,
604607 unused2 : Any? ,
605608): TestResult = runTest(dispatchTimeoutMs = if (marker and 1 != 0 ) dispatchTimeoutMs else 60_000L , testBody)
609+
610+ // Remove after https://youtrack.jetbrains.com/issue/KT-62423/
611+ private class AtomicBoolean (initial : Boolean ) {
612+ private val container = atomic(initial)
613+ var value: Boolean
614+ get() = container.value
615+ set(value: Boolean ) { container.value = value }
616+ }
0 commit comments