@@ -44,7 +44,7 @@ import kotlin.coroutines.*
44
44
*/
45
45
@Deprecated(" Use `runTest` instead to support completing from other dispatchers." , level = DeprecationLevel .WARNING )
46
46
public fun runBlockingTest (context : CoroutineContext = EmptyCoroutineContext , testBody : suspend TestCoroutineScope .() -> Unit ) {
47
- val scope = createTestCoroutineScope(TestCoroutineDispatcher () + context)
47
+ val scope = createTestCoroutineScope(TestCoroutineDispatcher () + SupervisorJob () + context)
48
48
val scheduler = scope.testScheduler
49
49
val deferred = scope.async {
50
50
scope.testBody()
@@ -179,32 +179,16 @@ public fun runTest(
179
179
): TestResult {
180
180
if (context[RunningInRunTest ] != null )
181
181
throw IllegalStateException (" Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details." )
182
- val testScope = createTestCoroutineScope(context + RunningInRunTest ())
182
+ val testScope = TestBodyCoroutine < Unit >( createTestCoroutineScope(context + RunningInRunTest () ))
183
183
val scheduler = testScope.testScheduler
184
+ testScope.start(CoroutineStart .DEFAULT , testScope) {
185
+ testBody()
186
+ }
184
187
return createTestResult {
185
- val deferred = testScope.async {
186
- testScope.testBody()
187
- }
188
188
var completed = false
189
189
while (! completed) {
190
- while (scheduler.tryRunNextTask()) {
191
- if (deferred.isCompleted && deferred.getCompletionExceptionOrNull() != null && testScope.isActive) {
192
- /* *
193
- * Here, we already know how the test will finish: it will throw
194
- * [Deferred.getCompletionExceptionOrNull]. Therefore, we won't care if there are uncompleted jobs,
195
- * and may as well just exit right here. However, in order to lower the surprise factor, we
196
- * cancel the child jobs here and wait for them to finish instead of dropping them: there could be
197
- * some cleanup procedures involved, and not having finalizers run could mean leaking resources.
198
- *
199
- * Another approach to take if this turns out not to be enough and some child jobs still fail is to
200
- * only make at most a fixed number of [TestCoroutineScheduler.tryRunNextTask] once we detect the
201
- * failure with which the test will finish. This has the downside that there is still some
202
- * negligible risk of not running the finalizers.
203
- */
204
- testScope.cancel()
205
- }
206
- }
207
- if (deferred.isCompleted) {
190
+ scheduler.advanceUntilIdle()
191
+ if (testScope.isCompleted) {
208
192
/* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
209
193
non-trivial dispatches. */
210
194
completed = true
@@ -213,7 +197,7 @@ public fun runTest(
213
197
try {
214
198
withTimeout(dispatchTimeoutMs) {
215
199
select<Unit > {
216
- deferred.onAwait {
200
+ testScope.onJoin {
217
201
completed = true
218
202
}
219
203
scheduler.onDispatchEvent {
@@ -230,7 +214,7 @@ public fun runTest(
230
214
throw UncompletedCoroutinesError (" The test coroutine was not completed after waiting for $dispatchTimeoutMs ms" )
231
215
}
232
216
}
233
- deferred .getCompletionExceptionOrNull()?.let {
217
+ testScope .getCompletionExceptionOrNull()?.let {
234
218
try {
235
219
testScope.cleanupTestCoroutines()
236
220
} catch (e: UncompletedCoroutinesError ) {
@@ -254,7 +238,7 @@ internal expect fun createTestResult(testProcedure: suspend () -> Unit): TestRes
254
238
* Runs a test in a [TestCoroutineScope] based on this one.
255
239
*
256
240
* Calls [runTest] using a coroutine context from this [TestCoroutineScope]. The [TestCoroutineScope] used to run
257
- * [block] will be different from this one, but will reuse its [Job]; therefore, even if calling
241
+ * [block] will be different from this one, but will use its [Job] as a parent ; therefore, even if calling
258
242
* [TestCoroutineScope.cleanupTestCoroutines] on this scope were to complete its job, [runTest] won't complete it at the
259
243
* end of the test.
260
244
*
@@ -291,3 +275,14 @@ private class RunningInRunTest: AbstractCoroutineContextElement(RunningInRunTest
291
275
/* * The default timeout to use when waiting for asynchronous completions of the coroutines managed by
292
276
* a [TestCoroutineScheduler]. */
293
277
private const val DEFAULT_DISPATCH_TIMEOUT_MS = 10_000L
278
+
279
+ private class TestBodyCoroutine <T >(
280
+ private val testScope : TestCoroutineScope ,
281
+ ) : AbstractCoroutine<T>(testScope.coroutineContext, initParentJob = true , active = true ), TestCoroutineScope
282
+ {
283
+ override val testScheduler get() = testScope.testScheduler
284
+
285
+ override fun cleanupTestCoroutines () = testScope.cleanupTestCoroutines()
286
+
287
+ override fun reportException (throwable : Throwable ) = testScope.reportException(throwable)
288
+ }
0 commit comments