Skip to content

Commit f1f7057

Browse files
committed
Handled PR notes from Yigit
(rest of commit)
1 parent 1382050 commit f1f7057

File tree

3 files changed

+68
-80
lines changed

3 files changed

+68
-80
lines changed

core/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt

Lines changed: 50 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ package kotlinx.coroutines.test
33
import kotlinx.coroutines.*
44
import kotlinx.coroutines.test.internal.ThreadSafeHeap
55
import kotlinx.coroutines.test.internal.ThreadSafeHeapNode
6-
import java.util.concurrent.TimeUnit
6+
import java.util.concurrent.atomic.AtomicLong
77
import kotlin.coroutines.CoroutineContext
8+
import kotlin.math.max
89

910
/**
1011
* Control the virtual clock time of a [CoroutineDispatcher].
@@ -16,37 +17,28 @@ interface DelayController {
1617
/**
1718
* Returns the current virtual clock-time as it is known to this Dispatcher.
1819
*
19-
* @param unit The [TimeUnit] in which the clock-time must be returned.
2020
* @return The virtual clock-time
2121
*/
2222
@ExperimentalCoroutinesApi
23-
fun currentTime(unit: TimeUnit = TimeUnit.MILLISECONDS): Long
23+
fun currentTime(): Long
2424

2525
/**
2626
* Moves the Dispatcher's virtual clock forward by a specified amount of time.
2727
*
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
2929
* blocking coroutines.
3030
*
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.
3332
* @return The amount of delay-time that this Dispatcher's clock has been forwarded.
3433
*/
3534
@ExperimentalCoroutinesApi
36-
fun advanceTimeBy(delayTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS): Long
35+
fun advanceTimeBy(delayTimeMillis: Long): Long
3736

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
4537

4638
/**
4739
* Immediately execute all pending tasks and advance the virtual clock-time to the last delay.
4840
*
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.
5042
*/
5143
@ExperimentalCoroutinesApi
5244
fun advanceUntilIdle(): Long
@@ -60,7 +52,7 @@ interface DelayController {
6052
fun runCurrent()
6153

6254
/**
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.
6456
*
6557
* @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended
6658
* coroutines.
@@ -75,7 +67,7 @@ interface DelayController {
7567
* By pausing the dispatcher any new coroutines will not execute immediately. After block executes, the dispatcher
7668
* will resume auto-advancing.
7769
*
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
7971
* setup may be done between the time the coroutine is created and started.
8072
*/
8173
@ExperimentalCoroutinesApi
@@ -84,8 +76,8 @@ interface DelayController {
8476
/**
8577
* Pause the dispatcher.
8678
*
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.
8981
*/
9082
@ExperimentalCoroutinesApi
9183
fun pauseDispatcher()
@@ -94,15 +86,15 @@ interface DelayController {
9486
* Resume the dispatcher from a paused state.
9587
*
9688
* 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],
9890
* or [advanceUntilIdle].
9991
*/
10092
@ExperimentalCoroutinesApi
10193
fun resumeDispatcher()
10294
}
10395

10496
/**
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.
10698
*/
10799
@ExperimentalCoroutinesApi
108100
class UncompletedCoroutinesError(message: String, cause: Throwable? = null): AssertionError(message, cause)
@@ -139,10 +131,10 @@ class TestCoroutineDispatcher:
139131
private val queue = ThreadSafeHeap<TimedRunnable>()
140132

141133
// The per-scheduler global order counter.
142-
private var counter = 0L
134+
private var counter = AtomicLong(0)
143135

144136
// Storing time in nanoseconds internally.
145-
private var time = 0L
137+
private var time = AtomicLong(0)
146138

147139
override fun dispatch(context: CoroutineContext, block: Runnable) {
148140
if (dispatchImmediately) {
@@ -165,13 +157,15 @@ class TestCoroutineDispatcher:
165157
}
166158
}
167159

168-
override fun toString(): String = "TestCoroutineDispatcher[time=${time}ns]"
160+
override fun toString(): String {
161+
return "TestCoroutineDispatcher[currentTime=${time}ms, queued=${queue.size}]"
162+
}
169163

170164
private fun post(block: Runnable) =
171-
queue.addLast(TimedRunnable(block, counter++))
165+
queue.addLast(TimedRunnable(block, counter.getAndIncrement()))
172166

173167
private fun postDelayed(block: Runnable, delayTime: Long) =
174-
TimedRunnable(block, counter++, time + TimeUnit.MILLISECONDS.toNanos(delayTime))
168+
TimedRunnable(block, counter.getAndIncrement(), time.get() + delayTime)
175169
.also {
176170
queue.addLast(it)
177171
}
@@ -181,49 +175,53 @@ class TestCoroutineDispatcher:
181175
while (true) {
182176
val current = queue.removeFirstIf { it.time <= targetTime } ?: break
183177
// 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+
}
185185
current.run()
186186
}
187187
}
188188

189-
override fun currentTime(unit: TimeUnit)=
190-
unit.convert(time, TimeUnit.NANOSECONDS)
189+
override fun currentTime() = time.get()
191190

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
196195
}
197196

