Skip to content

Commit 75356b3

Browse files
committed
Fixes
1 parent 1634a9f commit 75356b3

15 files changed

+246
-116
lines changed

kotlinx-coroutines-test/common/src/DelayController.kt

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4+
@file:Suppress("DEPRECATION")
45

56
package kotlinx.coroutines.test
67

@@ -102,7 +103,10 @@ public interface DelayController {
102103
* This is useful when testing functions that start a coroutine. By pausing the dispatcher assertions or
103104
* setup may be done between the time the coroutine is created and started.
104105
*/
105-
@ExperimentalCoroutinesApi
106+
@Deprecated(
107+
"Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
108+
level = DeprecationLevel.WARNING
109+
)
106110
public suspend fun pauseDispatcher(block: suspend () -> Unit)
107111

108112
/**
@@ -111,7 +115,10 @@ public interface DelayController {
111115
* When paused, the dispatcher will not execute any coroutines automatically, and you must call [runCurrent] or
112116
* [advanceTimeBy], or [advanceUntilIdle] to execute coroutines.
113117
*/
114-
@ExperimentalCoroutinesApi
118+
@Deprecated(
119+
"Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
120+
level = DeprecationLevel.WARNING
121+
)
115122
public fun pauseDispatcher()
116123

117124
/**
@@ -121,12 +128,15 @@ public interface DelayController {
121128
* time and execute coroutines scheduled in the future use, one of [advanceTimeBy],
122129
* or [advanceUntilIdle].
123130
*/
124-
@ExperimentalCoroutinesApi
131+
@Deprecated(
132+
"Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
133+
level = DeprecationLevel.WARNING
134+
)
125135
public fun resumeDispatcher()
126136
}
127137

128138
internal interface SchedulerAsDelayController : DelayController {
129-
public val scheduler: TestCoroutineScheduler
139+
val scheduler: TestCoroutineScheduler
130140

131141
/** @suppress */
132142
@Deprecated(
@@ -178,7 +188,7 @@ internal interface SchedulerAsDelayController : DelayController {
178188
scheduler.runCurrent()
179189
if (!scheduler.isIdle()) {
180190
throw UncompletedCoroutinesError(
181-
"Unfinished coroutines during teardown. Ensure all coroutines are" +
191+
"Unfinished coroutines during tear-down. Ensure all coroutines are" +
182192
" completed or cancelled by your test."
183193
)
184194
}

kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import kotlin.coroutines.*
2121
*
2222
* @see DelayController
2323
*/
24-
@ExperimentalCoroutinesApi
24+
@Deprecated("The execution order of `TestCoroutineDispatcher` can be confusing, and the mechanism of " +
25+
"pausing is typically misunderstood. Please use `StandardTestDispatcher` or `UnconfinedTestDispatcher` instead.",
26+
level = DeprecationLevel.WARNING)
2527
public class TestCoroutineDispatcher(public override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler()):
2628
TestDispatcher(), Delay, SchedulerAsDelayController
2729
{

kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ public class UnconfinedTestDispatcher(
8686
*
8787
* One can additionally pass a [name] in order to more easily distinguish this dispatcher during debugging.
8888
*
89-
* @see UnconfinedTestDispatcher for a dispatcher that immediately enters [launch] and [async] blocks and is not
90-
* confined to any particular thread.
89+
* @see UnconfinedTestDispatcher for a dispatcher that is not confined to any particular thread.
9190
*/
9291
@ExperimentalCoroutinesApi
9392
public class StandardTestDispatcher(

kotlinx-coroutines-test/common/src/TestCoroutineScope.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ private class TestCoroutineScopeImpl(
4444
private val initialJobs = coroutineContext.activeJobs()
4545

4646
override fun cleanupTestCoroutines() {
47-
coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutinesCaptor()
4847
val delayController = coroutineContext.delayController
4948
if (delayController != null) {
5049
delayController.cleanupTestCoroutines()
50+
coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutinesCaptor()
5151
} else {
5252
testScheduler.runCurrent()
53+
coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutinesCaptor()
5354
if (!testScheduler.isIdle()) {
5455
throw UncompletedCoroutinesError(
5556
"Unfinished coroutines during teardown. Ensure all coroutines are" +

kotlinx-coroutines-test/common/test/Helpers.kt

+30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package kotlinx.coroutines.test
66

7+
import kotlinx.atomicfu.*
78
import kotlin.test.*
89
import kotlin.time.*
910

@@ -35,3 +36,32 @@ inline fun <T> assertRunsFast(block: () -> T): T = assertRunsFast(Duration.secon
3536
expect fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult
3637

3738
class TestException(message: String? = null): Exception(message)
39+
40+
/**
41+
* A class inheriting from which allows to check the execution order inside tests.
42+
*
43+
* @see TestBase
44+
*/
45+
open class OrderedExecutionTestBase {
46+
private val actionIndex = atomic(0)
47+
private val finished = atomic(false)
48+
49+
/** Expect the next action to be [index] in order. */
50+
protected fun expect(index: Int) {
51+
val wasIndex = actionIndex.incrementAndGet()
52+
check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
53+
}
54+
55+
/** Expect this action to be final, with the given [index]. */
56+
protected fun finish(index: Int) {
57+
expect(index)
58+
check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" }
59+
}
60+
61+
@AfterTest
62+
fun ensureFinishCalls() {
63+
assertTrue(finished.value || actionIndex.value == 0, "Expected `finish` to be called")
64+
}
65+
}
66+
67+
internal fun <T> T.void() { }

kotlinx-coroutines-test/common/test/RunTestTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class RunTestTest {
5757
delay(2000)
5858
}
5959
val deferred = async {
60-
val job = launch(TestCoroutineDispatcher(testScheduler)) {
60+
val job = launch(StandardTestDispatcher(testScheduler)) {
6161
launch {
6262
delay(500)
6363
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.flow.*
9+
import kotlin.test.*
10+
11+
class StandardTestDispatcherTest: OrderedExecutionTestBase() {
12+
13+
private val scope = createTestCoroutineScope(StandardTestDispatcher())
14+
15+
@AfterTest
16+
fun cleanup() = scope.cleanupTestCoroutines()
17+
18+
/** Tests that the [StandardTestDispatcher] follows an execution order similar to `runBlocking`. */
19+
@Test
20+
fun testFlowsNotSkippingValues() = scope.launch {
21+
// https://github.com/Kotlin/kotlinx.coroutines/issues/1626#issuecomment-554632852
22+
val list = flowOf(1).onStart { emit(0) }
23+
.combine(flowOf("A")) { int, str -> "$str$int" }
24+
.toList()
25+
assertEquals(list, listOf("A0", "A1"))
26+
}.void()
27+
28+
/** Tests that each [launch] gets dispatched. */
29+
@Test
30+
fun testLaunchDispatched() = scope.launch {
31+
expect(1)
32+
launch {
33+
expect(3)
34+
}
35+
finish(2)
36+
}.void()
37+
38+
/** Tests that dispatching is done in a predictable order and [yield] puts this task at the end of the queue. */
39+
@Test
40+
fun testYield() = scope.launch {
41+
expect(1)
42+
scope.launch {
43+
expect(3)
44+
yield()
45+
expect(6)
46+
}
47+
scope.launch {
48+
expect(4)
49+
yield()
50+
finish(7)
51+
}
52+
expect(2)
53+
yield()
54+
expect(5)
55+
}.void()
56+
57+
}

0 commit comments

Comments
 (0)