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