• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 kotlin.test.*
9 import kotlin.time.*
10 import kotlin.time.Duration.Companion.seconds
11 import kotlin.time.Duration.Companion.milliseconds
12 
13 class TestCoroutineSchedulerTest {
14     /** Tests that `TestCoroutineScheduler` attempts to detect if there are several instances of it. */
15     @Test
<lambda>null16     fun testContextElement() = runTest {
17         assertFailsWith<IllegalStateException> {
18             withContext(StandardTestDispatcher()) {
19             }
20         }
21     }
22 
23     /** Tests that, as opposed to [DelayController.advanceTimeBy] or [TestCoroutineScope.advanceTimeBy],
24      * [TestCoroutineScheduler.advanceTimeBy] doesn't run the tasks scheduled at the target moment. */
25     @Test
<lambda>null26     fun testAdvanceTimeByDoesNotRunCurrent() = runTest {
27         var entered = false
28         launch {
29             delay(15)
30             entered = true
31         }
32         testScheduler.advanceTimeBy(15.milliseconds)
33         assertFalse(entered)
34         testScheduler.runCurrent()
35         assertTrue(entered)
36     }
37 
38     /** Tests that [TestCoroutineScheduler.advanceTimeBy] doesn't accept negative delays. */
39     @Test
testAdvanceTimeByWithNegativeDelaynull40     fun testAdvanceTimeByWithNegativeDelay() {
41         val scheduler = TestCoroutineScheduler()
42         assertFailsWith<IllegalArgumentException> {
43             scheduler.advanceTimeBy((-1).milliseconds)
44         }
45     }
46 
47     /** Tests that if [TestCoroutineScheduler.advanceTimeBy] encounters an arithmetic overflow, all the tasks scheduled
48      * until the moment [Long.MAX_VALUE] get run. */
49     @Test
<lambda>null50     fun testAdvanceTimeByEnormousDelays() = forTestDispatchers {
51         assertRunsFast {
52             with (TestScope(it)) {
53                 launch {
54                     val initialDelay = 10L
55                     delay(initialDelay)
56                     assertEquals(initialDelay, currentTime)
57                     var enteredInfinity = false
58                     launch {
59                         delay(Long.MAX_VALUE - 1) // delay(Long.MAX_VALUE) does nothing
60                         assertEquals(Long.MAX_VALUE, currentTime)
61                         enteredInfinity = true
62                     }
63                     var enteredNearInfinity = false
64                     launch {
65                         delay(Long.MAX_VALUE - initialDelay - 1)
66                         assertEquals(Long.MAX_VALUE - 1, currentTime)
67                         enteredNearInfinity = true
68                     }
69                     testScheduler.advanceTimeBy(Duration.INFINITE)
70                     assertFalse(enteredInfinity)
71                     assertTrue(enteredNearInfinity)
72                     assertEquals(Long.MAX_VALUE, currentTime)
73                     testScheduler.runCurrent()
74                     assertTrue(enteredInfinity)
75                 }
76                 testScheduler.advanceUntilIdle()
77             }
78         }
79     }
80 
81     /** Tests the basic functionality of [TestCoroutineScheduler.advanceTimeBy]. */
82     @Test
<lambda>null83     fun testAdvanceTimeBy() = runTest {
84         assertRunsFast {
85             var stage = 1
86             launch {
87                 delay(1_000)
88                 assertEquals(1_000, currentTime)
89                 stage = 2
90                 delay(500)
91                 assertEquals(1_500, currentTime)
92                 stage = 3
93                 delay(501)
94                 assertEquals(2_001, currentTime)
95                 stage = 4
96             }
97             assertEquals(1, stage)
98             assertEquals(0, currentTime)
99             advanceTimeBy(2.seconds)
100             assertEquals(3, stage)
101             assertEquals(2_000, currentTime)
102             advanceTimeBy(2.milliseconds)
103             assertEquals(4, stage)
104             assertEquals(2_002, currentTime)
105         }
106     }
107 
108     /** Tests the basic functionality of [TestCoroutineScheduler.runCurrent]. */
109     @Test
<lambda>null110     fun testRunCurrent() = runTest {
111         var stage = 0
112         launch {
113             delay(1)
114             ++stage
115             delay(1)
116             stage += 10
117         }
118         launch {
119             delay(1)
120             ++stage
121             delay(1)
122             stage += 10
123         }
124         testScheduler.advanceTimeBy(1.milliseconds)
125         assertEquals(0, stage)
126         runCurrent()
127         assertEquals(2, stage)
128         testScheduler.advanceTimeBy(1.milliseconds)
129         assertEquals(2, stage)
130         runCurrent()
131         assertEquals(22, stage)
132     }
133 
134     /** Tests that [TestCoroutineScheduler.runCurrent] will not run new tasks after the current time has advanced. */
135     @Test
<lambda>null136     fun testRunCurrentNotDrainingQueue() = forTestDispatchers {
137         assertRunsFast {
138             val scheduler = it.scheduler
139             val scope = TestScope(it)
140             var stage = 1
141             scope.launch {
142                 delay(SLOW)
143                 launch {
144                     delay(SLOW)
145                     stage = 3
146                 }
147                 scheduler.advanceTimeBy(SLOW.milliseconds)
148                 stage = 2
149             }
150             scheduler.advanceTimeBy(SLOW.milliseconds)
151             assertEquals(1, stage)
152             scheduler.runCurrent()
153             assertEquals(2, stage)
154             scheduler.runCurrent()
155             assertEquals(3, stage)
156         }
157     }
158 
159     /** Tests that [TestCoroutineScheduler.advanceUntilIdle] doesn't hang when itself running in a scheduler task. */
160     @Test
<lambda>null161     fun testNestedAdvanceUntilIdle() = forTestDispatchers {
162         assertRunsFast {
163             val scheduler = it.scheduler
164             val scope = TestScope(it)
165             var executed = false
166             scope.launch {
167                 launch {
168                     delay(SLOW)
169                     executed = true
170                 }
171                 scheduler.advanceUntilIdle()
172             }
173             scheduler.advanceUntilIdle()
174             assertTrue(executed)
175         }
176     }
177 
178     /** Tests [yield] scheduling tasks for future execution and not executing immediately. */
179     @Test
<lambda>null180     fun testYield() = forTestDispatchers {
181         val scope = TestScope(it)
182         var stage = 0
183         scope.launch {
184             yield()
185             assertEquals(1, stage)
186             stage = 2
187         }
188         scope.launch {
189             yield()
190             assertEquals(2, stage)
191             stage = 3
192         }
193         assertEquals(0, stage)
194         stage = 1
195         scope.runCurrent()
196     }
197 
198     /** Tests that dispatching the delayed tasks is ordered by their waking times. */
199     @Test
<lambda>null200     fun testDelaysPriority() = forTestDispatchers {
201         val scope = TestScope(it)
202         var lastMeasurement = 0L
203         fun checkTime(time: Long) {
204             assertTrue(lastMeasurement < time)
205             assertEquals(time, scope.currentTime)
206             lastMeasurement = scope.currentTime
207         }
208         scope.launch {
209             launch {
210                 delay(100)
211                 checkTime(100)
212                 val deferred = async {
213                     delay(70)
214                     checkTime(170)
215                 }
216                 delay(1)
217                 checkTime(101)
218                 deferred.await()
219                 delay(1)
220                 checkTime(171)
221             }
222             launch {
223                 delay(200)
224                 checkTime(200)
225             }
226             launch {
227                 delay(150)
228                 checkTime(150)
229                 delay(22)
230                 checkTime(172)
231             }
232             delay(201)
233         }
234         scope.advanceUntilIdle()
235         checkTime(201)
236     }
237 
TestScopenull238     private fun TestScope.checkTimeout(
239         timesOut: Boolean, timeoutMillis: Long = SLOW, block: suspend () -> Unit
240     ) = assertRunsFast {
241         var caughtException = false
242         asSpecificImplementation().enter()
243         launch {
244             try {
245                 withTimeout(timeoutMillis) {
246                     block()
247                 }
248             } catch (e: TimeoutCancellationException) {
249                 caughtException = true
250             }
251         }
252         advanceUntilIdle()
253         throwAll(null, asSpecificImplementation().legacyLeave())
254         if (timesOut)
255             assertTrue(caughtException)
256         else
257             assertFalse(caughtException)
258     }
259 
260     /** Tests that timeouts get triggered. */
261     @Test
<lambda>null262     fun testSmallTimeouts() = forTestDispatchers {
263         val scope = TestScope(it)
264         scope.checkTimeout(true) {
265             val half = SLOW / 2
266             delay(half)
267             delay(SLOW - half)
268         }
269     }
270 
271     /** Tests that timeouts don't get triggered if the code finishes in time. */
272     @Test
<lambda>null273     fun testLargeTimeouts() = forTestDispatchers {
274         val scope = TestScope(it)
275         scope.checkTimeout(false) {
276             val half = SLOW / 2
277             delay(half)
278             delay(SLOW - half - 1)
279         }
280     }
281 
282     /** Tests that timeouts get triggered if the code fails to finish in time asynchronously. */
283     @Test
<lambda>null284     fun testSmallAsynchronousTimeouts() = forTestDispatchers {
285         val scope = TestScope(it)
286         val deferred = CompletableDeferred<Unit>()
287         scope.launch {
288             val half = SLOW / 2
289             delay(half)
290             delay(SLOW - half)
291             deferred.complete(Unit)
292         }
293         scope.checkTimeout(true) {
294             deferred.await()
295         }
296     }
297 
298     /** Tests that timeouts don't get triggered if the code finishes in time, even if it does so asynchronously. */
299     @Test
<lambda>null300     fun testLargeAsynchronousTimeouts() = forTestDispatchers {
301         val scope = TestScope(it)
302         val deferred = CompletableDeferred<Unit>()
303         scope.launch {
304             val half = SLOW / 2
305             delay(half)
306             delay(SLOW - half - 1)
307             deferred.complete(Unit)
308         }
309         scope.checkTimeout(false) {
310             deferred.await()
311         }
312     }
313 
314     @Test
315     @ExperimentalTime
<lambda>null316     fun testAdvanceTimeSource() = runTest {
317         val expected = 1.seconds
318         val before = testTimeSource.markNow()
319         val actual = testTimeSource.measureTime {
320             delay(expected)
321         }
322         assertEquals(expected, actual)
323         val after = testTimeSource.markNow()
324         assertTrue(before < after)
325         assertEquals(expected, after - before)
326     }
327 
forTestDispatchersnull328     private fun forTestDispatchers(block: (TestDispatcher) -> Unit): Unit =
329         @Suppress("DEPRECATION")
330         listOf(
331             StandardTestDispatcher(),
332             UnconfinedTestDispatcher()
333         ).forEach {
334             try {
335                 block(it)
336             } catch (e: Throwable) {
337                 throw RuntimeException("Test failed for dispatcher $it", e)
338             }
339         }
340 }
341