From e2e2d10da638567a859f71b248b2cbef8041b955 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 25 Oct 2021 11:51:45 +0300 Subject: [PATCH] Fixes --- .../common/src/DelayController.kt | 22 ++++++------- .../common/src/TestBuilders.kt | 6 ++-- .../common/src/TestCoroutineDispatcher.kt | 2 +- .../src/TestCoroutineExceptionHandler.kt | 4 +-- .../common/src/TestCoroutineScheduler.kt | 33 ++++++++----------- .../common/src/TestCoroutineScope.kt | 8 ++--- 6 files changed, 35 insertions(+), 40 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/DelayController.kt b/kotlinx-coroutines-test/common/src/DelayController.kt index 693a44da55..b86955db31 100644 --- a/kotlinx-coroutines-test/common/src/DelayController.kt +++ b/kotlinx-coroutines-test/common/src/DelayController.kt @@ -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 { @@ -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 /** @@ -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 /** @@ -70,7 +70,7 @@ 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 /** @@ -78,7 +78,7 @@ public interface DelayController { * * Calling this function will never advance the clock. */ - @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0 + @ExperimentalCoroutinesApi public fun runCurrent() /** @@ -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() @@ -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) /** @@ -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() /** @@ -119,7 +119,7 @@ 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() } @@ -127,7 +127,7 @@ public interface DelayController { * 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 { @@ -179,4 +179,4 @@ internal interface SchedulerAsDelayController: DelayController { ) } } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index c3984f7a3f..ebe22050f1 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -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() @@ -68,14 +68,14 @@ private fun CoroutineContext.activeJobs(): Set { * 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) diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt index 12d42b88e3..7baf0a8e17 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt @@ -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 { diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt index b1296df12a..9f49292dab 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt @@ -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. @@ -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 { diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt index db5d2c1dcb..0d0716999c 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt @@ -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 @@ -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 /** This heap stores the knowledge about which dispatchers are interested in which moments of virtual time. */ @@ -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. */ @@ -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. */ @@ -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 } } } @@ -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.") @@ -177,16 +175,13 @@ private class TestDispatchEvent( private val count: Long, @JvmField val time: Long, @JvmField val marker: T, - val isCancelled: () -> Boolean + @JvmField val isCancelled: () -> Boolean ) : Comparable>, 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)" } diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt index 426a3cae2f..fae427aa62 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt @@ -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. @@ -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) } @@ -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() } @@ -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") \ No newline at end of file + ?: throw IllegalStateException("This scope isn't able to pause its dispatchers")