From 89db97bdf2537ff2576c46634646dbc8c1735d2d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 23 May 2022 15:06:19 +0300 Subject: [PATCH] Remove TestCoroutineContext.kt completely (#3291) Fixes #3218 --- kotlinx-coroutines-core/README.md | 4 - .../api/kotlinx-coroutines-core.api | 33 --- kotlinx-coroutines-core/build.gradle | 3 +- .../jvm/src/test_/TestCoroutineContext.kt | 260 ------------------ .../src/CoroutinesBlockHoundIntegration.kt | 3 - 5 files changed, 1 insertion(+), 302 deletions(-) delete mode 100644 kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md index c06cd358ad..38a112e89d 100644 --- a/kotlinx-coroutines-core/README.md +++ b/kotlinx-coroutines-core/README.md @@ -83,10 +83,6 @@ Select expression to perform multiple suspending operations simultaneously until Low-level primitives for finer-grained control of coroutines. -# Package kotlinx.coroutines.test - -Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-coroutines-test` module. - diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index fa80bff43a..8fdb4afdac 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -1308,36 +1308,3 @@ public final class kotlinx/coroutines/sync/SemaphoreKt { public static final fun withPermit (Lkotlinx/coroutines/sync/Semaphore;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class kotlinx/coroutines/test/TestCoroutineContext : kotlin/coroutines/CoroutineContext { - public fun ()V - public fun (Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun advanceTimeBy (JLjava/util/concurrent/TimeUnit;)J - public static synthetic fun advanceTimeBy$default (Lkotlinx/coroutines/test/TestCoroutineContext;JLjava/util/concurrent/TimeUnit;ILjava/lang/Object;)J - public final fun advanceTimeTo (JLjava/util/concurrent/TimeUnit;)V - public static synthetic fun advanceTimeTo$default (Lkotlinx/coroutines/test/TestCoroutineContext;JLjava/util/concurrent/TimeUnit;ILjava/lang/Object;)V - public final fun assertAllUnhandledExceptions (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun assertAllUnhandledExceptions$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public final fun assertAnyUnhandledException (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun assertAnyUnhandledException$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public final fun assertExceptions (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun assertExceptions$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public final fun assertUnhandledException (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun assertUnhandledException$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public final fun cancelAllActions ()V - public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; - public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; - public final fun getExceptions ()Ljava/util/List; - public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; - public final fun now (Ljava/util/concurrent/TimeUnit;)J - public static synthetic fun now$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/util/concurrent/TimeUnit;ILjava/lang/Object;)J - public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; - public fun toString ()Ljava/lang/String; - public final fun triggerActions ()V -} - -public final class kotlinx/coroutines/test/TestCoroutineContextKt { - public static final fun withTestContext (Lkotlinx/coroutines/test/TestCoroutineContext;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun withTestContext$default (Lkotlinx/coroutines/test/TestCoroutineContext;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V -} - diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 78d2af14b2..9791b445bf 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -274,8 +274,7 @@ tasks.jvmLincheckTest { } def commonKoverExcludes = - ["kotlinx.coroutines.test.*", // Deprecated package for removal - "kotlinx.coroutines.debug.*", // Tested by debug module + ["kotlinx.coroutines.debug.*", // Tested by debug module "kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated "kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher" // Deprecated diff --git a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt deleted file mode 100644 index 6a71a4deb4..0000000000 --- a/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.test - -import kotlinx.coroutines.* -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.internal.* -import java.util.concurrent.* -import kotlin.coroutines.* - -/** @suppress */ -@Deprecated("This API has been deprecated to integrate with Structured Concurrency.", - ReplaceWith("TestCoroutineScope", "kotlin.coroutines.test"), - level = DeprecationLevel.ERROR) // ERROR in 1.6.0, removed in 1.7.0 -public class TestCoroutineContext(private val name: String? = null) : CoroutineContext { - private val uncaughtExceptions = mutableListOf() - - private val ctxDispatcher = Dispatcher() - - private val ctxHandler = CoroutineExceptionHandler { _, exception -> - uncaughtExceptions += exception - } - - // The ordered queue for the runnable tasks. - private val queue = ThreadSafeHeap() - - // The per-scheduler global order counter. - private var counter = 0L - - // Storing time in nanoseconds internally. - private var time = 0L - - /** - * Exceptions that were caught during a [launch][CoroutineScope.launch] or a [async][CoroutineScope.async] + [Deferred.await]. - */ - public val exceptions: List get() = uncaughtExceptions - - // -- CoroutineContext implementation - - public override fun fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R = - operation(operation(initial, ctxDispatcher), ctxHandler) - - @Suppress("UNCHECKED_CAST") - public override fun get(key: CoroutineContext.Key): E? = when { - key === ContinuationInterceptor -> ctxDispatcher as E - key === CoroutineExceptionHandler -> ctxHandler as E - else -> null - } - - public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = when { - key === ContinuationInterceptor -> ctxHandler - key === CoroutineExceptionHandler -> ctxDispatcher - else -> this - } - - /** - * Returns the current virtual clock-time as it is known to this CoroutineContext. - * - * @param unit The [TimeUnit] in which the clock-time must be returned. - * @return The virtual clock-time - */ - public fun now(unit: TimeUnit = TimeUnit.MILLISECONDS): Long= - unit.convert(time, TimeUnit.NANOSECONDS) - - /** - * Moves the CoroutineContext's virtual clock forward by a specified amount of time. - * - * The returned delay-time can be larger than the specified delay-time if the code - * under test contains *blocking* Coroutines. - * - * @param delayTime The amount of time to move the CoroutineContext's clock forward. - * @param unit The [TimeUnit] in which [delayTime] and the return value is expressed. - * @return The amount of delay-time that this CoroutineContext's clock has been forwarded. - */ - public fun advanceTimeBy(delayTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS): Long { - val oldTime = time - advanceTimeTo(oldTime + unit.toNanos(delayTime), TimeUnit.NANOSECONDS) - return unit.convert(time - oldTime, TimeUnit.NANOSECONDS) - } - - /** - * Moves the CoroutineContext's clock-time to a particular moment in time. - * - * @param targetTime The point in time to which to move the CoroutineContext's clock. - * @param unit The [TimeUnit] in which [targetTime] is expressed. - */ - public fun advanceTimeTo(targetTime: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) { - val nanoTime = unit.toNanos(targetTime) - triggerActions(nanoTime) - if (nanoTime > time) time = nanoTime - } - - /** - * Triggers any actions that have not yet been triggered and that are scheduled to be triggered at or - * before this CoroutineContext's present virtual clock-time. - */ - public fun triggerActions(): Unit = triggerActions(time) - - /** - * Cancels all not yet triggered actions. Be careful calling this, since it can seriously - * mess with your coroutines work. This method should usually be called on tear-down of a - * unit test. - */ - public fun cancelAllActions() { - // An 'is-empty' test is required to avoid a NullPointerException in the 'clear()' method - if (!queue.isEmpty) queue.clear() - } - - /** - * This method does nothing if there is one unhandled exception that satisfies the given predicate. - * Otherwise it throws an [AssertionError] with the given message. - * - * (this method will clear the list of unhandled exceptions) - * - * @param message Message of the [AssertionError]. Defaults to an empty String. - * @param predicate The predicate that must be satisfied. - */ - public fun assertUnhandledException(message: String = "", predicate: (Throwable) -> Boolean) { - if (uncaughtExceptions.size != 1 || !predicate(uncaughtExceptions[0])) throw AssertionError(message) - uncaughtExceptions.clear() - } - - /** - * This method does nothing if there are no unhandled exceptions or all of them satisfy the given predicate. - * Otherwise it throws an [AssertionError] with the given message. - * - * (this method will clear the list of unhandled exceptions) - * - * @param message Message of the [AssertionError]. Defaults to an empty String. - * @param predicate The predicate that must be satisfied. - */ - public fun assertAllUnhandledExceptions(message: String = "", predicate: (Throwable) -> Boolean) { - if (!uncaughtExceptions.all(predicate)) throw AssertionError(message) - uncaughtExceptions.clear() - } - - /** - * This method does nothing if one or more unhandled exceptions satisfy the given predicate. - * Otherwise it throws an [AssertionError] with the given message. - * - * (this method will clear the list of unhandled exceptions) - * - * @param message Message of the [AssertionError]. Defaults to an empty String. - * @param predicate The predicate that must be satisfied. - */ - public fun assertAnyUnhandledException(message: String = "", predicate: (Throwable) -> Boolean) { - if (!uncaughtExceptions.any(predicate)) throw AssertionError(message) - uncaughtExceptions.clear() - } - - /** - * This method does nothing if the list of unhandled exceptions satisfy the given predicate. - * Otherwise it throws an [AssertionError] with the given message. - * - * (this method will clear the list of unhandled exceptions) - * - * @param message Message of the [AssertionError]. Defaults to an empty String. - * @param predicate The predicate that must be satisfied. - */ - public fun assertExceptions(message: String = "", predicate: (List) -> Boolean) { - if (!predicate(uncaughtExceptions)) throw AssertionError(message) - uncaughtExceptions.clear() - } - - private fun enqueue(block: Runnable) = - queue.addLast(TimedRunnableObsolete(block, counter++)) - - private fun postDelayed(block: Runnable, delayTime: Long) = - TimedRunnableObsolete(block, counter++, time + TimeUnit.MILLISECONDS.toNanos(delayTime)) - .also { - queue.addLast(it) - } - - private fun processNextEvent(): Long { - val current = queue.peek() - if (current != null) { - // Automatically advance time for EventLoop callbacks - triggerActions(current.time) - } - return if (queue.isEmpty) Long.MAX_VALUE else 0L - } - - private fun triggerActions(targetTime: Long) { - while (true) { - val current = queue.removeFirstIf { it.time <= targetTime } ?: break - // If the scheduled time is 0 (immediate) use current virtual time - if (current.time != 0L) time = current.time - current.run() - } - } - - public override fun toString(): String = name ?: "TestCoroutineContext@$hexAddress" - - private inner class Dispatcher : EventLoop(), Delay { - init { - incrementUseCount() // this event loop is never completed - } - - override fun dispatch(context: CoroutineContext, block: Runnable) { - this@TestCoroutineContext.enqueue(block) - } - - // override runBlocking to process this event loop - override fun shouldBeProcessedFromContext(): Boolean = true - - override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - postDelayed(Runnable { - with(continuation) { resumeUndispatched(Unit) } - }, timeMillis) - } - - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - val node = postDelayed(block, timeMillis) - return object : DisposableHandle { - override fun dispose() { - queue.remove(node) - } - } - } - - override fun processNextEvent() = this@TestCoroutineContext.processNextEvent() - - public override fun toString(): String = "Dispatcher(${this@TestCoroutineContext})" - } -} - -private class TimedRunnableObsolete( - private val run: Runnable, - private val count: Long = 0, - @JvmField internal val time: Long = 0 -) : Comparable, Runnable by run, ThreadSafeHeapNode { - override var heap: ThreadSafeHeap<*>? = null - override var index: Int = 0 - - override fun run() = run.run() - - override fun compareTo(other: TimedRunnableObsolete) = if (time == other.time) { - count.compareTo(other.count) - } else { - time.compareTo(other.time) - } - - override fun toString() = "TimedRunnable(time=$time, run=$run)" -} - -/** @suppress */ -@Deprecated("This API has been deprecated to integrate with Structured Concurrency.", - ReplaceWith("testContext.runBlockingTest(testBody)", "kotlin.coroutines.test"), - level = DeprecationLevel.ERROR) // ERROR in 1.6.0, removed in 1.7.0 -@Suppress("DEPRECATION_ERROR") -public fun withTestContext(testContext: TestCoroutineContext = TestCoroutineContext(), testBody: TestCoroutineContext.() -> Unit) { - with (testContext) { - testBody() - if (!exceptions.all { it is CancellationException }) { - throw AssertionError("Coroutine encountered unhandled exceptions:\n$exceptions") - } - } -} diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index f172bd4288..a26c1928c1 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -77,9 +77,6 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { for (method in listOf("clear", "peek", "removeFirstOrNull", "addLast")) { allowBlockingCallsInside("kotlinx.coroutines.internal.ThreadSafeHeap", method) } - // [addLastIf] is only used in [EventLoop.common]. Users of [removeFirstIf]: - allowBlockingCallsInside("kotlinx.coroutines.test.TestCoroutineDispatcher", "doActionsUntil") - allowBlockingCallsInside("kotlinx.coroutines.test.TestCoroutineContext", "triggerActions") } private fun BlockHound.Builder.allowBlockingCallsInFlow() {