Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
dkhalanskyjb committed Oct 25, 2021
1 parent 9f36b28 commit 2f818a3
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 40 deletions.
22 changes: 11 additions & 11 deletions kotlinx-coroutines-test/common/src/DelayController.kt
Expand Up @@ -12,7 +12,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
*
* Testing libraries may expose this interface to the tests instead of [TestCoroutineDispatcher].
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
@Deprecated("Use `TestCoroutineScheduler` to control virtual time.",
level = DeprecationLevel.WARNING)
public interface DelayController {
Expand All @@ -21,7 +21,7 @@ public interface DelayController {
*
* @return The virtual clock-time
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public val currentTime: Long

/**
Expand Down Expand Up @@ -59,7 +59,7 @@ public interface DelayController {
* @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 // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun advanceTimeBy(delayTimeMillis: Long): Long

/**
Expand All @@ -70,15 +70,15 @@ public interface DelayController {
*
* @return the amount of delay-time that this Dispatcher's clock has been forwarded in milliseconds.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@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 // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun runCurrent()

/**
Expand All @@ -87,7 +87,7 @@ public interface DelayController {
* @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended
* coroutines.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
@Throws(UncompletedCoroutinesError::class)
public fun cleanupTestCoroutines()

Expand All @@ -100,7 +100,7 @@ public interface DelayController {
* 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.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public suspend fun pauseDispatcher(block: suspend () -> Unit)

/**
Expand All @@ -109,7 +109,7 @@ public interface DelayController {
* When paused, the dispatcher will not execute any coroutines automatically, and you must call [runCurrent] or
* [advanceTimeBy], or [advanceUntilIdle] to execute coroutines.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun pauseDispatcher()

/**
Expand All @@ -119,15 +119,15 @@ public interface DelayController {
* time and execute coroutines scheduled in the future use, one of [advanceTimeBy],
* or [advanceUntilIdle].
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun resumeDispatcher()
}

/**
* Thrown when a test has completed and there are tasks that are not completed or cancelled.
*/
// todo: maybe convert into non-public class in 1.3.0 (need use-cases for a public exception type)
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public class UncompletedCoroutinesError(message: String): AssertionError(message)

internal interface SchedulerAsDelayController: DelayController {
Expand Down Expand Up @@ -179,4 +179,4 @@ internal interface SchedulerAsDelayController: DelayController {
)
}
}
}
}
6 changes: 3 additions & 3 deletions kotlinx-coroutines-test/common/src/TestBuilders.kt
Expand Up @@ -41,7 +41,7 @@ import kotlin.coroutines.*
* then they must implement [DelayController] and [TestCoroutineExceptionHandler] respectively.
* @param testBody The code of the unit-test.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun runBlockingTest(context: CoroutineContext = EmptyCoroutineContext, testBody: suspend TestCoroutineScope.() -> Unit) {
val (safeContext, dispatcher) = context.checkTestScopeArguments()
val startingJobs = safeContext.activeJobs()
Expand All @@ -68,14 +68,14 @@ private fun CoroutineContext.activeJobs(): Set<Job> {
* Convenience method for calling [runBlockingTest] on an existing [TestCoroutineScope].
*/
// todo: need documentation on how this extension is supposed to be used
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
runBlockingTest(coroutineContext, block)

/**
* Convenience method for calling [runBlockingTest] on an existing [TestCoroutineDispatcher].
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
runBlockingTest(this, block)

Expand Down
Expand Up @@ -21,7 +21,7 @@ import kotlin.coroutines.*
*
* @see DelayController
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public class TestCoroutineDispatcher(public override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler()):
TestDispatcher(), Delay, SchedulerAsDelayController
{
Expand Down
Expand Up @@ -11,7 +11,7 @@ import kotlin.coroutines.*
/**
* Access uncaught coroutine exceptions captured during test execution.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public interface UncaughtExceptionCaptor {
/**
* List of uncaught coroutine exceptions.
Expand All @@ -35,7 +35,7 @@ public interface UncaughtExceptionCaptor {
/**
* An exception handler that captures uncaught exceptions in tests.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public class TestCoroutineExceptionHandler :
AbstractCoroutineContextElement(CoroutineExceptionHandler), UncaughtExceptionCaptor, CoroutineExceptionHandler
{
Expand Down
33 changes: 14 additions & 19 deletions kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt
Expand Up @@ -13,9 +13,9 @@ import kotlin.jvm.*
/**
* This is a scheduler for coroutines used in tests, providing the delay-skipping behavior.
*
* [Test dispatchers][TestCoroutineDispatcher] are parameterized with a scheduler. Several dispatchers can share the
* same scheduler, in which case * their knowledge about the virtual time will be synchronized. When the dispatchers
* require scheduling an event at a * later point in time, they notify the scheduler, which will establish the order of
* [Test dispatchers][TestDispatcher] are parameterized with a scheduler. Several dispatchers can share the
* same scheduler, in which case their knowledge about the virtual time will be synchronized. When the dispatchers
* require scheduling an event at a later point in time, they notify the scheduler, which will establish the order of
* the tasks.
*
* The scheduler can be queried to advance the time (via [advanceTimeBy]), run all the scheduled tasks advancing the
Expand All @@ -26,6 +26,7 @@ import kotlin.jvm.*
// TODO: maybe make this a `TimeSource`?
public class TestCoroutineScheduler: AbstractCoroutineContextElement(TestCoroutineScheduler), CoroutineContext.Element {

/** @suppress */
public companion object Key: CoroutineContext.Key<TestCoroutineScheduler>

/** This heap stores the knowledge about which dispatchers are interested in which moments of virtual time. */
Expand All @@ -45,8 +46,8 @@ public class TestCoroutineScheduler: AbstractCoroutineContextElement(TestCorouti
private set

/**
* Registers a request for the scheduler to notify [dispatcher] at a virtual moment [timeDeltaMillis] milliseconds later
* via [TestDispatcher.processEvent], which will be called with the provided [marker] object.
* Registers a request for the scheduler to notify [dispatcher] at a virtual moment [timeDeltaMillis] milliseconds
* later via [TestDispatcher.processEvent], which will be called with the provided [marker] object.
*
* Returns the handler which can be used to cancel the registration.
*/
Expand Down Expand Up @@ -74,7 +75,7 @@ public class TestCoroutineScheduler: AbstractCoroutineContextElement(TestCorouti
* Runs the enqueued tasks in the specified order, advancing the virtual time as needed until there are no more
* tasks associated with the dispatchers linked to this scheduler.
*
* A breaking change from [TestCoroutineDispatcher.advanceTimeBy] is that it no longer returns the total amount of
* A breaking change from [TestCoroutineDispatcher.advanceTimeBy] is that it no longer returns the total number of
* milliseconds by which the execution of this method has advanced the virtual time. If you want to recreate that
* functionality, query [currentTime] before and after the execution to achieve the same result.
*/
Expand Down Expand Up @@ -124,24 +125,22 @@ public class TestCoroutineScheduler: AbstractCoroutineContextElement(TestCorouti
*/
@ExperimentalCoroutinesApi
public fun advanceTimeBy(delayTimeMillis: Long) {
require(delayTimeMillis >= 0) { "" }
require(delayTimeMillis >= 0) { "Can not advance time by a negative delay: $delayTimeMillis" }
val startingTime = currentTime
val targetTime = addClamping(startingTime, delayTimeMillis)
while (true) {
val event = synchronized(lock) {
val timeMark = currentTime
val event = events.peek()
val event = events.removeFirstIf { targetTime > it.time }
when {
event == null || targetTime <= event.time -> {
event == null -> {
currentTime = targetTime
return
}
timeMark > event.time -> currentTimeAheadOfEvents()
else -> {
val event2 = events.removeFirstOrNull()
if (event !== event2) concurrentModificationUnderLock()
currentTime = event.time
event2
event
}
}
}
Expand All @@ -166,7 +165,6 @@ public class TestCoroutineScheduler: AbstractCoroutineContextElement(TestCorouti

// Some error-throwing functions for pretty stack traces
private fun currentTimeAheadOfEvents(): Nothing = invalidSchedulerState()
private fun concurrentModificationUnderLock(): Nothing = invalidSchedulerState()

private fun invalidSchedulerState(): Nothing =
throw IllegalStateException("The test scheduler entered an invalid state. Please report this at https://github.com/Kotlin/kotlinx.coroutines/issues.")
Expand All @@ -177,16 +175,13 @@ private class TestDispatchEvent<T>(
private val count: Long,
@JvmField val time: Long,
@JvmField val marker: T,
val isCancelled: () -> Boolean
@JvmField val isCancelled: () -> Boolean
) : Comparable<TestDispatchEvent<*>>, ThreadSafeHeapNode {
override var heap: ThreadSafeHeap<*>? = null
override var index: Int = 0

override fun compareTo(other: TestDispatchEvent<*>) = if (time == other.time) {
count.compareTo(other.count)
} else {
time.compareTo(other.time)
}
override fun compareTo(other: TestDispatchEvent<*>) =
compareValuesBy(this, other, TestDispatchEvent<*>::time, TestDispatchEvent<*>::count)

override fun toString() = "TestDispatchEvent(time=$time, dispatcher=$dispatcher)"
}
Expand Down
8 changes: 4 additions & 4 deletions kotlinx-coroutines-test/common/src/TestCoroutineScope.kt
Expand Up @@ -10,7 +10,7 @@ import kotlin.coroutines.*
/**
* A scope which provides detailed control over the execution of coroutines for tests.
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public interface TestCoroutineScope: CoroutineScope, UncaughtExceptionCaptor {
/**
* Call after the test completes.
Expand Down Expand Up @@ -54,7 +54,7 @@ private class TestCoroutineScopeImpl (
* @param context an optional context that MAY provide [UncaughtExceptionCaptor] and/or [DelayController]
*/
@Suppress("FunctionName")
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope =
context.checkTestScopeArguments().let { TestCoroutineScopeImpl(it.first, it.second.scheduler) }

Expand Down Expand Up @@ -109,7 +109,7 @@ public fun TestCoroutineScope.advanceTimeBy(delayTimeMillis: Long): Unit =
* Advances the [testScheduler][TestCoroutineScope.testScheduler] to the point where there are no tasks remaining.
* @see TestCoroutineScheduler.advanceUntilIdle
*/
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
@ExperimentalCoroutinesApi
public fun TestCoroutineScope.advanceUntilIdle(): Unit {
coroutineContext.delayController?.advanceUntilIdle() ?: testScheduler.advanceUntilIdle()
}
Expand Down Expand Up @@ -157,4 +157,4 @@ public fun TestCoroutineScope.resumeDispatcher() {

private val TestCoroutineScope.delayControllerForPausing: DelayController
get() = coroutineContext.delayController
?: throw IllegalStateException("This scope isn't able to pause its dispatchers")
?: throw IllegalStateException("This scope isn't able to pause its dispatchers")

0 comments on commit 2f818a3

Please sign in to comment.