198197
/**
199198
* Moves the CoroutineContext's clock-time to a particular moment in time.
200199
*
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).
203201
*/
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
216212
}
217213

218214
override fun advanceUntilIdle(): Long {
219-
val oldTime = time
215+
val oldTime = time.get()
220216
while(!queue.isEmpty) {
221-
advanceTimeToNextDelayed()
217+
runCurrent()
218+
val next = queue.peek() ?: break
219+
advanceUntilTime(next.time)
222220
}
223-
return time - oldTime
221+
return time.get() - oldTime
224222
}
225223

226-
override fun runCurrent() = doActionsUntil(time)
224+
override fun runCurrent() = doActionsUntil(time.get())
227225

228226
override suspend fun pauseDispatcher(block: suspend () -> Unit) {
229227
val previous = dispatchImmediately
@@ -245,7 +243,7 @@ class TestCoroutineDispatcher:
245243

246244
override fun cleanupTestCoroutines() {
247245
// process any pending cancellations or completions, but don't advance time
248-
doActionsUntil(time)
246+
doActionsUntil(time.get())
249247

250248
// run through all pending tasks, ignore any submitted coroutines that are not active
251249
val pendingTasks = mutableListOf<TimedRunnable>()

core/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ interface UncaughtExceptionCaptor {
1212
/**
1313
* List of uncaught coroutine exceptions.
1414
*
15-
* The returned list will be a copy of the currently caught exceptions.
15+
* The returned list will be a copy of the currently caught exceptions. All other exceptions will
16+
* be printed using [Throwable.printStackTrace]
1617
*
1718
* During [cleanupTestCoroutines] the first element of this list will be rethrown if it is not empty.
1819
*/
@@ -48,6 +49,8 @@ class TestCoroutineExceptionHandler: UncaughtExceptionCaptor, CoroutineException
4849
override fun cleanupTestCoroutines() {
4950
synchronized(_exceptions) {
5051
val exception = _exceptions.firstOrNull() ?: return
52+
// log the rest
53+
_exceptions.drop(1).forEach { it.printStackTrace() }
5154
throw exception
5255
}
5356
}

core/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,29 @@ class TestCoroutineDispatcherTest {
1111
@Test
1212
fun whenStringCalled_itReturnsString() {
1313
val subject = TestCoroutineDispatcher()
14-
assertEquals("TestCoroutineDispatcher[time=0ns]", subject.toString())
14+
assertEquals("TestCoroutineDispatcher[currentTime=0ms, queued=0]", subject.toString())
1515
}
1616

1717
@Test
1818
fun whenStringCalled_itReturnsCurrentTime() {
1919
val subject = TestCoroutineDispatcher()
20-
subject.advanceTimeBy(1000, TimeUnit.NANOSECONDS)
21-
assertEquals("TestCoroutineDispatcher[time=1000ns]", subject.toString())
20+
subject.advanceTimeBy(1000)
21+
assertEquals("TestCoroutineDispatcher[currentTime=1000ms, queued=0]", subject.toString())
2222
}
2323

2424
@Test
25-
fun whenCurrentTimeCalled_returnsTimeAsSpecified() {
25+
fun whenStringCalled_itShowsQueuedJobs() {
2626
val subject = TestCoroutineDispatcher()
27-
subject.advanceTimeBy(1000, TimeUnit.MILLISECONDS)
28-
29-
assertEquals(1_000_000_000, subject.currentTime(TimeUnit.NANOSECONDS))
30-
assertEquals(1_000, subject.currentTime(TimeUnit.MILLISECONDS))
31-
assertEquals(1, subject.currentTime(TimeUnit.SECONDS))
32-
33-
assertEquals(1_000, subject.currentTime())
34-
}
35-
36-
@Test
37-
fun whenAdvanceTimeCalled_defaultsToMilliseconds() {
38-
val subject = TestCoroutineDispatcher()
39-
subject.advanceTimeBy(1_000)
40-
41-
assertEquals(1_000, subject.currentTime(TimeUnit.MILLISECONDS))
42-
}
43-
44-
@Test
45-
fun whenAdvanceTimeCalled_respectsTimeUnit() {
46-
val subject = TestCoroutineDispatcher()
47-
subject.advanceTimeBy(1, TimeUnit.SECONDS)
48-
49-
assertEquals(1_000, subject.currentTime(TimeUnit.MILLISECONDS))
27+
val scope = TestCoroutineScope(subject)
28+
scope.pauseDispatcher()
29+
scope.launch {
30+
delay(1_000)
31+
}
32+
assertEquals("TestCoroutineDispatcher[currentTime=0ms, queued=1]", subject.toString())
33+
scope.advanceTimeBy(50)
34+
assertEquals("TestCoroutineDispatcher[currentTime=50ms, queued=1]", subject.toString())
35+
scope.advanceUntilIdle()
36+
assertEquals("TestCoroutineDispatcher[currentTime=1000ms, queued=0]", subject.toString())
5037
}
5138

5239
@Test

0 commit comments

Comments
 (0)