Skip to content

Commit b7f4216

Browse files
committed
Fixes
1 parent 51950b5 commit b7f4216

14 files changed

+242
-113
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

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

106110
/**
@@ -109,7 +113,10 @@ public interface DelayController {
109113
* When paused, the dispatcher will not execute any coroutines automatically, and you must call [runCurrent] or
110114
* [advanceTimeBy], or [advanceUntilIdle] to execute coroutines.
111115
*/
112-
@ExperimentalCoroutinesApi
116+
@Deprecated(
117+
"Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
118+
level = DeprecationLevel.WARNING
119+
)
113120
public fun pauseDispatcher()
114121

115122
/**
@@ -119,12 +126,15 @@ public interface DelayController {
119126
* time and execute coroutines scheduled in the future use, one of [advanceTimeBy],
120127
* or [advanceUntilIdle].
121128
*/
122-
@ExperimentalCoroutinesApi
129+
@Deprecated(
130+
"Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
131+
level = DeprecationLevel.WARNING
132+
)
123133
public fun resumeDispatcher()
124134
}
125135

126136
internal interface SchedulerAsDelayController: DelayController {
127-
public val scheduler: TestCoroutineScheduler
137+
val scheduler: TestCoroutineScheduler
128138

129139
/** @suppress */
130140
@Deprecated("This property delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
@@ -167,7 +177,7 @@ internal interface SchedulerAsDelayController: DelayController {
167177
scheduler.runCurrent()
168178
if (!scheduler.isIdle()) {
169179
throw UncompletedCoroutinesError(
170-
"Unfinished coroutines during teardown. Ensure all coroutines are" +
180+
"Unfinished coroutines during tear-down. Ensure all coroutines are" +
171181
" completed or cancelled by your test."
172182
)
173183
}

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/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)