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