Skip to content

Commit b9ba46a

Browse files
committed
Improve tests
1 parent dd0e6ba commit b9ba46a

13 files changed

+149
-131
lines changed

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

+3-2
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

@@ -140,7 +141,7 @@ public interface DelayController {
140141
public class UncompletedCoroutinesError(message: String): AssertionError(message)
141142

142143
internal interface SchedulerAsDelayController: DelayController {
143-
public val scheduler: TestCoroutineScheduler
144+
val scheduler: TestCoroutineScheduler
144145

145146
/** @suppress */
146147
@Deprecated("This property delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
@@ -183,7 +184,7 @@ internal interface SchedulerAsDelayController: DelayController {
183184
scheduler.runCurrent()
184185
if (!scheduler.isIdle()) {
185186
throw UncompletedCoroutinesError(
186-
"Unfinished coroutines during teardown. Ensure all coroutines are" +
187+
"Unfinished coroutines during tear-down. Ensure all coroutines are" +
187188
" completed or cancelled by your test."
188189
)
189190
}

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

+29-1
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

@@ -32,4 +33,31 @@ inline fun <T> assertRunsFast(block: () -> T): T = assertRunsFast(Duration.secon
3233
/**
3334
* Passes [test] as an argument to [block], but as a function returning not a [TestResult] but [Unit].
3435
*/
35-
expect fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult
36+
expect fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult
37+
38+
/**
39+
* A class inheriting from which allows to check the execution order inside tests.
40+
*
41+
* @see TestBase
42+
*/
43+
open class OrderedExecutionTestBase {
44+
private val actionIndex = atomic(0)
45+
private val finished = atomic(false)
46+
47+
/** Expect the next action to be [index] in order. */
48+
protected fun expect(index: Int) {
49+
val wasIndex = actionIndex.incrementAndGet()
50+
check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
51+
}
52+
53+
/** Expect this action to be final, with the given [index]. */
54+
protected fun finish(index: Int) {
55+
expect(index)
56+
check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" }
57+
}
58+
59+
@AfterTest
60+
fun ensureFinishCalls() {
61+
assertTrue(finished.value || actionIndex.value == 0, "Expected `finish` to be called")
62+
}
63+
}

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

-23
This file was deleted.

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,27 @@
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+
/** Tests that the [StandardTestDispatcher] follows an execution order similar to `runBlocking`. */
14+
@Test
15+
fun testFlowsNotSkippingValues() {
16+
val scope = createTestCoroutineScope(StandardTestDispatcher())
17+
scope.launch {
18+
// https://github.com/Kotlin/kotlinx.coroutines/issues/1626#issuecomment-554632852
19+
val list = flowOf(1).onStart { emit(0) }
20+
.combine(flowOf("A")) { int, str -> "$str$int" }
21+
.toList()
22+
assertEquals(list, listOf("A0", "A1"))
23+
}
24+
scope.cleanupTestCoroutines()
25+
}
26+
27+
}

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

+77-57
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class TestCoroutineSchedulerTest {
1212
@Test
1313
fun testContextElement() = runTest {
1414
assertFailsWith<IllegalStateException> {
15-
withContext(TestCoroutineDispatcher()) {
15+
withContext(StandardTestDispatcher()) {
1616
}
1717
}
1818
}
@@ -44,28 +44,35 @@ class TestCoroutineSchedulerTest {
4444
/** Tests that if [TestCoroutineScheduler.advanceTimeBy] encounters an arithmetic overflow, all the tasks scheduled
4545
* until the moment [Long.MAX_VALUE] get run. */
4646
@Test
47-
fun testAdvanceTimeByEnormousDelays() = runTest {
48-
val initialDelay = 10L
49-
delay(initialDelay)
50-
assertEquals(initialDelay, currentTime)
51-
var enteredInfinity = false
52-
launch {
53-
delay(Long.MAX_VALUE - 1) // delay(Long.MAX_VALUE) does nothing
54-
assertEquals(Long.MAX_VALUE, currentTime)
55-
enteredInfinity = true
56-
}
57-
var enteredNearInfinity = false
58-
launch {
59-
delay(Long.MAX_VALUE - initialDelay - 1)
60-
assertEquals(Long.MAX_VALUE - 1, currentTime)
61-
enteredNearInfinity = true
47+
fun testAdvanceTimeByEnormousDelays() = forTestDispatchers {
48+
assertRunsFast {
49+
with (createTestCoroutineScope(it)) {
50+
launch {
51+
val initialDelay = 10L
52+
delay(initialDelay)
53+
assertEquals(initialDelay, currentTime)
54+
var enteredInfinity = false
55+
launch {
56+
delay(Long.MAX_VALUE - 1) // delay(Long.MAX_VALUE) does nothing
57+
assertEquals(Long.MAX_VALUE, currentTime)
58+
enteredInfinity = true
59+
}
60+
var enteredNearInfinity = false
61+
launch {
62+
delay(Long.MAX_VALUE - initialDelay - 1)
63+
assertEquals(Long.MAX_VALUE - 1, currentTime)
64+
enteredNearInfinity = true
65+
}
66+
testScheduler.advanceTimeBy(Long.MAX_VALUE)
67+
assertFalse(enteredInfinity)
68+
assertTrue(enteredNearInfinity)
69+
assertEquals(Long.MAX_VALUE, currentTime)
70+
testScheduler.runCurrent()
71+
assertTrue(enteredInfinity)
72+
}
73+
testScheduler.advanceUntilIdle()
74+
}
6275
}
63-
testScheduler.advanceTimeBy(Long.MAX_VALUE)
64-
assertFalse(enteredInfinity)
65-
assertTrue(enteredNearInfinity)
66-
assertEquals(Long.MAX_VALUE, currentTime)
67-
testScheduler.runCurrent()
68-
assertTrue(enteredInfinity)
6976
}
7077

7178
/** Tests the basic functionality of [TestCoroutineScheduler.advanceTimeBy]. */
@@ -124,48 +131,52 @@ class TestCoroutineSchedulerTest {
124131

125132
/** Tests that [TestCoroutineScheduler.runCurrent] will not run new tasks after the current time has advanced. */
126133
@Test
127-
fun testRunCurrentNotDrainingQueue() = assertRunsFast {
128-
val scheduler = TestCoroutineScheduler()
129-
val scope = createTestCoroutineScope(scheduler)
130-
var stage = 1
131-
scope.launch {
132-
delay(SLOW)
133-
launch {
134+
fun testRunCurrentNotDrainingQueue() = forTestDispatchers {
135+
assertRunsFast {
136+
val scheduler = it.scheduler
137+
val scope = createTestCoroutineScope(it)
138+
var stage = 1
139+
scope.launch {
134140
delay(SLOW)
135-
stage = 3
141+
launch {
142+
delay(SLOW)
143+
stage = 3
144+
}
145+
scheduler.advanceTimeBy(SLOW)
146+
stage = 2
136147
}
137148
scheduler.advanceTimeBy(SLOW)
138-
stage = 2
149+
assertEquals(1, stage)
150+
scheduler.runCurrent()
151+
assertEquals(2, stage)
152+
scheduler.runCurrent()
153+
assertEquals(3, stage)
139154
}
140-
scheduler.advanceTimeBy(SLOW)
141-
assertEquals(1, stage)
142-
scheduler.runCurrent()
143-
assertEquals(2, stage)
144-
scheduler.runCurrent()
145-
assertEquals(3, stage)
146155
}
147156

148157
/** Tests that [TestCoroutineScheduler.advanceUntilIdle] doesn't hang when itself running in a scheduler task. */
149158
@Test
150-
fun testNestedAdvanceUntilIdle() = assertRunsFast {
151-
val scheduler = TestCoroutineScheduler()
152-
val scope = createTestCoroutineScope(scheduler)
153-
var executed = false
154-
scope.launch {
155-
launch {
156-
delay(SLOW)
157-
executed = true
159+
fun testNestedAdvanceUntilIdle() = forTestDispatchers {
160+
assertRunsFast {
161+
val scheduler = it.scheduler
162+
val scope = createTestCoroutineScope(it)
163+
var executed = false
164+
scope.launch {
165+
launch {
166+
delay(SLOW)
167+
executed = true
168+
}
169+
scheduler.advanceUntilIdle()
158170
}
159171
scheduler.advanceUntilIdle()
172+
assertTrue(executed)
160173
}
161-
scheduler.advanceUntilIdle()
162-
assertTrue(executed)
163174
}
164175

165176
/** Tests [yield] scheduling tasks for future execution and not executing immediately. */
166177
@Test
167-
fun testYield() {
168-
val scope = createTestCoroutineScope()
178+
fun testYield() = forTestDispatchers {
179+
val scope = createTestCoroutineScope(it)
169180
var stage = 0
170181
scope.launch {
171182
yield()
@@ -205,8 +216,8 @@ class TestCoroutineSchedulerTest {
205216

206217
/** Tests that timeouts get triggered. */
207218
@Test
208-
fun testSmallTimeouts() {
209-
val scope = createTestCoroutineScope(TestCoroutineDispatcher())
219+
fun testSmallTimeouts() = forTestDispatchers {
220+
val scope = createTestCoroutineScope(it)
210221
scope.checkTimeout(true) {
211222
val half = SLOW / 2
212223
delay(half)
@@ -216,8 +227,8 @@ class TestCoroutineSchedulerTest {
216227

217228
/** Tests that timeouts don't get triggered if the code finishes in time. */
218229
@Test
219-
fun testLargeTimeouts() {
220-
val scope = createTestCoroutineScope()
230+
fun testLargeTimeouts() = forTestDispatchers {
231+
val scope = createTestCoroutineScope(it)
221232
scope.checkTimeout(false) {
222233
val half = SLOW / 2
223234
delay(half)
@@ -227,8 +238,8 @@ class TestCoroutineSchedulerTest {
227238

228239
/** Tests that timeouts get triggered if the code fails to finish in time asynchronously. */
229240
@Test
230-
fun testSmallAsynchronousTimeouts() {
231-
val scope = createTestCoroutineScope()
241+
fun testSmallAsynchronousTimeouts() = forTestDispatchers {
242+
val scope = createTestCoroutineScope(it)
232243
val deferred = CompletableDeferred<Unit>()
233244
scope.launch {
234245
val half = SLOW / 2
@@ -243,8 +254,8 @@ class TestCoroutineSchedulerTest {
243254

244255
/** Tests that timeouts don't get triggered if the code finishes in time, even if it does so asynchronously. */
245256
@Test
246-
fun testLargeAsynchronousTimeouts() {
247-
val scope = createTestCoroutineScope()
257+
fun testLargeAsynchronousTimeouts() = forTestDispatchers {
258+
val scope = createTestCoroutineScope(it)
248259
val deferred = CompletableDeferred<Unit>()
249260
scope.launch {
250261
val half = SLOW / 2
@@ -256,4 +267,13 @@ class TestCoroutineSchedulerTest {
256267
deferred.await()
257268
}
258269
}
270+
271+
private fun forTestDispatchers(block: (TestDispatcher) -> Unit): Unit =
272+
listOf(TestCoroutineDispatcher(), StandardTestDispatcher(), UnconfinedTestDispatcher()).forEach {
273+
try {
274+
block(it)
275+
} catch (e: Throwable) {
276+
throw RuntimeException("Test failed for dispatcher $it", e)
277+
}
278+
}
259279
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class TestCoroutineScopeTest {
2929
}
3030
// Reuses the scheduler that the dispatcher is linked to.
3131
run {
32-
val dispatcher = TestCoroutineDispatcher()
32+
val dispatcher = StandardTestDispatcher()
3333
val scope = createTestCoroutineScope(dispatcher)
3434
assertSame(dispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler])
3535
}
@@ -43,7 +43,7 @@ class TestCoroutineScopeTest {
4343
// Doesn't touch the passed dispatcher and the scheduler if they match.
4444
run {
4545
val scheduler = TestCoroutineScheduler()
46-
val dispatcher = TestCoroutineDispatcher(scheduler)
46+
val dispatcher = StandardTestDispatcher(scheduler)
4747
val scope = createTestCoroutineScope(scheduler + dispatcher)
4848
assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
4949
assertSame(dispatcher, scope.coroutineContext[ContinuationInterceptor])
@@ -123,7 +123,7 @@ class TestCoroutineScopeTest {
123123
companion object {
124124
internal val invalidContexts = listOf(
125125
Dispatchers.Default, // not a [TestDispatcher]
126-
TestCoroutineDispatcher() + TestCoroutineScheduler(), // the dispatcher is not linked to the scheduler
126+
StandardTestDispatcher() + TestCoroutineScheduler(), // the dispatcher is not linked to the scheduler
127127
CoroutineExceptionHandler { _, _ -> }, // not an `UncaughtExceptionCaptor`
128128
)
129129
}

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

+1-14
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,11 @@
33
*/
44
package kotlinx.coroutines.test
55

6-
import kotlinx.atomicfu.*
76
import kotlinx.coroutines.*
87
import kotlin.coroutines.*
98
import kotlin.test.*
109

11-
class TestDispatchersTest {
12-
private val actionIndex = atomic(0)
13-
private val finished = atomic(false)
14-
15-
private fun expect(index: Int) {
16-
val wasIndex = actionIndex.incrementAndGet()
17-
check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
18-
}
19-
20-
private fun finish(index: Int) {
21-
expect(index)
22-
check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" }
23-
}
10+
class TestDispatchersTest: OrderedExecutionTestBase() {
2411

2512
@BeforeTest
2613
fun setUp() {

kotlinx-coroutines-test/common/test/TestBuildersTest.kt renamed to kotlinx-coroutines-test/common/test/migration/TestBuildersTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import kotlinx.coroutines.*
88
import kotlin.coroutines.*
99
import kotlin.test.*
1010

11+
@Suppress("DEPRECATION")
1112
class TestBuildersTest {
1213

1314
@Test

0 commit comments

Comments
 (0)