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