@@ -3,8 +3,9 @@ package kotlinx.coroutines.test
3
3
import kotlinx.coroutines.*
4
4
import kotlinx.coroutines.test.internal.ThreadSafeHeap
5
5
import kotlinx.coroutines.test.internal.ThreadSafeHeapNode
6
- import java.util.concurrent.TimeUnit
6
+ import java.util.concurrent.atomic.AtomicLong
7
7
import kotlin.coroutines.CoroutineContext
8
+ import kotlin.math.max
8
9
9
10
/* *
10
11
* Control the virtual clock time of a [CoroutineDispatcher].
@@ -16,37 +17,28 @@ interface DelayController {
16
17
/* *
17
18
* Returns the current virtual clock-time as it is known to this Dispatcher.
18
19
*
19
- * @param unit The [TimeUnit] in which the clock-time must be returned.
20
20
* @return The virtual clock-time
21
21
*/
22
22
@ExperimentalCoroutinesApi
23
- fun currentTime (unit : TimeUnit = TimeUnit . MILLISECONDS ): Long
23
+ fun currentTime (): Long
24
24
25
25
/* *
26
26
* Moves the Dispatcher's virtual clock forward by a specified amount of time.
27
27
*
28
- * The amount the clock is progressed may be larger than the requested delayTime if the code under test uses
28
+ * The amount the clock is progressed may be larger than the requested delayTimeMillis if the code under test uses
29
29
* blocking coroutines.
30
30
*
31
- * @param delayTime The amount of time to move the CoroutineContext's clock forward.
32
- * @param unit The [TimeUnit] in which [delayTime] and the return value is expressed.
31
+ * @param delayTimeMillis The amount of time to move the CoroutineContext's clock forward.
33
32
* @return The amount of delay-time that this Dispatcher's clock has been forwarded.
34
33
*/
35
34
@ExperimentalCoroutinesApi
36
- fun advanceTimeBy (delayTime : Long , unit : TimeUnit = TimeUnit . MILLISECONDS ): Long
35
+ fun advanceTimeBy (delayTimeMillis : Long ): Long
37
36
38
- /* *
39
- * Moves the current virtual clock forward just far enough so the next delay will return.
40
- *
41
- * @return the amount of delay-time that this Dispatcher's clock has been forwarded.
42
- */
43
- @ExperimentalCoroutinesApi
44
- fun advanceTimeToNextDelayed (): Long
45
37
46
38
/* *
47
39
* Immediately execute all pending tasks and advance the virtual clock-time to the last delay.
48
40
*
49
- * @return the amount of delay-time that this Dispatcher's clock has been forwarded.
41
+ * @return the amount of delay-time that this Dispatcher's clock has been forwarded in milliseconds .
50
42
*/
51
43
@ExperimentalCoroutinesApi
52
44
fun advanceUntilIdle (): Long
@@ -60,7 +52,7 @@ interface DelayController {
60
52
fun runCurrent ()
61
53
62
54
/* *
63
- * Call after a test case completes.
55
+ * Test code must call this after test code completes to ensure that the dispatcher is properly cleaned up .
64
56
*
65
57
* @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended
66
58
* coroutines.
@@ -75,7 +67,7 @@ interface DelayController {
75
67
* By pausing the dispatcher any new coroutines will not execute immediately. After block executes, the dispatcher
76
68
* will resume auto-advancing.
77
69
*
78
- * This is useful when testing functions that that start a coroutine. By pausing the dispatcher assertions or
70
+ * This is useful when testing functions that start a coroutine. By pausing the dispatcher assertions or
79
71
* setup may be done between the time the coroutine is created and started.
80
72
*/
81
73
@ExperimentalCoroutinesApi
@@ -84,8 +76,8 @@ interface DelayController {
84
76
/* *
85
77
* Pause the dispatcher.
86
78
*
87
- * When paused the dispatcher will not execute any coroutines automatically, and you must call [runCurrent], or one
88
- * of [advanceTimeBy], [advanceTimeToNextDelayed ], or [advanceUntilIdle] to execute coroutines.
79
+ * When paused, the dispatcher will not execute any coroutines automatically, and you must call [runCurrent] or
80
+ * [advanceTimeBy], or [advanceUntilIdle] to execute coroutines.
89
81
*/
90
82
@ExperimentalCoroutinesApi
91
83
fun pauseDispatcher ()
@@ -94,15 +86,15 @@ interface DelayController {
94
86
* Resume the dispatcher from a paused state.
95
87
*
96
88
* Resumed dispatchers will automatically progress through all coroutines scheduled at the current time. To advance
97
- * time and execute coroutines scheduled in the future use one of [advanceTimeBy], [advanceTimeToNextDelayed ],
89
+ * time and execute coroutines scheduled in the future use, one of [advanceTimeBy],
98
90
* or [advanceUntilIdle].
99
91
*/
100
92
@ExperimentalCoroutinesApi
101
93
fun resumeDispatcher ()
102
94
}
103
95
104
96
/* *
105
- * Thrown when a test has completed by there are tasks that are not completed or cancelled.
97
+ * Thrown when a test has completed and there are tasks that are not completed or cancelled.
106
98
*/
107
99
@ExperimentalCoroutinesApi
108
100
class UncompletedCoroutinesError (message : String , cause : Throwable ? = null ): AssertionError(message, cause)
@@ -139,10 +131,10 @@ class TestCoroutineDispatcher:
139
131
private val queue = ThreadSafeHeap <TimedRunnable >()
140
132
141
133
// The per-scheduler global order counter.
142
- private var counter = 0L
134
+ private var counter = AtomicLong ( 0 )
143
135
144
136
// Storing time in nanoseconds internally.
145
- private var time = 0L
137
+ private var time = AtomicLong ( 0 )
146
138
147
139
override fun dispatch (context : CoroutineContext , block : Runnable ) {
148
140
if (dispatchImmediately) {
@@ -165,13 +157,15 @@ class TestCoroutineDispatcher:
165
157
}
166
158
}
167
159
168
- override fun toString (): String = " TestCoroutineDispatcher[time=${time} ns]"
160
+ override fun toString (): String {
161
+ return " TestCoroutineDispatcher[currentTime=${time} ms, queued=${queue.size} ]"
162
+ }
169
163
170
164
private fun post (block : Runnable ) =
171
- queue.addLast(TimedRunnable (block, counter++ ))
165
+ queue.addLast(TimedRunnable (block, counter.getAndIncrement() ))
172
166
173
167
private fun postDelayed (block : Runnable , delayTime : Long ) =
174
- TimedRunnable (block, counter++ , time + TimeUnit . MILLISECONDS .toNanos( delayTime) )
168
+ TimedRunnable (block, counter.getAndIncrement() , time.get() + delayTime)
175
169
.also {
176
170
queue.addLast(it)
177
171
}
@@ -181,49 +175,53 @@ class TestCoroutineDispatcher:
181
175
while (true ) {
182
176
val current = queue.removeFirstIf { it.time <= targetTime } ? : break
183
177
// If the scheduled time is 0 (immediate) use current virtual time
184
- if (current.time != 0L ) time = current.time
178
+ time.getAndAccumulate(current.time) { currentValue: Long , proposedValue: Long ->
179
+ if (proposedValue != 0L ) {
180
+ proposedValue
181
+ } else {
182
+ currentValue
183
+ }
184
+ }
185
185
current.run ()
186
186
}
187
187
}
188
188
189
- override fun currentTime (unit : TimeUnit )=
190
- unit.convert(time, TimeUnit .NANOSECONDS )
189
+ override fun currentTime () = time.get()
191
190
192
- override fun advanceTimeBy (delayTime : Long , unit : TimeUnit ): Long {
193
- val oldTime = time
194
- advanceUntilTime(oldTime + unit.toNanos(delayTime), TimeUnit . NANOSECONDS )
195
- return unit.convert(time - oldTime, TimeUnit . NANOSECONDS )
191
+ override fun advanceTimeBy (delayTimeMillis : Long ): Long {
192
+ val oldTime = time.get()
193
+ advanceUntilTime(oldTime + delayTimeMillis )
194
+ return time.get() - oldTime
196
195
}
197
196
198
197
/* *
199
198
* Moves the CoroutineContext's clock-time to a particular moment in time.
200
199
*
201
- * @param targetTime The point in time to which to move the CoroutineContext's clock.
202
- * @param unit The [TimeUnit] in which [targetTime] is expressed.
200
+ * @param targetTime The point in time to which to move the CoroutineContext's clock (milliseconds).
203
201
*/
204
- private fun advanceUntilTime (targetTime : Long , unit : TimeUnit ) {
205
- val nanoTime = unit.toNanos(targetTime)
206
- doActionsUntil(nanoTime)
207
- if (nanoTime > time) time = nanoTime
208
- }
209
-
210
- override fun advanceTimeToNextDelayed (): Long {
211
- val oldTime = time
212
- runCurrent()
213
- val next = queue.peek() ? : return 0
214
- advanceUntilTime(next.time, TimeUnit .NANOSECONDS )
215
- return time - oldTime
202
+ private fun advanceUntilTime (targetTime : Long ) {
203
+ doActionsUntil(targetTime)
204
+ time.getAndAccumulate(targetTime) { currentValue: Long , proposedValue: Long ->
205
+ if (currentValue < proposedValue) {
206
+ proposedValue
207
+ } else {
208
+ currentValue
209
+ }
210
+ }
211
+ if (targetTime > time.get()) time
216
212
}
217
213
218
214
override fun advanceUntilIdle (): Long {
219
- val oldTime = time
215
+ val oldTime = time.get()
220
216
while (! queue.isEmpty) {
221
- advanceTimeToNextDelayed()
217
+ runCurrent()
218
+ val next = queue.peek() ? : break
219
+ advanceUntilTime(next.time)
222
220
}
223
- return time - oldTime
221
+ return time.get() - oldTime
224
222
}
225
223
226
- override fun runCurrent () = doActionsUntil(time)
224
+ override fun runCurrent () = doActionsUntil(time.get() )
227
225
228
226
override suspend fun pauseDispatcher (block : suspend () -> Unit ) {
229
227
val previous = dispatchImmediately
@@ -245,7 +243,7 @@ class TestCoroutineDispatcher:
245
243
246
244
override fun cleanupTestCoroutines () {
247
245
// process any pending cancellations or completions, but don't advance time
248
- doActionsUntil(time)
246
+ doActionsUntil(time.get() )
249
247
250
248
// run through all pending tasks, ignore any submitted coroutines that are not active
251
249
val pendingTasks = mutableListOf<TimedRunnable >()
0 commit comments