• 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 @file:Suppress("DEPRECATION")
5 
6 package kotlinx.coroutines.test
7 
8 import kotlinx.coroutines.*
9 
10 /**
11  * Control the virtual clock time of a [CoroutineDispatcher].
12  *
13  * Testing libraries may expose this interface to the tests instead of [TestCoroutineDispatcher].
14  *
15  * This interface is deprecated without replacement.
16  * Instead, [TestCoroutineScheduler] is supposed to be used to control the virtual time.
17  * Please see the
18  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
19  * for an instruction on how to update the code for the new API.
20  */
21 @ExperimentalCoroutinesApi
22 @Deprecated(
23     "Use `TestCoroutineScheduler` to control virtual time.",
24     level = DeprecationLevel.WARNING
25 )
26 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
27 public interface DelayController {
28     /**
29      * Returns the current virtual clock-time as it is known to this Dispatcher.
30      *
31      * @return The virtual clock-time
32      */
33     @ExperimentalCoroutinesApi
34     public val currentTime: Long
35 
36     /**
37      * Moves the Dispatcher's virtual clock forward by a specified amount of time.
38      *
39      * The amount the clock is progressed may be larger than the requested `delayTimeMillis` if the code under test uses
40      * blocking coroutines.
41      *
42      * The virtual clock time will advance once for each delay resumed until the next delay exceeds the requested
43      * `delayTimeMills`. In the following test, the virtual time will progress by 2_000 then 1 to resume three different
44      * calls to delay.
45      *
46      * ```
47      * @Test
48      * fun advanceTimeTest() = runBlockingTest {
49      *     foo()
50      *     advanceTimeBy(2_000)  // advanceTimeBy(2_000) will progress through the first two delays
51      *     // virtual time is 2_000, next resume is at 2_001
52      *     advanceTimeBy(2)      // progress through the last delay of 501 (note 500ms were already advanced)
53      *     // virtual time is 2_0002
54      * }
55      *
56      * fun CoroutineScope.foo() {
57      *     launch {
58      *         delay(1_000)    // advanceTimeBy(2_000) will progress through this delay (resume @ virtual time 1_000)
59      *         // virtual time is 1_000
60      *         delay(500)      // advanceTimeBy(2_000) will progress through this delay (resume @ virtual time 1_500)
61      *         // virtual time is 1_500
62      *         delay(501)      // advanceTimeBy(2_000) will not progress through this delay (resume @ virtual time 2_001)
63      *         // virtual time is 2_001
64      *     }
65      * }
66      * ```
67      *
68      * @param delayTimeMillis The amount of time to move the CoroutineContext's clock forward.
69      * @return The amount of delay-time that this Dispatcher's clock has been forwarded.
70      */
71     @ExperimentalCoroutinesApi
advanceTimeBynull72     public fun advanceTimeBy(delayTimeMillis: Long): Long
73 
74     /**
75      * Immediately execute all pending tasks and advance the virtual clock-time to the last delay.
76      *
77      * If new tasks are scheduled due to advancing virtual time, they will be executed before `advanceUntilIdle`
78      * returns.
79      *
80      * @return the amount of delay-time that this Dispatcher's clock has been forwarded in milliseconds.
81      */
82     @ExperimentalCoroutinesApi
83     public fun advanceUntilIdle(): Long
84 
85     /**
86      * Run any tasks that are pending at or before the current virtual clock-time.
87      *
88      * Calling this function will never advance the clock.
89      */
90     @ExperimentalCoroutinesApi
91     public fun runCurrent()
92 
93     /**
94      * Call after test code completes to ensure that the dispatcher is properly cleaned up.
95      *
96      * @throws AssertionError if any pending tasks are active, however it will not throw for suspended
97      * coroutines.
98      */
99     @ExperimentalCoroutinesApi
100     @Throws(AssertionError::class)
101     public fun cleanupTestCoroutines()
102 
103     /**
104      * Run a block of code in a paused dispatcher.
105      *
106      * By pausing the dispatcher any new coroutines will not execute immediately. After block executes, the dispatcher
107      * will resume auto-advancing.
108      *
109      * This is useful when testing functions that start a coroutine. By pausing the dispatcher assertions or
110      * setup may be done between the time the coroutine is created and started.
111      */
112     @Deprecated(
113         "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
114         level = DeprecationLevel.WARNING
115     )
116     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
117     public suspend fun pauseDispatcher(block: suspend () -> Unit)
118 
119     /**
120      * Pause the dispatcher.
121      *
122      * When paused, the dispatcher will not execute any coroutines automatically, and you must call [runCurrent] or
123      * [advanceTimeBy], or [advanceUntilIdle] to execute coroutines.
124      */
125     @Deprecated(
126         "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
127         level = DeprecationLevel.WARNING
128     )
129     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
130     public fun pauseDispatcher()
131 
132     /**
133      * Resume the dispatcher from a paused state.
134      *
135      * Resumed dispatchers will automatically progress through all coroutines scheduled at the current time. To advance
136      * time and execute coroutines scheduled in the future use, one of [advanceTimeBy],
137      * or [advanceUntilIdle].
138      */
139     @Deprecated(
140         "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.",
141         level = DeprecationLevel.WARNING
142     )
143     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
144     public fun resumeDispatcher()
145 }
146 
147 internal interface SchedulerAsDelayController : DelayController {
148     val scheduler: TestCoroutineScheduler
149 
150     /** @suppress */
151     @Deprecated(
152         "This property delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
153         ReplaceWith("this.scheduler.currentTime"),
154         level = DeprecationLevel.WARNING
155     )
156     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
157     override val currentTime: Long
158         get() = scheduler.currentTime
159 
160 
161     /** @suppress */
162     @Deprecated(
163         "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
164         ReplaceWith("this.scheduler.apply { advanceTimeBy(delayTimeMillis); runCurrent() }"),
165         level = DeprecationLevel.WARNING
166     )
167     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
168     override fun advanceTimeBy(delayTimeMillis: Long): Long {
169         val oldTime = scheduler.currentTime
170         scheduler.advanceTimeBy(delayTimeMillis)
171         scheduler.runCurrent()
172         return scheduler.currentTime - oldTime
173     }
174 
175     /** @suppress */
176     @Deprecated(
177         "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
178         ReplaceWith("this.scheduler.advanceUntilIdle()"),
179         level = DeprecationLevel.WARNING
180     )
181     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
182     override fun advanceUntilIdle(): Long {
183         val oldTime = scheduler.currentTime
184         scheduler.advanceUntilIdle()
185         return scheduler.currentTime - oldTime
186     }
187 
188     /** @suppress */
189     @Deprecated(
190         "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.",
191         ReplaceWith("this.scheduler.runCurrent()"),
192         level = DeprecationLevel.WARNING
193     )
194     // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
195     override fun runCurrent(): Unit = scheduler.runCurrent()
196 
197     /** @suppress */
198     @ExperimentalCoroutinesApi
199     override fun cleanupTestCoroutines() {
200         // process any pending cancellations or completions, but don't advance time
201         scheduler.runCurrent()
202         if (!scheduler.isIdle(strict = false)) {
203             throw UncompletedCoroutinesError(
204                 "Unfinished coroutines during tear-down. Ensure all coroutines are" +
205                     " completed or cancelled by your test."
206             )
207         }
208     }
209 }
210