From be4416c239ca143ba903e9d725a892beb333b85c Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Oct 2021 16:00:28 +0300 Subject: [PATCH 1/7] Configure building the test module as an MPP --- build.gradle | 51 ++-- gradle/dokka.gradle.kts | 2 +- gradle/publish.gradle | 2 +- .../common/src/Unconfined.kt | 2 +- .../common/src/flow/Migration.kt | 2 +- .../src/internal/MainDispatchers.kt | 39 +-- .../jvm/src/internal/MainDispatchersJvm.kt | 46 +++ .../api/kotlinx-coroutines-test.api | 7 +- kotlinx-coroutines-test/build.gradle.kts | 28 +- .../{ => common}/src/DelayController.kt | 2 +- .../{ => common}/src/TestBuilders.kt | 21 +- .../src/TestCoroutineDispatcher.kt | 1 + .../src/TestCoroutineExceptionHandler.kt | 14 +- .../{ => common}/src/TestCoroutineScope.kt | 2 +- .../{ => common}/src/TestDispatchers.kt | 7 +- .../src/internal/MainTestDispatcher.kt | 0 .../{ => common}/test/TestBuildersTest.kt | 1 - .../test/TestCoroutineDispatcherTest.kt | 44 +-- .../test/TestCoroutineExceptionHandlerTest.kt | 1 - .../test/TestCoroutineScopeTest.kt | 5 +- .../{ => common}/test/TestModuleHelpers.kt | 13 +- .../{ => common}/test/TestRunBlockingTest.kt | 264 +++++++++++------- .../META-INF/proguard/coroutines.pro | 0 ....coroutines.internal.MainDispatcherFactory | 0 .../jvm/test/MultithreadingTest.kt | 89 ++++++ .../test/TestCoroutineDispatcherOrderTest.kt | 9 +- .../jvm/test/TestDispatchersTest.kt | 58 ++++ .../test/TestRunBlockingOrderTest.kt | 8 +- kotlinx-coroutines-test/npm/README.md | 4 + kotlinx-coroutines-test/npm/package.json | 26 ++ .../test/TestDispatchersTest.kt | 89 ------ 31 files changed, 501 insertions(+), 336 deletions(-) rename kotlinx-coroutines-core/{jvm => common}/src/internal/MainDispatchers.kt (66%) create mode 100644 kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt rename kotlinx-coroutines-test/{ => common}/src/DelayController.kt (97%) rename kotlinx-coroutines-test/{ => common}/src/TestBuilders.kt (85%) rename kotlinx-coroutines-test/{ => common}/src/TestCoroutineDispatcher.kt (99%) rename kotlinx-coroutines-test/{ => common}/src/TestCoroutineExceptionHandler.kt (81%) rename kotlinx-coroutines-test/{ => common}/src/TestCoroutineScope.kt (99%) rename kotlinx-coroutines-test/{ => common}/src/TestDispatchers.kt (89%) rename kotlinx-coroutines-test/{ => common}/src/internal/MainTestDispatcher.kt (100%) rename kotlinx-coroutines-test/{ => common}/test/TestBuildersTest.kt (99%) rename kotlinx-coroutines-test/{ => common}/test/TestCoroutineDispatcherTest.kt (69%) rename kotlinx-coroutines-test/{ => common}/test/TestCoroutineExceptionHandlerTest.kt (95%) rename kotlinx-coroutines-test/{ => common}/test/TestCoroutineScopeTest.kt (74%) rename kotlinx-coroutines-test/{ => common}/test/TestModuleHelpers.kt (60%) rename kotlinx-coroutines-test/{ => common}/test/TestRunBlockingTest.kt (53%) rename kotlinx-coroutines-test/{ => jvm}/resources/META-INF/proguard/coroutines.pro (100%) rename kotlinx-coroutines-test/{ => jvm}/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory (100%) create mode 100644 kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt rename kotlinx-coroutines-test/{ => jvm}/test/TestCoroutineDispatcherOrderTest.kt (85%) create mode 100644 kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt rename kotlinx-coroutines-test/{ => jvm}/test/TestRunBlockingOrderTest.kt (91%) create mode 100644 kotlinx-coroutines-test/npm/README.md create mode 100644 kotlinx-coroutines-test/npm/package.json delete mode 100644 kotlinx-coroutines-test/test/TestDispatchersTest.kt diff --git a/build.gradle b/build.gradle index f55e6c39d2..5fb1da782a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,14 +4,14 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.konan.target.HostManager -import org.gradle.util.VersionNumber import org.jetbrains.dokka.gradle.DokkaTaskPartial -import org.jetbrains.dokka.gradle.DokkaMultiModuleTask apply plugin: 'jdk-convention' apply from: rootProject.file("gradle/opt-in.gradle") def coreModule = "kotlinx-coroutines-core" +def testModule = "kotlinx-coroutines-test" +def multiplatform = [coreModule, testModule] // Not applicable for Kotlin plugin def sourceless = ['kotlinx.coroutines', 'kotlinx-coroutines-bom', 'integration-testing'] def internal = ['kotlinx.coroutines', 'benchmarks', 'integration-testing'] @@ -112,7 +112,7 @@ apiValidation { ignoredProjects += unpublished + ["kotlinx-coroutines-bom"] if (build_snapshot_train) { ignoredProjects.remove("example-frontend-js") - ignoredProjects.add("kotlinx-coroutines-core") + ignoredProjects.add(coreModule) } ignoredPackages += "kotlinx.coroutines.internal" } @@ -133,13 +133,31 @@ allprojects { // Add dependency to core source sets. Core is configured in kx-core/build.gradle configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != coreModule }) { evaluationDependsOn(":$coreModule") - def platform = PlatformKt.platformOf(it) - apply plugin: "kotlin-${platform}-conventions" - dependencies { - // See comment below for rationale, it will be replaced with "project" dependency - api project(":$coreModule") - // the only way IDEA can resolve test classes - testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs + if (it.name in multiplatform) { + apply plugin: "kotlin-multiplatform" + apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle") + apply from: rootProject.file("gradle/compile-common.gradle") + + if (rootProject.ext["native_targets_enabled"] as Boolean) { + apply from: rootProject.file("gradle/compile-native-multiplatform.gradle") + } + + apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") + apply from: rootProject.file("gradle/publish-npm-js.gradle") + kotlin.sourceSets.commonMain.dependencies { + api project(":$coreModule") + } + kotlin.sourceSets.jvmTest.dependencies { + implementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs + } + } else { + def platform = PlatformKt.platformOf(it) + apply plugin: "kotlin-${platform}-conventions" + dependencies { + api project(":$coreModule") + // the only way IDEA can resolve test classes + testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs + } } } @@ -177,7 +195,7 @@ if (build_snapshot_train) { } println "Manifest of kotlin-compiler-embeddable.jar for coroutines" - configure(subprojects.findAll { it.name == "kotlinx-coroutines-core" }) { + configure(subprojects.findAll { it.name == coreModule }) { configurations.matching { it.name == "kotlinCompilerClasspath" }.all { resolvedConfiguration.getFiles().findAll { it.name.contains("kotlin-compiler-embeddable") }.each { def manifest = zipTree(it).matching { @@ -194,9 +212,8 @@ if (build_snapshot_train) { // Redefine source sets because we are not using 'kotlin/main/fqn' folder convention configure(subprojects.findAll { - !sourceless.contains(it.name) && + !sourceless.contains(it.name) && !multiplatform.contains(it.name) && it.name != "benchmarks" && - it.name != coreModule && it.name != "example-frontend-js" }) { // Pure JS and pure MPP doesn't have this notion and are configured separately @@ -250,7 +267,7 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) { } List jarTasks - if (it.name == "kotlinx-coroutines-core") { + if (it.name in multiplatform) { jarTasks = ["jvmJar", "metadataJar"] } else if (it.name == "kotlinx-coroutines-debug") { // We shadow debug module instead of just packaging it @@ -324,12 +341,12 @@ allprojects { subProject -> .matching { // Excluding substituted project itself because of circular dependencies, but still do it // for "*Test*" configurations - subProject.name != "kotlinx-coroutines-core" || it.name.contains("Test") + subProject.name != coreModule || it.name.contains("Test") } .configureEach { conf -> conf.resolutionStrategy.dependencySubstitution { - substitute(module("org.jetbrains.kotlinx:kotlinx-coroutines-core")) - .using(project(":kotlinx-coroutines-core")) + substitute(module("org.jetbrains.kotlinx:$coreModule")) + .using(project(":$coreModule")) .because("Because Kotlin compiler embeddable leaks coroutines into the runtime classpath, " + "triggering all sort of incompatible class changes errors") } diff --git a/gradle/dokka.gradle.kts b/gradle/dokka.gradle.kts index 659890a30b..a4926f7e61 100644 --- a/gradle/dokka.gradle.kts +++ b/gradle/dokka.gradle.kts @@ -37,7 +37,7 @@ tasks.withType(DokkaTaskPartial::class).configureEach { packageListUrl.set(rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL()) } - if (project.name != "kotlinx-coroutines-core") { + if (project.name != "kotlinx-coroutines-core" && project.name != "kotlinx-coroutines-test") { dependsOn(project.configurations["compileClasspath"]) doFirst { // resolve classpath only during execution diff --git a/gradle/publish.gradle b/gradle/publish.gradle index 3a0a4224ab..fa2bbb8544 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -12,7 +12,7 @@ apply plugin: 'signing' // ------------- tasks -def isMultiplatform = project.name == "kotlinx-coroutines-core" +def isMultiplatform = project.name == "kotlinx-coroutines-core" || project.name == "kotlinx-coroutines-test" def isBom = project.name == "kotlinx-coroutines-bom" if (!isBom) { diff --git a/kotlinx-coroutines-core/common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt index 4f48645895..df0087100a 100644 --- a/kotlinx-coroutines-core/common/src/Unconfined.kt +++ b/kotlinx-coroutines-core/common/src/Unconfined.kt @@ -14,7 +14,7 @@ internal object Unconfined : CoroutineDispatcher() { override fun isDispatchNeeded(context: CoroutineContext): Boolean = false override fun dispatch(context: CoroutineContext, block: Runnable) { - // It can only be called by the "yield" function. See also code of "yield" function. + /** It can only be called by the [yield] function. See also code of [yield] function. */ val yieldContext = context[YieldContext] if (yieldContext != null) { // report to "yield" that it is an unconfined dispatcher and don't call "block.run()" diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt index 6278081a5d..64effbf395 100644 --- a/kotlinx-coroutines-core/common/src/flow/Migration.kt +++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt @@ -260,7 +260,7 @@ public fun Flow.skip(count: Int): Flow = noImpl() @Deprecated( level = DeprecationLevel.ERROR, message = "Flow analogue of 'forEach' is 'collect'", - replaceWith = ReplaceWith("collect(block)") + replaceWith = ReplaceWith("collect(action)") ) public fun Flow.forEach(action: suspend (value: T) -> Unit): Unit = noImpl() diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt similarity index 66% rename from kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt rename to kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt index 2d447413b8..afd484520a 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt +++ b/kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt @@ -5,45 +5,8 @@ package kotlinx.coroutines.internal import kotlinx.coroutines.* -import java.util.* import kotlin.coroutines.* -/** - * Name of the boolean property that enables using of [FastServiceLoader]. - */ -private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader" - -// Lazy loader for the main dispatcher -internal object MainDispatcherLoader { - - private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true) - - @JvmField - val dispatcher: MainCoroutineDispatcher = loadMainDispatcher() - - private fun loadMainDispatcher(): MainCoroutineDispatcher { - return try { - val factories = if (FAST_SERVICE_LOADER_ENABLED) { - FastServiceLoader.loadMainDispatcherFactory() - } else { - // We are explicitly using the - // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()` - // form of the ServiceLoader call to enable R8 optimization when compiled on Android. - ServiceLoader.load( - MainDispatcherFactory::class.java, - MainDispatcherFactory::class.java.classLoader - ).iterator().asSequence().toList() - } - @Suppress("ConstantConditionIf") - factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories) - ?: createMissingDispatcher() - } catch (e: Throwable) { - // Service loader can throw an exception as well - createMissingDispatcher(e) - } - } -} - /** * If anything goes wrong while trying to create main dispatcher (class not found, * initialization failed, etc), then replace the main dispatcher with a special @@ -71,7 +34,7 @@ private val SUPPORT_MISSING = true "ConstantConditionIf", "IMPLICIT_NOTHING_TYPE_ARGUMENT_AGAINST_NOT_NOTHING_EXPECTED_TYPE" // KT-47626 ) -private fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null) = +internal fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null): MainCoroutineDispatcher = if (SUPPORT_MISSING) MissingMainCoroutineDispatcher(cause, errorHint) else cause?.let { throw it } ?: throwMissingMainDispatcherException() diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt new file mode 100644 index 0000000000..0bac97ee47 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.coroutines.* +import java.util.* +import kotlin.coroutines.* + +/** + * Name of the boolean property that enables using of [FastServiceLoader]. + */ +private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader" + +// Lazy loader for the main dispatcher +internal object MainDispatcherLoader { + + private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true) + + @JvmField + val dispatcher: MainCoroutineDispatcher = loadMainDispatcher() + + private fun loadMainDispatcher(): MainCoroutineDispatcher { + return try { + val factories = if (FAST_SERVICE_LOADER_ENABLED) { + FastServiceLoader.loadMainDispatcherFactory() + } else { + // We are explicitly using the + // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()` + // form of the ServiceLoader call to enable R8 optimization when compiled on Android. + ServiceLoader.load( + MainDispatcherFactory::class.java, + MainDispatcherFactory::class.java.classLoader + ).iterator().asSequence().toList() + } + @Suppress("ConstantConditionIf") + factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories) + ?: createMissingDispatcher() + } catch (e: Throwable) { + // Service loader can throw an exception as well + createMissingDispatcher(e) + } + } +} + diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api index c99ec5cbf1..707ee43df2 100644 --- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api +++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api @@ -36,7 +36,7 @@ public final class kotlinx/coroutines/test/TestCoroutineDispatcher : kotlinx/cor public final class kotlinx/coroutines/test/TestCoroutineExceptionHandler : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/CoroutineExceptionHandler, kotlinx/coroutines/test/UncaughtExceptionCaptor { public fun ()V - public fun cleanupTestCoroutines ()V + public fun cleanupTestCoroutinesCaptor ()V public fun getUncaughtExceptions ()Ljava/util/List; public fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V } @@ -56,12 +56,11 @@ public final class kotlinx/coroutines/test/TestDispatchers { } public abstract interface class kotlinx/coroutines/test/UncaughtExceptionCaptor { - public abstract fun cleanupTestCoroutines ()V + public abstract fun cleanupTestCoroutinesCaptor ()V public abstract fun getUncaughtExceptions ()Ljava/util/List; } public final class kotlinx/coroutines/test/UncompletedCoroutinesError : java/lang/AssertionError { - public fun (Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;)V } diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index fef0a146f7..577d3e5261 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -1,7 +1,31 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +import java.net.URL -dependencies { - implementation(project(":kotlinx-coroutines-debug")) +val experimentalAnnotations = listOf( + "kotlin.Experimental", + "kotlin.experimental.ExperimentalTypeInference", + "kotlin.ExperimentalMultiplatform", + "kotlinx.coroutines.ExperimentalCoroutinesApi", + "kotlinx.coroutines.ObsoleteCoroutinesApi", + "kotlinx.coroutines.InternalCoroutinesApi", + "kotlinx.coroutines.FlowPreview" +) + +kotlin { + sourceSets.all { + val srcDir = if (name.endsWith("Main")) "src" else "test" + val platform = name.dropLast(4) + kotlin.srcDir("$platform/$srcDir") + if (name == "jvmMain") { + resources.srcDir("$platform/resources") + } else if (name == "jvmTest") { + resources.srcDir("$platform/test-resources") + } + languageSettings { + progressiveMode = true + experimentalAnnotations.forEach { useExperimentalAnnotation(it) } + } + } } diff --git a/kotlinx-coroutines-test/src/DelayController.kt b/kotlinx-coroutines-test/common/src/DelayController.kt similarity index 97% rename from kotlinx-coroutines-test/src/DelayController.kt rename to kotlinx-coroutines-test/common/src/DelayController.kt index 6e72222718..a4ab8c4aba 100644 --- a/kotlinx-coroutines-test/src/DelayController.kt +++ b/kotlinx-coroutines-test/common/src/DelayController.kt @@ -126,4 +126,4 @@ public interface DelayController { */ // 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 -public class UncompletedCoroutinesError(message: String, cause: Throwable? = null): AssertionError(message, cause) +public class UncompletedCoroutinesError(message: String): AssertionError(message) diff --git a/kotlinx-coroutines-test/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt similarity index 85% rename from kotlinx-coroutines-test/src/TestBuilders.kt rename to kotlinx-coroutines-test/common/src/TestBuilders.kt index b40769ee97..dde9ac7b12 100644 --- a/kotlinx-coroutines-test/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -80,19 +80,16 @@ public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineS runBlockingTest(this, block) private fun CoroutineContext.checkArguments(): Pair { - // TODO optimize it - val dispatcher = get(ContinuationInterceptor).run { - this?.let { require(this is DelayController) { "Dispatcher must implement DelayController: $this" } } - this ?: TestCoroutineDispatcher() + val dispatcher = when (val dispatcher = get(ContinuationInterceptor)) { + is DelayController -> dispatcher + null -> TestCoroutineDispatcher() + else -> throw IllegalArgumentException("Dispatcher must implement DelayController: $dispatcher") } - - val exceptionHandler = get(CoroutineExceptionHandler).run { - this?.let { - require(this is UncaughtExceptionCaptor) { "coroutineExceptionHandler must implement UncaughtExceptionCaptor: $this" } - } - this ?: TestCoroutineExceptionHandler() + val exceptionHandler = when (val handler = get(CoroutineExceptionHandler)) { + is UncaughtExceptionCaptor -> handler + null -> TestCoroutineExceptionHandler() + else -> throw IllegalArgumentException("coroutineExceptionHandler must implement UncaughtExceptionCaptor: $handler") } - val job = get(Job) ?: SupervisorJob() - return Pair(this + dispatcher + exceptionHandler + job, dispatcher as DelayController) + return Pair(this + dispatcher + exceptionHandler + job, dispatcher) } diff --git a/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt similarity index 99% rename from kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt rename to kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt index f6464789fc..5bd6783d40 100644 --- a/kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt @@ -8,6 +8,7 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* +import kotlin.jvm.* import kotlin.math.* /** diff --git a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt similarity index 81% rename from kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt rename to kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt index 66eb235906..b1296df12a 100644 --- a/kotlinx-coroutines-test/src/TestCoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineExceptionHandler.kt @@ -5,6 +5,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* +import kotlinx.coroutines.internal.* import kotlin.coroutines.* /** @@ -16,7 +17,7 @@ public interface UncaughtExceptionCaptor { * List of uncaught coroutine exceptions. * * The returned list is a copy of the currently caught exceptions. - * During [cleanupTestCoroutines] the first element of this list is rethrown if it is not empty. + * During [cleanupTestCoroutinesCaptor] the first element of this list is rethrown if it is not empty. */ public val uncaughtExceptions: List @@ -28,7 +29,7 @@ public interface UncaughtExceptionCaptor { * * @throws Throwable the first uncaught exception, if there are any uncaught exceptions. */ - public fun cleanupTestCoroutines() + public fun cleanupTestCoroutinesCaptor() } /** @@ -39,21 +40,22 @@ public class TestCoroutineExceptionHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), UncaughtExceptionCaptor, CoroutineExceptionHandler { private val _exceptions = mutableListOf() + private val _lock = SynchronizedObject() /** @suppress **/ override fun handleException(context: CoroutineContext, exception: Throwable) { - synchronized(_exceptions) { + synchronized(_lock) { _exceptions += exception } } /** @suppress **/ override val uncaughtExceptions: List - get() = synchronized(_exceptions) { _exceptions.toList() } + get() = synchronized(_lock) { _exceptions.toList() } /** @suppress **/ - override fun cleanupTestCoroutines() { - synchronized(_exceptions) { + override fun cleanupTestCoroutinesCaptor() { + synchronized(_lock) { val exception = _exceptions.firstOrNull() ?: return // log the rest _exceptions.drop(1).forEach { it.printStackTrace() } diff --git a/kotlinx-coroutines-test/src/TestCoroutineScope.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt similarity index 99% rename from kotlinx-coroutines-test/src/TestCoroutineScope.kt rename to kotlinx-coroutines-test/common/src/TestCoroutineScope.kt index 7c1ff872ec..fc467e2444 100644 --- a/kotlinx-coroutines-test/src/TestCoroutineScope.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt @@ -31,7 +31,7 @@ private class TestCoroutineScopeImpl ( DelayController by coroutineContext.delayController { override fun cleanupTestCoroutines() { - coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutines() + coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutinesCaptor() coroutineContext.delayController.cleanupTestCoroutines() } } diff --git a/kotlinx-coroutines-test/src/TestDispatchers.kt b/kotlinx-coroutines-test/common/src/TestDispatchers.kt similarity index 89% rename from kotlinx-coroutines-test/src/TestDispatchers.kt rename to kotlinx-coroutines-test/common/src/TestDispatchers.kt index bf068f9d7b..6400fddc5b 100644 --- a/kotlinx-coroutines-test/src/TestDispatchers.kt +++ b/kotlinx-coroutines-test/common/src/TestDispatchers.kt @@ -8,17 +8,18 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlinx.coroutines.test.internal.* +import kotlin.jvm.* /** * Sets the given [dispatcher] as an underlying dispatcher of [Dispatchers.Main]. - * All consecutive usages of [Dispatchers.Main] will use given [dispatcher] under the hood. + * All subsequent usages of [Dispatchers.Main] will use given [dispatcher] under the hood. * * It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist. */ @ExperimentalCoroutinesApi public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" } - val mainDispatcher = Dispatchers.Main + val mainDispatcher = Main require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." } mainDispatcher.setDispatcher(dispatcher) } @@ -32,7 +33,7 @@ public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { */ @ExperimentalCoroutinesApi public fun Dispatchers.resetMain() { - val mainDispatcher = Dispatchers.Main + val mainDispatcher = Main require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." } mainDispatcher.resetDispatcher() } diff --git a/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt b/kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt similarity index 100% rename from kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt rename to kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt diff --git a/kotlinx-coroutines-test/test/TestBuildersTest.kt b/kotlinx-coroutines-test/common/test/TestBuildersTest.kt similarity index 99% rename from kotlinx-coroutines-test/test/TestBuildersTest.kt rename to kotlinx-coroutines-test/common/test/TestBuildersTest.kt index 27c8f5fb19..8f85a64c7d 100644 --- a/kotlinx-coroutines-test/test/TestBuildersTest.kt +++ b/kotlinx-coroutines-test/common/test/TestBuildersTest.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* -import org.junit.Test import kotlin.coroutines.* import kotlin.test.* diff --git a/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherTest.kt similarity index 69% rename from kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt rename to kotlinx-coroutines-test/common/test/TestCoroutineDispatcherTest.kt index 260edf9dc8..baf946f2e1 100644 --- a/kotlinx-coroutines-test/test/TestCoroutineDispatcherTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherTest.kt @@ -1,11 +1,10 @@ /* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * 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 org.junit.Test import kotlin.test.* class TestCoroutineDispatcherTest { @@ -91,7 +90,7 @@ class TestCoroutineDispatcherTest { assertEquals(1, executed) } - @Test(expected = UncompletedCoroutinesError::class) + @Test fun whenDispatcherHasUncompletedCoroutines_itThrowsErrorInCleanup() { val subject = TestCoroutineDispatcher() subject.pauseDispatcher() @@ -99,44 +98,7 @@ class TestCoroutineDispatcherTest { scope.launch { delay(1_000) } - subject.cleanupTestCoroutines() - } - - @Test - fun whenDispatchCalled_runsOnCurrentThread() { - val currentThread = Thread.currentThread() - val subject = TestCoroutineDispatcher() - val scope = TestCoroutineScope(subject) - - val deferred = scope.async(Dispatchers.Default) { - withContext(subject) { - assertNotSame(currentThread, Thread.currentThread()) - 3 - } - } - - runBlocking { - // just to ensure the above code terminates - assertEquals(3, deferred.await()) - } + assertFailsWith { subject.cleanupTestCoroutines() } } - @Test - fun whenAllDispatchersMocked_runsOnSameThread() { - val currentThread = Thread.currentThread() - val subject = TestCoroutineDispatcher() - val scope = TestCoroutineScope(subject) - - val deferred = scope.async(subject) { - withContext(subject) { - assertSame(currentThread, Thread.currentThread()) - 3 - } - } - - runBlocking { - // just to ensure the above code terminates - assertEquals(3, deferred.await()) - } - } } \ No newline at end of file diff --git a/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt similarity index 95% rename from kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt rename to kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt index 1a0833af50..7dbea56f5b 100644 --- a/kotlinx-coroutines-test/test/TestCoroutineExceptionHandlerTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt @@ -4,7 +4,6 @@ package kotlinx.coroutines.test -import org.junit.Test import kotlin.test.* class TestCoroutineExceptionHandlerTest { diff --git a/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineScopeTest.kt similarity index 74% rename from kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt rename to kotlinx-coroutines-test/common/test/TestCoroutineScopeTest.kt index fa14c38409..4480cd99a3 100644 --- a/kotlinx-coroutines-test/test/TestCoroutineScopeTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineScopeTest.kt @@ -5,13 +5,12 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* -import org.junit.Test import kotlin.test.* class TestCoroutineScopeTest { @Test fun whenGivenInvalidExceptionHandler_throwsException() { - val handler = CoroutineExceptionHandler { _, _ -> Unit } + val handler = CoroutineExceptionHandler { _, _ -> } assertFails { TestCoroutineScope(handler) } @@ -20,7 +19,7 @@ class TestCoroutineScopeTest { @Test fun whenGivenInvalidDispatcher_throwsException() { assertFails { - TestCoroutineScope(newSingleThreadContext("incorrect call")) + TestCoroutineScope(Dispatchers.Default) } } } diff --git a/kotlinx-coroutines-test/test/TestModuleHelpers.kt b/kotlinx-coroutines-test/common/test/TestModuleHelpers.kt similarity index 60% rename from kotlinx-coroutines-test/test/TestModuleHelpers.kt rename to kotlinx-coroutines-test/common/test/TestModuleHelpers.kt index 12541bd90f..a34dbfd6c7 100644 --- a/kotlinx-coroutines-test/test/TestModuleHelpers.kt +++ b/kotlinx-coroutines-test/common/test/TestModuleHelpers.kt @@ -5,18 +5,21 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* -import org.junit.* -import java.time.* +import kotlin.test.* +import kotlin.time.* const val SLOW = 10_000L /** * Assert a block completes within a second or fail the suite */ +@OptIn(ExperimentalTime::class) suspend fun CoroutineScope.assertRunsFast(block: suspend CoroutineScope.() -> Unit) { - val start = Instant.now().toEpochMilli() + val start = TimeSource.Monotonic.markNow() // don't need to be fancy with timeouts here since anything longer than a few ms is an error block() - val duration = Instant.now().minusMillis(start).toEpochMilli() - Assert.assertTrue("All tests must complete within 2000ms (use longer timeouts to cause failure)", duration < 2_000) + val duration = start.elapsedNow() + assertTrue("All tests must complete within 2000ms (use longer timeouts to cause failure)") { + duration.inWholeSeconds < 2 + } } diff --git a/kotlinx-coroutines-test/test/TestRunBlockingTest.kt b/kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt similarity index 53% rename from kotlinx-coroutines-test/test/TestRunBlockingTest.kt rename to kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt index e0c7091505..7af3a4f4fc 100644 --- a/kotlinx-coroutines-test/test/TestRunBlockingTest.kt +++ b/kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* -import kotlin.coroutines.* import kotlin.test.* class TestRunBlockingTest { @@ -53,41 +52,59 @@ class TestRunBlockingTest { } @Test - fun incorrectlyCalledRunblocking_doesNotHaveSameInterceptor() = runBlockingTest { - // this code is an error as a production test, please do not use this as an example - - // this test exists to document this error condition, if it's possible to make this code work please update - val outerInterceptor = coroutineContext[ContinuationInterceptor] - // runBlocking always requires an argument to pass the context in tests - runBlocking { - assertNotSame(coroutineContext[ContinuationInterceptor], outerInterceptor) + fun whenUsingTimeout_triggersWhenDelayed() { + assertFailsWith { + runBlockingTest { + assertRunsFast { + withTimeout(SLOW) { + delay(SLOW) + } + } + } } } - @Test(expected = TimeoutCancellationException::class) - fun whenUsingTimeout_triggersWhenDelayed() = runBlockingTest { + @Test + fun whenUsingTimeout_doesNotTriggerWhenFast() = runBlockingTest { assertRunsFast { withTimeout(SLOW) { - delay(SLOW) + delay(0) } } } @Test - fun whenUsingTimeout_doesNotTriggerWhenFast() = runBlockingTest { - assertRunsFast { - withTimeout(SLOW) { - delay(0) + fun kryak() { + class ClassUnderTest(private val mainScope: CoroutineScope, private val bgDispatcher: CoroutineDispatcher) { + fun doSomething() { + mainScope.launch { + val data = withContext(bgDispatcher) { + delay(1000) + 42 + } + println(data) + } } } + + val testBgDispatcher = TestCoroutineDispatcher() + val testScope = TestCoroutineScope() + val subject = ClassUnderTest(testScope, testBgDispatcher) + + subject.doSomething() + testScope.advanceUntilIdle() } - @Test(expected = TimeoutCancellationException::class) - fun whenUsingTimeout_triggersWhenWaiting() = runBlockingTest { - val uncompleted = CompletableDeferred() - assertRunsFast { - withTimeout(SLOW) { - uncompleted.await() + @Test + fun whenUsingTimeout_triggersWhenWaiting() { + assertFailsWith { + runBlockingTest { + val uncompleted = CompletableDeferred() + assertRunsFast { + withTimeout(SLOW) { + uncompleted.await() + } + } } } } @@ -114,16 +131,20 @@ class TestRunBlockingTest { } } - @Test(expected = TimeoutCancellationException::class) - fun whenUsingTimeout_inAsync_triggersWhenDelayed() = runBlockingTest { - val deferred = async { - withTimeout(SLOW) { - delay(SLOW) - } - } + @Test + fun whenUsingTimeout_inAsync_triggersWhenDelayed() { + assertFailsWith { + runBlockingTest { + val deferred = async { + withTimeout(SLOW) { + delay(SLOW) + } + } - assertRunsFast { - deferred.await() + assertRunsFast { + deferred.await() + } + } } } @@ -141,18 +162,21 @@ class TestRunBlockingTest { } } - @Test(expected = TimeoutCancellationException::class) - fun whenUsingTimeout_inLaunch_triggersWhenDelayed() = runBlockingTest { - val job= launch { - withTimeout(1) { - delay(SLOW + 1) - 3 - } - } + @Test + fun whenUsingTimeout_inLaunch_triggersWhenDelayed() { + assertFailsWith { + runBlockingTest { + val job = launch { + withTimeout(1) { + delay(SLOW + 1) + } + } - assertRunsFast { - job.join() - throw job.getCancellationException() + assertRunsFast { + job.join() + throw job.getCancellationException() + } + } } } @@ -170,36 +194,48 @@ class TestRunBlockingTest { } } - @Test(expected = IllegalArgumentException::class) - fun throwingException_throws() = runBlockingTest { - assertRunsFast { - delay(SLOW) - throw IllegalArgumentException("Test") + @Test + fun throwingException_throws() { + assertFailsWith { + runBlockingTest { + assertRunsFast { + delay(SLOW) + throw IllegalArgumentException("Test") + } + } } } - @Test(expected = IllegalArgumentException::class) - fun throwingException_inLaunch_throws() = runBlockingTest { - val job = launch { - delay(SLOW) - throw IllegalArgumentException("Test") - } + @Test + fun throwingException_inLaunch_throws() { + assertFailsWith { + runBlockingTest { + val job = launch { + delay(SLOW) + throw IllegalArgumentException("Test") + } - assertRunsFast { - job.join() - throw job.getCancellationException().cause ?: assertFails { "expected exception" } + assertRunsFast { + job.join() + throw job.getCancellationException().cause ?: assertFails { "expected exception" } + } + } } } - @Test(expected = IllegalArgumentException::class) - fun throwingException__inAsync_throws() = runBlockingTest { - val deferred = async { - delay(SLOW) - throw IllegalArgumentException("Test") - } + @Test + fun throwingException__inAsync_throws() { + assertFailsWith { + runBlockingTest { + val deferred: Deferred = async { + delay(SLOW) + throw IllegalArgumentException("Test") + } - assertRunsFast { - deferred.await() + assertRunsFast { + deferred.await() + } + } } } @@ -273,25 +309,33 @@ class TestRunBlockingTest { job.join() } - @Test(expected = UncompletedCoroutinesError::class) - fun whenACoroutineLeaks_errorIsThrown() = runBlockingTest { - val uncompleted = CompletableDeferred() - launch { - uncompleted.await() + @Test + fun whenACoroutineLeaks_errorIsThrown() { + assertFailsWith { + runBlockingTest { + val uncompleted = CompletableDeferred() + launch { + uncompleted.await() + } + } } } - @Test(expected = java.lang.IllegalArgumentException::class) + @Test fun runBlockingTestBuilder_throwsOnBadDispatcher() { - runBlockingTest(newSingleThreadContext("name")) { + assertFailsWith { + runBlockingTest(Dispatchers.Default) { + } } } - @Test(expected = java.lang.IllegalArgumentException::class) + @Test fun runBlockingTestBuilder_throwsOnBadHandler() { - runBlockingTest(CoroutineExceptionHandler { _, _ -> Unit} ) { + assertFailsWith { + runBlockingTest(CoroutineExceptionHandler { _, _ -> }) { + } } } @@ -338,36 +382,48 @@ class TestRunBlockingTest { } - @Test(expected = IllegalAccessError::class) - fun testWithTestContextThrowingAnAssertionError() = runBlockingTest { - val expectedError = IllegalAccessError("hello") + @Test + fun testWithTestContextThrowingAnAssertionError() { + assertFailsWith { + runBlockingTest { + val expectedError = TestException("hello") - val job = launch { - throw expectedError - } + val job = launch { + throw expectedError + } - // don't rethrow or handle the exception + // don't rethrow or handle the exception + } + } } - @Test(expected = IllegalAccessError::class) - fun testExceptionHandlingWithLaunch() = runBlockingTest { - val expectedError = IllegalAccessError("hello") + @Test + fun testExceptionHandlingWithLaunch() { + assertFailsWith { + runBlockingTest { + val expectedError = TestException("hello") - launch { - throw expectedError + launch { + throw expectedError + } + } } } - @Test(expected = IllegalAccessError::class) - fun testExceptions_notThrownImmediately() = runBlockingTest { - val expectedException = IllegalAccessError("hello") - val result = runCatching { - launch { - throw expectedException + @Test + fun testExceptions_notThrownImmediately() { + assertFailsWith { + runBlockingTest { + val expectedException = TestException("hello") + val result = runCatching { + launch { + throw expectedException + } + } + runCurrent() + assertEquals(true, result.isSuccess) } } - runCurrent() - assertEquals(true, result.isSuccess) } @@ -380,9 +436,13 @@ class TestRunBlockingTest { assertNotSame(coroutineContext[CoroutineExceptionHandler], exceptionHandler) } - @Test(expected = IllegalArgumentException::class) - fun testPartialDispatcherOverride() = runBlockingTest(Dispatchers.Unconfined) { - fail("Unreached") + @Test + fun testPartialDispatcherOverride() { + assertFailsWith { + runBlockingTest(Dispatchers.Unconfined) { + fail("Unreached") + } + } } @Test @@ -390,8 +450,14 @@ class TestRunBlockingTest { assertSame(coroutineContext[CoroutineExceptionHandler], exceptionHandler) } - @Test(expected = IllegalArgumentException::class) - fun testOverrideExceptionHandlerError() = runBlockingTest(CoroutineExceptionHandler { _, _ -> }) { - fail("Unreached") + @Test + fun testOverrideExceptionHandlerError() { + assertFailsWith { + runBlockingTest(CoroutineExceptionHandler { _, _ -> }) { + fail("Unreached") + } + } } } + +private class TestException(message: String? = null): Exception(message) \ No newline at end of file diff --git a/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-test/jvm/resources/META-INF/proguard/coroutines.pro similarity index 100% rename from kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro rename to kotlinx-coroutines-test/jvm/resources/META-INF/proguard/coroutines.pro diff --git a/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory similarity index 100% rename from kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory rename to kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory diff --git a/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt b/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt new file mode 100644 index 0000000000..d06f2a35c6 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import kotlinx.coroutines.* +import kotlinx.coroutines.test.* +import kotlin.coroutines.* +import kotlin.test.* + +class MultithreadingTest : TestBase() { + + @Test + fun incorrectlyCalledRunblocking_doesNotHaveSameInterceptor() = runBlockingTest { + // this code is an error as a production test, please do not use this as an example + + // this test exists to document this error condition, if it's possible to make this code work please update + val outerInterceptor = coroutineContext[ContinuationInterceptor] + // runBlocking always requires an argument to pass the context in tests + runBlocking { + assertNotSame(coroutineContext[ContinuationInterceptor], outerInterceptor) + } + } + + @Test + fun testSingleThreadExecutor() = runTest { + val mainThread = Thread.currentThread() + Dispatchers.setMain(Dispatchers.Unconfined) + newSingleThreadContext("testSingleThread").use { threadPool -> + withContext(Dispatchers.Main) { + assertSame(mainThread, Thread.currentThread()) + } + + Dispatchers.setMain(threadPool) + withContext(Dispatchers.Main) { + assertNotSame(mainThread, Thread.currentThread()) + } + assertSame(mainThread, Thread.currentThread()) + + withContext(Dispatchers.Main.immediate) { + assertNotSame(mainThread, Thread.currentThread()) + } + assertSame(mainThread, Thread.currentThread()) + + Dispatchers.setMain(Dispatchers.Unconfined) + withContext(Dispatchers.Main.immediate) { + assertSame(mainThread, Thread.currentThread()) + } + assertSame(mainThread, Thread.currentThread()) + } + } + + @Test + fun whenDispatchCalled_runsOnCurrentThread() { + val currentThread = Thread.currentThread() + val subject = TestCoroutineDispatcher() + val scope = TestCoroutineScope(subject) + + val deferred = scope.async(Dispatchers.Default) { + withContext(subject) { + assertNotSame(currentThread, Thread.currentThread()) + 3 + } + } + + runBlocking { + // just to ensure the above code terminates + assertEquals(3, deferred.await()) + } + } + + @Test + fun whenAllDispatchersMocked_runsOnSameThread() { + val currentThread = Thread.currentThread() + val subject = TestCoroutineDispatcher() + val scope = TestCoroutineScope(subject) + + val deferred = scope.async(subject) { + withContext(subject) { + assertSame(currentThread, Thread.currentThread()) + 3 + } + } + + runBlocking { + // just to ensure the above code terminates + assertEquals(3, deferred.await()) + } + } +} \ No newline at end of file diff --git a/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt b/kotlinx-coroutines-test/jvm/test/TestCoroutineDispatcherOrderTest.kt similarity index 85% rename from kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt rename to kotlinx-coroutines-test/jvm/test/TestCoroutineDispatcherOrderTest.kt index 116aadcf8d..39bb1f78f7 100644 --- a/kotlinx-coroutines-test/test/TestCoroutineDispatcherOrderTest.kt +++ b/kotlinx-coroutines-test/jvm/test/TestCoroutineDispatcherOrderTest.kt @@ -1,9 +1,10 @@ -package kotlinx.coroutines.test +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ import kotlinx.coroutines.* -import org.junit.* -import kotlin.coroutines.* -import kotlin.test.assertEquals +import kotlinx.coroutines.test.* +import kotlin.test.* class TestCoroutineDispatcherOrderTest : TestBase() { diff --git a/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt new file mode 100644 index 0000000000..ce02a31594 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import kotlinx.coroutines.* +import kotlinx.coroutines.test.* +import kotlin.coroutines.* +import kotlin.test.* + +class TestDispatchersTest : TestBase() { + + @BeforeTest + fun setUp() { + Dispatchers.resetMain() + } + + @Test + fun testSelfSet() = runTest { + assertFailsWith { Dispatchers.setMain(Dispatchers.Main) } + } + + @Test + fun testImmediateDispatcher() = runTest { + Dispatchers.setMain(ImmediateDispatcher()) + expect(1) + withContext(Dispatchers.Main) { + expect(3) + } + + Dispatchers.setMain(RegularDispatcher()) + withContext(Dispatchers.Main) { + expect(6) + } + + finish(7) + } + + private inner class ImmediateDispatcher : CoroutineDispatcher() { + override fun isDispatchNeeded(context: CoroutineContext): Boolean { + expect(2) + return false + } + + override fun dispatch(context: CoroutineContext, block: Runnable) = expectUnreached() + } + + private inner class RegularDispatcher : CoroutineDispatcher() { + override fun isDispatchNeeded(context: CoroutineContext): Boolean { + expect(4) + return true + } + + override fun dispatch(context: CoroutineContext, block: Runnable) { + expect(5) + block.run() + } + } +} diff --git a/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt b/kotlinx-coroutines-test/jvm/test/TestRunBlockingOrderTest.kt similarity index 91% rename from kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt rename to kotlinx-coroutines-test/jvm/test/TestRunBlockingOrderTest.kt index e21c82b95c..2b18ca63bd 100644 --- a/kotlinx-coroutines-test/test/TestRunBlockingOrderTest.kt +++ b/kotlinx-coroutines-test/jvm/test/TestRunBlockingOrderTest.kt @@ -1,12 +1,10 @@ /* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * 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 org.junit.* -import kotlin.coroutines.* +import kotlinx.coroutines.test.* +import kotlin.test.* class TestRunBlockingOrderTest : TestBase() { @Test diff --git a/kotlinx-coroutines-test/npm/README.md b/kotlinx-coroutines-test/npm/README.md new file mode 100644 index 0000000000..4df4825da9 --- /dev/null +++ b/kotlinx-coroutines-test/npm/README.md @@ -0,0 +1,4 @@ +# kotlinx-coroutines-test + +Testing support for `kotlinx-coroutines` in +[Kotlin/JS](https://kotlinlang.org/docs/js-overview.html). diff --git a/kotlinx-coroutines-test/npm/package.json b/kotlinx-coroutines-test/npm/package.json new file mode 100644 index 0000000000..adb7521c6f --- /dev/null +++ b/kotlinx-coroutines-test/npm/package.json @@ -0,0 +1,26 @@ +{ + "name": "kotlinx-coroutines-test", + "version" : "$version", + "description" : "Test utilities for kotlinx-coroutines", + "main" : "kotlinx-coroutines-test.js", + "author": "JetBrains", + "license": "Apache-2.0", + "homepage": "https://github.com/Kotlin/kotlinx.coroutines", + "bugs": { + "url": "https://github.com/Kotlin/kotlinx.coroutines/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Kotlin/kotlinx.coroutines.git" + }, + "keywords": [ + "Kotlin", + "async", + "coroutines", + "JetBrains", + "test" + ], + "dependencies": { + "kotlinx-coroutines-core": "~$version" + } +} \ No newline at end of file diff --git a/kotlinx-coroutines-test/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/test/TestDispatchersTest.kt deleted file mode 100644 index 98d9705311..0000000000 --- a/kotlinx-coroutines-test/test/TestDispatchersTest.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016-2018 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 org.junit.* -import org.junit.Test -import kotlin.coroutines.* -import kotlin.test.* - -class TestDispatchersTest : TestBase() { - - @Before - fun setUp() { - Dispatchers.resetMain() - } - - @Test(expected = IllegalArgumentException::class) - fun testSelfSet() = runTest { - Dispatchers.setMain(Dispatchers.Main) - } - - @Test - fun testSingleThreadExecutor() = runTest { - val mainThread = Thread.currentThread() - Dispatchers.setMain(Dispatchers.Unconfined) - newSingleThreadContext("testSingleThread").use { threadPool -> - withContext(Dispatchers.Main) { - assertSame(mainThread, Thread.currentThread()) - } - - Dispatchers.setMain(threadPool) - withContext(Dispatchers.Main) { - assertNotSame(mainThread, Thread.currentThread()) - } - assertSame(mainThread, Thread.currentThread()) - - withContext(Dispatchers.Main.immediate) { - assertNotSame(mainThread, Thread.currentThread()) - } - assertSame(mainThread, Thread.currentThread()) - - Dispatchers.setMain(Dispatchers.Unconfined) - withContext(Dispatchers.Main.immediate) { - assertSame(mainThread, Thread.currentThread()) - } - assertSame(mainThread, Thread.currentThread()) - } - } - - @Test - fun testImmediateDispatcher() = runTest { - Dispatchers.setMain(ImmediateDispatcher()) - expect(1) - withContext(Dispatchers.Main) { - expect(3) - } - - Dispatchers.setMain(RegularDispatcher()) - withContext(Dispatchers.Main) { - expect(6) - } - - finish(7) - } - - private inner class ImmediateDispatcher : CoroutineDispatcher() { - override fun isDispatchNeeded(context: CoroutineContext): Boolean { - expect(2) - return false - } - - override fun dispatch(context: CoroutineContext, block: Runnable) = expectUnreached() - } - - private inner class RegularDispatcher : CoroutineDispatcher() { - override fun isDispatchNeeded(context: CoroutineContext): Boolean { - expect(4) - return true - } - - override fun dispatch(context: CoroutineContext, block: Runnable) { - expect(5) - block.run() - } - } -} From 5520b65d266ba2b0245ddbe07d545156f98fb325 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Oct 2021 16:33:39 +0300 Subject: [PATCH 2/7] Remove unused experimental annotations from the test module --- kotlinx-coroutines-test/build.gradle.kts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index 577d3e5261..d14bdccb4c 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -1,16 +1,10 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -import java.net.URL val experimentalAnnotations = listOf( - "kotlin.Experimental", - "kotlin.experimental.ExperimentalTypeInference", - "kotlin.ExperimentalMultiplatform", "kotlinx.coroutines.ExperimentalCoroutinesApi", - "kotlinx.coroutines.ObsoleteCoroutinesApi", - "kotlinx.coroutines.InternalCoroutinesApi", - "kotlinx.coroutines.FlowPreview" + "kotlinx.coroutines.InternalCoroutinesApi" ) kotlin { @@ -24,7 +18,6 @@ kotlin { resources.srcDir("$platform/test-resources") } languageSettings { - progressiveMode = true experimentalAnnotations.forEach { useExperimentalAnnotation(it) } } } From d78e8ad48d5a00a151e56cb9fc01746c0a556577 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Oct 2021 16:46:53 +0300 Subject: [PATCH 3/7] Fix some warnings --- kotlinx-coroutines-test/build.gradle.kts | 1 + .../common/src/TestCoroutineDispatcher.kt | 6 +----- kotlinx-coroutines-test/common/src/TestCoroutineScope.kt | 2 +- .../common/src/internal/MainTestDispatcher.kt | 2 +- kotlinx-coroutines-test/common/test/TestBuildersTest.kt | 2 +- .../common/test/TestCoroutineExceptionHandlerTest.kt | 2 +- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index d14bdccb4c..89c54f9973 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -3,6 +3,7 @@ */ val experimentalAnnotations = listOf( + "kotlin.Experimental", "kotlinx.coroutines.ExperimentalCoroutinesApi", "kotlinx.coroutines.InternalCoroutinesApi" ) diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt index 5bd6783d40..55b92cd6b7 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt @@ -68,11 +68,7 @@ public class TestCoroutineDispatcher: CoroutineDispatcher(), Delay, DelayControl /** @suppress */ override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { val node = postDelayed(block, timeMillis) - return object : DisposableHandle { - override fun dispose() { - queue.remove(node) - } - } + return DisposableHandle { queue.remove(node) } } /** @suppress */ diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt index fc467e2444..da29cd22b4 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt @@ -14,7 +14,7 @@ import kotlin.coroutines.* public interface TestCoroutineScope: CoroutineScope, UncaughtExceptionCaptor, DelayController { /** * Call after the test completes. - * Calls [UncaughtExceptionCaptor.cleanupTestCoroutines] and [DelayController.cleanupTestCoroutines]. + * Calls [UncaughtExceptionCaptor.cleanupTestCoroutinesCaptor] and [DelayController.cleanupTestCoroutines]. * * @throws Throwable the first uncaught exception, if there are any uncaught exceptions. * @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended diff --git a/kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt b/kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt index c85d27ea87..4c70ca3fcf 100644 --- a/kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt @@ -69,7 +69,7 @@ internal class TestMainDispatcherFactory : MainDispatcherFactory { /** * [Int.MAX_VALUE] -- test dispatcher always wins no matter what factories are present in the classpath. - * By default all actions are delegated to the second-priority dispatcher, so that it won't be the issue. + * By default, all actions are delegated to the second-priority dispatcher, so that it won't be the issue. */ override val loadPriority: Int get() = Int.MAX_VALUE diff --git a/kotlinx-coroutines-test/common/test/TestBuildersTest.kt b/kotlinx-coroutines-test/common/test/TestBuildersTest.kt index 8f85a64c7d..a3167e5876 100644 --- a/kotlinx-coroutines-test/common/test/TestBuildersTest.kt +++ b/kotlinx-coroutines-test/common/test/TestBuildersTest.kt @@ -58,7 +58,7 @@ class TestBuildersTest { } @Test - fun scopeRunBlocking_disablesImmedateOnExit() { + fun scopeRunBlocking_disablesImmediatelyOnExit() { val scope = TestCoroutineScope() scope.runBlockingTest { assertRunsFast { diff --git a/kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt index 7dbea56f5b..674fd288dd 100644 --- a/kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineExceptionHandlerTest.kt @@ -8,7 +8,7 @@ import kotlin.test.* class TestCoroutineExceptionHandlerTest { @Test - fun whenExceptionsCaught_avaliableViaProperty() { + fun whenExceptionsCaught_availableViaProperty() { val subject = TestCoroutineExceptionHandler() val expected = IllegalArgumentException() subject.handleException(subject, expected) From 0e7efab3921e38bf7670b8a8959568dba39bafde Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Oct 2021 18:06:38 +0300 Subject: [PATCH 4/7] Move most tests in the test module to common code --- .../test/TestCoroutineDispatcherOrderTest.kt | 17 +++++++++++++- .../test/TestRunBlockingOrderTest.kt | 18 ++++++++++++++- .../common/test/TestRunBlockingTest.kt | 22 ------------------- .../jvm/test/TestDispatchersTest.kt | 22 +++++++++++++++---- kotlinx-coroutines-test/npm/package.json | 7 ++---- 5 files changed, 53 insertions(+), 33 deletions(-) rename kotlinx-coroutines-test/{jvm => common}/test/TestCoroutineDispatcherOrderTest.kt (66%) rename kotlinx-coroutines-test/{jvm => common}/test/TestRunBlockingOrderTest.kt (74%) diff --git a/kotlinx-coroutines-test/jvm/test/TestCoroutineDispatcherOrderTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt similarity index 66% rename from kotlinx-coroutines-test/jvm/test/TestCoroutineDispatcherOrderTest.kt rename to kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt index 39bb1f78f7..53c7c42b79 100644 --- a/kotlinx-coroutines-test/jvm/test/TestCoroutineDispatcherOrderTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt @@ -2,11 +2,26 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.test.* import kotlin.test.* -class TestCoroutineDispatcherOrderTest : TestBase() { +class TestCoroutineDispatcherOrderTest { + + private val actionIndex = atomic(0) + private val finished = atomic(false) + + private fun expect(index: Int) { + val wasIndex = actionIndex.incrementAndGet() + // println("expect($index), wasIndex=$wasIndex") + check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" } + } + + private fun finish(index: Int) { + expect(index) + check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" } + } @Test fun testAdvanceTimeBy_progressesOnEachDelay() { diff --git a/kotlinx-coroutines-test/jvm/test/TestRunBlockingOrderTest.kt b/kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt similarity index 74% rename from kotlinx-coroutines-test/jvm/test/TestRunBlockingOrderTest.kt rename to kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt index 2b18ca63bd..d56753db8e 100644 --- a/kotlinx-coroutines-test/jvm/test/TestRunBlockingOrderTest.kt +++ b/kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt @@ -2,11 +2,27 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.test.* import kotlin.test.* -class TestRunBlockingOrderTest : TestBase() { +class TestRunBlockingOrderTest { + + private val actionIndex = atomic(0) + private val finished = atomic(false) + + private fun expect(index: Int) { + val wasIndex = actionIndex.incrementAndGet() + // println("expect($index), wasIndex=$wasIndex") + check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" } + } + + private fun finish(index: Int) { + expect(index) + check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" } + } + @Test fun testLaunchImmediate() = runBlockingTest { expect(1) diff --git a/kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt b/kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt index 7af3a4f4fc..c93b50811f 100644 --- a/kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt +++ b/kotlinx-coroutines-test/common/test/TestRunBlockingTest.kt @@ -73,28 +73,6 @@ class TestRunBlockingTest { } } - @Test - fun kryak() { - class ClassUnderTest(private val mainScope: CoroutineScope, private val bgDispatcher: CoroutineDispatcher) { - fun doSomething() { - mainScope.launch { - val data = withContext(bgDispatcher) { - delay(1000) - 42 - } - println(data) - } - } - } - - val testBgDispatcher = TestCoroutineDispatcher() - val testScope = TestCoroutineScope() - val subject = ClassUnderTest(testScope, testBgDispatcher) - - subject.doSomething() - testScope.advanceUntilIdle() - } - @Test fun whenUsingTimeout_triggersWhenWaiting() { assertFailsWith { diff --git a/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt index ce02a31594..3ab78987f0 100644 --- a/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt +++ b/kotlinx-coroutines-test/jvm/test/TestDispatchersTest.kt @@ -2,12 +2,26 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.test.* import kotlin.coroutines.* import kotlin.test.* -class TestDispatchersTest : TestBase() { +class TestDispatchersTest { + private val actionIndex = atomic(0) + private val finished = atomic(false) + + private fun expect(index: Int) { + val wasIndex = actionIndex.incrementAndGet() + println("expect($index), wasIndex=$wasIndex") + check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" } + } + + private fun finish(index: Int) { + expect(index) + check(!finished.getAndSet(true)) { "Should call 'finish(...)' at most once" } + } @BeforeTest fun setUp() { @@ -15,12 +29,12 @@ class TestDispatchersTest : TestBase() { } @Test - fun testSelfSet() = runTest { + fun testSelfSet() { assertFailsWith { Dispatchers.setMain(Dispatchers.Main) } } @Test - fun testImmediateDispatcher() = runTest { + fun testImmediateDispatcher() = runBlockingTest { Dispatchers.setMain(ImmediateDispatcher()) expect(1) withContext(Dispatchers.Main) { @@ -41,7 +55,7 @@ class TestDispatchersTest : TestBase() { return false } - override fun dispatch(context: CoroutineContext, block: Runnable) = expectUnreached() + override fun dispatch(context: CoroutineContext, block: Runnable) = throw RuntimeException("Shouldn't be reached") } private inner class RegularDispatcher : CoroutineDispatcher() { diff --git a/kotlinx-coroutines-test/npm/package.json b/kotlinx-coroutines-test/npm/package.json index adb7521c6f..b59d92fe03 100644 --- a/kotlinx-coroutines-test/npm/package.json +++ b/kotlinx-coroutines-test/npm/package.json @@ -19,8 +19,5 @@ "coroutines", "JetBrains", "test" - ], - "dependencies": { - "kotlinx-coroutines-core": "~$version" - } -} \ No newline at end of file + ] +} From 0ab0d458f5dcb24302b9a0827e90725a19a239d8 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Oct 2021 18:17:58 +0300 Subject: [PATCH 5/7] Fix package names for tests --- .../common/test/TestCoroutineDispatcherOrderTest.kt | 3 ++- .../common/test/TestRunBlockingOrderTest.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt index 53c7c42b79..c3686845de 100644 --- a/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineDispatcherOrderTest.kt @@ -2,9 +2,10 @@ * 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.atomicfu.* import kotlinx.coroutines.* -import kotlinx.coroutines.test.* import kotlin.test.* class TestCoroutineDispatcherOrderTest { diff --git a/kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt b/kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt index d56753db8e..064cb7cb71 100644 --- a/kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt +++ b/kotlinx-coroutines-test/common/test/TestRunBlockingOrderTest.kt @@ -2,9 +2,10 @@ * 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.atomicfu.* import kotlinx.coroutines.* -import kotlinx.coroutines.test.* import kotlin.test.* class TestRunBlockingOrderTest { From a613bb765d3fd945e55695dab5447d61636db7a1 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Sun, 3 Oct 2021 15:27:50 +0300 Subject: [PATCH 6/7] Make the code build without changes to the core module --- kotlinx-coroutines-core/build.gradle | 2 +- .../src/internal/MainDispatchers.kt | 39 +++++++++++++++- .../jvm/src/internal/MainDispatchersJvm.kt | 46 ------------------- kotlinx-coroutines-test/build.gradle.kts | 2 +- .../common/src/TestDispatchers.kt | 17 +------ .../js/src/TestDispatchers.kt | 16 +++++++ .../jvm/src/TestDispatchers.kt | 24 ++++++++++ .../src/internal/TestMainDispatcher.kt} | 0 .../native/src/TestDispatchers.kt | 17 +++++++ 9 files changed, 99 insertions(+), 64 deletions(-) rename kotlinx-coroutines-core/{common => jvm}/src/internal/MainDispatchers.kt (66%) delete mode 100644 kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt create mode 100644 kotlinx-coroutines-test/js/src/TestDispatchers.kt create mode 100644 kotlinx-coroutines-test/jvm/src/TestDispatchers.kt rename kotlinx-coroutines-test/{common/src/internal/MainTestDispatcher.kt => jvm/src/internal/TestMainDispatcher.kt} (100%) create mode 100644 kotlinx-coroutines-test/native/src/TestDispatchers.kt diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index c45ca08cef..ef262ff061 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -81,7 +81,7 @@ kotlin { } languageSettings { progressiveMode = true - optInAnnotations.each { useExperimentalAnnotation(it) } + optInAnnotations.each { optIn(it) } } } diff --git a/kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt similarity index 66% rename from kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt rename to kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt index afd484520a..2d447413b8 100644 --- a/kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt @@ -5,8 +5,45 @@ package kotlinx.coroutines.internal import kotlinx.coroutines.* +import java.util.* import kotlin.coroutines.* +/** + * Name of the boolean property that enables using of [FastServiceLoader]. + */ +private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader" + +// Lazy loader for the main dispatcher +internal object MainDispatcherLoader { + + private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true) + + @JvmField + val dispatcher: MainCoroutineDispatcher = loadMainDispatcher() + + private fun loadMainDispatcher(): MainCoroutineDispatcher { + return try { + val factories = if (FAST_SERVICE_LOADER_ENABLED) { + FastServiceLoader.loadMainDispatcherFactory() + } else { + // We are explicitly using the + // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()` + // form of the ServiceLoader call to enable R8 optimization when compiled on Android. + ServiceLoader.load( + MainDispatcherFactory::class.java, + MainDispatcherFactory::class.java.classLoader + ).iterator().asSequence().toList() + } + @Suppress("ConstantConditionIf") + factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories) + ?: createMissingDispatcher() + } catch (e: Throwable) { + // Service loader can throw an exception as well + createMissingDispatcher(e) + } + } +} + /** * If anything goes wrong while trying to create main dispatcher (class not found, * initialization failed, etc), then replace the main dispatcher with a special @@ -34,7 +71,7 @@ private val SUPPORT_MISSING = true "ConstantConditionIf", "IMPLICIT_NOTHING_TYPE_ARGUMENT_AGAINST_NOT_NOTHING_EXPECTED_TYPE" // KT-47626 ) -internal fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null): MainCoroutineDispatcher = +private fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null) = if (SUPPORT_MISSING) MissingMainCoroutineDispatcher(cause, errorHint) else cause?.let { throw it } ?: throwMissingMainDispatcherException() diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt deleted file mode 100644 index 0bac97ee47..0000000000 --- a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchersJvm.kt +++ /dev/null @@ -1,46 +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.internal - -import kotlinx.coroutines.* -import java.util.* -import kotlin.coroutines.* - -/** - * Name of the boolean property that enables using of [FastServiceLoader]. - */ -private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader" - -// Lazy loader for the main dispatcher -internal object MainDispatcherLoader { - - private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true) - - @JvmField - val dispatcher: MainCoroutineDispatcher = loadMainDispatcher() - - private fun loadMainDispatcher(): MainCoroutineDispatcher { - return try { - val factories = if (FAST_SERVICE_LOADER_ENABLED) { - FastServiceLoader.loadMainDispatcherFactory() - } else { - // We are explicitly using the - // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()` - // form of the ServiceLoader call to enable R8 optimization when compiled on Android. - ServiceLoader.load( - MainDispatcherFactory::class.java, - MainDispatcherFactory::class.java.classLoader - ).iterator().asSequence().toList() - } - @Suppress("ConstantConditionIf") - factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories) - ?: createMissingDispatcher() - } catch (e: Throwable) { - // Service loader can throw an exception as well - createMissingDispatcher(e) - } - } -} - diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index 89c54f9973..77341f9751 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -19,7 +19,7 @@ kotlin { resources.srcDir("$platform/test-resources") } languageSettings { - experimentalAnnotations.forEach { useExperimentalAnnotation(it) } + experimentalAnnotations.forEach { optIn(it) } } } } diff --git a/kotlinx-coroutines-test/common/src/TestDispatchers.kt b/kotlinx-coroutines-test/common/src/TestDispatchers.kt index 6400fddc5b..2f00331506 100644 --- a/kotlinx-coroutines-test/common/src/TestDispatchers.kt +++ b/kotlinx-coroutines-test/common/src/TestDispatchers.kt @@ -1,14 +1,10 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("unused") -@file:JvmName("TestDispatchers") package kotlinx.coroutines.test import kotlinx.coroutines.* -import kotlinx.coroutines.test.internal.* -import kotlin.jvm.* /** * Sets the given [dispatcher] as an underlying dispatcher of [Dispatchers.Main]. @@ -17,12 +13,7 @@ import kotlin.jvm.* * It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist. */ @ExperimentalCoroutinesApi -public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { - require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" } - val mainDispatcher = Main - require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." } - mainDispatcher.setDispatcher(dispatcher) -} +public expect fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) /** * Resets state of the [Dispatchers.Main] to the original main dispatcher. @@ -32,8 +23,4 @@ public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { * It is unsafe to call this method if alive coroutines launched in [Dispatchers.Main] exist. */ @ExperimentalCoroutinesApi -public fun Dispatchers.resetMain() { - val mainDispatcher = Main - require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." } - mainDispatcher.resetDispatcher() -} +public expect fun Dispatchers.resetMain() diff --git a/kotlinx-coroutines-test/js/src/TestDispatchers.kt b/kotlinx-coroutines-test/js/src/TestDispatchers.kt new file mode 100644 index 0000000000..10322079d3 --- /dev/null +++ b/kotlinx-coroutines-test/js/src/TestDispatchers.kt @@ -0,0 +1,16 @@ +/* + * 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.* + +@ExperimentalCoroutinesApi +public actual fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { + throw UnsupportedOperationException("`setMain` is not supported on JS") +} + +@ExperimentalCoroutinesApi +public actual fun Dispatchers.resetMain() { + throw UnsupportedOperationException("`resetMain` is not supported on JS") +} diff --git a/kotlinx-coroutines-test/jvm/src/TestDispatchers.kt b/kotlinx-coroutines-test/jvm/src/TestDispatchers.kt new file mode 100644 index 0000000000..800eca5d1d --- /dev/null +++ b/kotlinx-coroutines-test/jvm/src/TestDispatchers.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:JvmName("TestDispatchers") + +package kotlinx.coroutines.test + +import kotlinx.coroutines.* +import kotlinx.coroutines.test.internal.* + +@ExperimentalCoroutinesApi +public actual fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { + require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" } + val mainDispatcher = Main + require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." } + mainDispatcher.setDispatcher(dispatcher) +} + +@ExperimentalCoroutinesApi +public actual fun Dispatchers.resetMain() { + val mainDispatcher = Main + require(mainDispatcher is TestMainDispatcher) { "TestMainDispatcher is not set as main dispatcher, have $mainDispatcher instead." } + mainDispatcher.resetDispatcher() +} diff --git a/kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt b/kotlinx-coroutines-test/jvm/src/internal/TestMainDispatcher.kt similarity index 100% rename from kotlinx-coroutines-test/common/src/internal/MainTestDispatcher.kt rename to kotlinx-coroutines-test/jvm/src/internal/TestMainDispatcher.kt diff --git a/kotlinx-coroutines-test/native/src/TestDispatchers.kt b/kotlinx-coroutines-test/native/src/TestDispatchers.kt new file mode 100644 index 0000000000..44c7a72752 --- /dev/null +++ b/kotlinx-coroutines-test/native/src/TestDispatchers.kt @@ -0,0 +1,17 @@ +/* + * 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.* + +@ExperimentalCoroutinesApi +public actual fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) { + throw UnsupportedOperationException("`setMain` is not supported on Native") +} + +@ExperimentalCoroutinesApi +public actual fun Dispatchers.resetMain() { + throw UnsupportedOperationException("`resetMain` is not supported on Native") +} \ No newline at end of file From 78e76b3990f8f5b68dcc65210b19651f6bf897c0 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 7 Oct 2021 11:39:42 +0300 Subject: [PATCH 7/7] DRY in the build scripts --- build.gradle | 3 +-- buildSrc/src/main/kotlin/OptInPreset.kt | 13 +++++++++++++ buildSrc/src/main/kotlin/SourceSets.kt | 19 +++++++++++++++++++ gradle/opt-in.gradle | 13 ------------- kotlinx-coroutines-core/build.gradle | 15 ++------------- kotlinx-coroutines-test/build.gradle.kts | 14 +------------- 6 files changed, 36 insertions(+), 41 deletions(-) create mode 100644 buildSrc/src/main/kotlin/OptInPreset.kt create mode 100644 buildSrc/src/main/kotlin/SourceSets.kt delete mode 100644 gradle/opt-in.gradle diff --git a/build.gradle b/build.gradle index 5fb1da782a..26b598ff5d 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,6 @@ import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.dokka.gradle.DokkaTaskPartial apply plugin: 'jdk-convention' -apply from: rootProject.file("gradle/opt-in.gradle") def coreModule = "kotlinx-coroutines-core" def testModule = "kotlinx-coroutines-test" @@ -168,7 +167,7 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) { // Configure options for all Kotlin compilation tasks tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all { - kotlinOptions.freeCompilerArgs += optInAnnotations.collect { "-Xopt-in=" + it } + kotlinOptions.freeCompilerArgs += OptInPresetKt.optInAnnotations.collect { "-Xopt-in=" + it } kotlinOptions.freeCompilerArgs += "-progressive" // Disable KT-36770 for RxJava2 integration kotlinOptions.freeCompilerArgs += "-XXLanguage:-ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated" diff --git a/buildSrc/src/main/kotlin/OptInPreset.kt b/buildSrc/src/main/kotlin/OptInPreset.kt new file mode 100644 index 0000000000..ee2aab11cf --- /dev/null +++ b/buildSrc/src/main/kotlin/OptInPreset.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +val optInAnnotations = listOf( + "kotlin.RequiresOptIn", + "kotlin.experimental.ExperimentalTypeInference", + "kotlin.ExperimentalMultiplatform", + "kotlinx.coroutines.DelicateCoroutinesApi", + "kotlinx.coroutines.ExperimentalCoroutinesApi", + "kotlinx.coroutines.ObsoleteCoroutinesApi", + "kotlinx.coroutines.InternalCoroutinesApi", + "kotlinx.coroutines.FlowPreview") \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/SourceSets.kt b/buildSrc/src/main/kotlin/SourceSets.kt new file mode 100644 index 0000000000..533ac70ac6 --- /dev/null +++ b/buildSrc/src/main/kotlin/SourceSets.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +import org.jetbrains.kotlin.gradle.plugin.* + +fun KotlinSourceSet.configureMultiplatform() { + val srcDir = if (name.endsWith("Main")) "src" else "test" + val platform = name.dropLast(4) + kotlin.srcDir("$platform/$srcDir") + if (name == "jvmMain") { + resources.srcDir("$platform/resources") + } else if (name == "jvmTest") { + resources.srcDir("$platform/test-resources") + } + languageSettings { + optInAnnotations.forEach { optIn(it) } + progressiveMode = true + } +} \ No newline at end of file diff --git a/gradle/opt-in.gradle b/gradle/opt-in.gradle deleted file mode 100644 index 22f022dbb5..0000000000 --- a/gradle/opt-in.gradle +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -ext.optInAnnotations = [ - "kotlin.RequiresOptIn", - "kotlin.experimental.ExperimentalTypeInference", - "kotlin.ExperimentalMultiplatform", - "kotlinx.coroutines.DelicateCoroutinesApi", - "kotlinx.coroutines.ExperimentalCoroutinesApi", - "kotlinx.coroutines.ObsoleteCoroutinesApi", - "kotlinx.coroutines.InternalCoroutinesApi", - "kotlinx.coroutines.FlowPreview"] diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index ef262ff061..4435ad7fa1 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -70,19 +70,8 @@ if (rootProject.ext.native_targets_enabled) { * because JMV-only projects depend on core, thus core should always be initialized before configuration. */ kotlin { - configure(sourceSets) { - def srcDir = name.endsWith('Main') ? 'src' : 'test' - def platform = name[0..-5] - kotlin.srcDirs = ["$platform/$srcDir"] - if (name == "jvmMain") { - resources.srcDirs = ["$platform/resources"] - } else if (name == "jvmTest") { - resources.srcDirs = ["$platform/test-resources"] - } - languageSettings { - progressiveMode = true - optInAnnotations.each { optIn(it) } - } + sourceSets.forEach { + SourceSetsKt.configureMultiplatform(it) } configure(targets) { diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index 77341f9751..7b244bb091 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -9,17 +9,5 @@ val experimentalAnnotations = listOf( ) kotlin { - sourceSets.all { - val srcDir = if (name.endsWith("Main")) "src" else "test" - val platform = name.dropLast(4) - kotlin.srcDir("$platform/$srcDir") - if (name == "jvmMain") { - resources.srcDir("$platform/resources") - } else if (name == "jvmTest") { - resources.srcDir("$platform/test-resources") - } - languageSettings { - experimentalAnnotations.forEach { optIn(it) } - } - } + sourceSets.all { configureMultiplatform() } }