Skip to content

Commit be4416c

Browse files
committed
Configure building the test module as an MPP
1 parent 1ba202d commit be4416c

31 files changed

+501
-336
lines changed

build.gradle

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
66
import org.jetbrains.kotlin.konan.target.HostManager
7-
import org.gradle.util.VersionNumber
87
import org.jetbrains.dokka.gradle.DokkaTaskPartial
9-
import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
108

119
apply plugin: 'jdk-convention'
1210
apply from: rootProject.file("gradle/opt-in.gradle")
1311

1412
def coreModule = "kotlinx-coroutines-core"
13+
def testModule = "kotlinx-coroutines-test"
14+
def multiplatform = [coreModule, testModule]
1515
// Not applicable for Kotlin plugin
1616
def sourceless = ['kotlinx.coroutines', 'kotlinx-coroutines-bom', 'integration-testing']
1717
def internal = ['kotlinx.coroutines', 'benchmarks', 'integration-testing']
@@ -112,7 +112,7 @@ apiValidation {
112112
ignoredProjects += unpublished + ["kotlinx-coroutines-bom"]
113113
if (build_snapshot_train) {
114114
ignoredProjects.remove("example-frontend-js")
115-
ignoredProjects.add("kotlinx-coroutines-core")
115+
ignoredProjects.add(coreModule)
116116
}
117117
ignoredPackages += "kotlinx.coroutines.internal"
118118
}
@@ -133,13 +133,31 @@ allprojects {
133133
// Add dependency to core source sets. Core is configured in kx-core/build.gradle
134134
configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != coreModule }) {
135135
evaluationDependsOn(":$coreModule")
136-
def platform = PlatformKt.platformOf(it)
137-
apply plugin: "kotlin-${platform}-conventions"
138-
dependencies {
139-
// See comment below for rationale, it will be replaced with "project" dependency
140-
api project(":$coreModule")
141-
// the only way IDEA can resolve test classes
142-
testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
136+
if (it.name in multiplatform) {
137+
apply plugin: "kotlin-multiplatform"
138+
apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle")
139+
apply from: rootProject.file("gradle/compile-common.gradle")
140+
141+
if (rootProject.ext["native_targets_enabled"] as Boolean) {
142+
apply from: rootProject.file("gradle/compile-native-multiplatform.gradle")
143+
}
144+
145+
apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
146+
apply from: rootProject.file("gradle/publish-npm-js.gradle")
147+
kotlin.sourceSets.commonMain.dependencies {
148+
api project(":$coreModule")
149+
}
150+
kotlin.sourceSets.jvmTest.dependencies {
151+
implementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
152+
}
153+
} else {
154+
def platform = PlatformKt.platformOf(it)
155+
apply plugin: "kotlin-${platform}-conventions"
156+
dependencies {
157+
api project(":$coreModule")
158+
// the only way IDEA can resolve test classes
159+
testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
160+
}
143161
}
144162
}
145163

@@ -177,7 +195,7 @@ if (build_snapshot_train) {
177195
}
178196

179197
println "Manifest of kotlin-compiler-embeddable.jar for coroutines"
180-
configure(subprojects.findAll { it.name == "kotlinx-coroutines-core" }) {
198+
configure(subprojects.findAll { it.name == coreModule }) {
181199
configurations.matching { it.name == "kotlinCompilerClasspath" }.all {
182200
resolvedConfiguration.getFiles().findAll { it.name.contains("kotlin-compiler-embeddable") }.each {
183201
def manifest = zipTree(it).matching {
@@ -194,9 +212,8 @@ if (build_snapshot_train) {
194212

195213
// Redefine source sets because we are not using 'kotlin/main/fqn' folder convention
196214
configure(subprojects.findAll {
197-
!sourceless.contains(it.name) &&
215+
!sourceless.contains(it.name) && !multiplatform.contains(it.name) &&
198216
it.name != "benchmarks" &&
199-
it.name != coreModule &&
200217
it.name != "example-frontend-js"
201218
}) {
202219
// 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) }) {
250267
}
251268

252269
List<String> jarTasks
253-
if (it.name == "kotlinx-coroutines-core") {
270+
if (it.name in multiplatform) {
254271
jarTasks = ["jvmJar", "metadataJar"]
255272
} else if (it.name == "kotlinx-coroutines-debug") {
256273
// We shadow debug module instead of just packaging it
@@ -324,12 +341,12 @@ allprojects { subProject ->
324341
.matching {
325342
// Excluding substituted project itself because of circular dependencies, but still do it
326343
// for "*Test*" configurations
327-
subProject.name != "kotlinx-coroutines-core" || it.name.contains("Test")
344+
subProject.name != coreModule || it.name.contains("Test")
328345
}
329346
.configureEach { conf ->
330347
conf.resolutionStrategy.dependencySubstitution {
331-
substitute(module("org.jetbrains.kotlinx:kotlinx-coroutines-core"))
332-
.using(project(":kotlinx-coroutines-core"))
348+
substitute(module("org.jetbrains.kotlinx:$coreModule"))
349+
.using(project(":$coreModule"))
333350
.because("Because Kotlin compiler embeddable leaks coroutines into the runtime classpath, " +
334351
"triggering all sort of incompatible class changes errors")
335352
}

gradle/dokka.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ tasks.withType(DokkaTaskPartial::class).configureEach {
3737
packageListUrl.set(rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL())
3838
}
3939

40-
if (project.name != "kotlinx-coroutines-core") {
40+
if (project.name != "kotlinx-coroutines-core" && project.name != "kotlinx-coroutines-test") {
4141
dependsOn(project.configurations["compileClasspath"])
4242
doFirst {
4343
// resolve classpath only during execution

gradle/publish.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ apply plugin: 'signing'
1212

1313
// ------------- tasks
1414

15-
def isMultiplatform = project.name == "kotlinx-coroutines-core"
15+
def isMultiplatform = project.name == "kotlinx-coroutines-core" || project.name == "kotlinx-coroutines-test"
1616
def isBom = project.name == "kotlinx-coroutines-bom"
1717

1818
if (!isBom) {

kotlinx-coroutines-core/common/src/Unconfined.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal object Unconfined : CoroutineDispatcher() {
1414
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
1515

1616
override fun dispatch(context: CoroutineContext, block: Runnable) {
17-
// It can only be called by the "yield" function. See also code of "yield" function.
17+
/** It can only be called by the [yield] function. See also code of [yield] function. */
1818
val yieldContext = context[YieldContext]
1919
if (yieldContext != null) {
2020
// report to "yield" that it is an unconfined dispatcher and don't call "block.run()"

kotlinx-coroutines-core/common/src/flow/Migration.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public fun <T> Flow<T>.skip(count: Int): Flow<T> = noImpl()
260260
@Deprecated(
261261
level = DeprecationLevel.ERROR,
262262
message = "Flow analogue of 'forEach' is 'collect'",
263-
replaceWith = ReplaceWith("collect(block)")
263+
replaceWith = ReplaceWith("collect(action)")
264264
)
265265
public fun <T> Flow<T>.forEach(action: suspend (value: T) -> Unit): Unit = noImpl()
266266

kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt renamed to kotlinx-coroutines-core/common/src/internal/MainDispatchers.kt

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,8 @@
55
package kotlinx.coroutines.internal
66

77
import kotlinx.coroutines.*
8-
import java.util.*
98
import kotlin.coroutines.*
109

11-
/**
12-
* Name of the boolean property that enables using of [FastServiceLoader].
13-
*/
14-
private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader"
15-
16-
// Lazy loader for the main dispatcher
17-
internal object MainDispatcherLoader {
18-
19-
private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)
20-
21-
@JvmField
22-
val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()
23-
24-
private fun loadMainDispatcher(): MainCoroutineDispatcher {
25-
return try {
26-
val factories = if (FAST_SERVICE_LOADER_ENABLED) {
27-
FastServiceLoader.loadMainDispatcherFactory()
28-
} else {
29-
// We are explicitly using the
30-
// `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
31-
// form of the ServiceLoader call to enable R8 optimization when compiled on Android.
32-
ServiceLoader.load(
33-
MainDispatcherFactory::class.java,
34-
MainDispatcherFactory::class.java.classLoader
35-
).iterator().asSequence().toList()
36-
}
37-
@Suppress("ConstantConditionIf")
38-
factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories)
39-
?: createMissingDispatcher()
40-
} catch (e: Throwable) {
41-
// Service loader can throw an exception as well
42-
createMissingDispatcher(e)
43-
}
44-
}
45-
}
46-
4710
/**
4811
* If anything goes wrong while trying to create main dispatcher (class not found,
4912
* initialization failed, etc), then replace the main dispatcher with a special
@@ -71,7 +34,7 @@ private val SUPPORT_MISSING = true
7134
"ConstantConditionIf",
7235
"IMPLICIT_NOTHING_TYPE_ARGUMENT_AGAINST_NOT_NOTHING_EXPECTED_TYPE" // KT-47626
7336
)
74-
private fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null) =
37+
internal fun createMissingDispatcher(cause: Throwable? = null, errorHint: String? = null): MainCoroutineDispatcher =
7538
if (SUPPORT_MISSING) MissingMainCoroutineDispatcher(cause, errorHint) else
7639
cause?.let { throw it } ?: throwMissingMainDispatcherException()
7740

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.internal
6+
7+
import kotlinx.coroutines.*
8+
import java.util.*
9+
import kotlin.coroutines.*
10+
11+
/**
12+
* Name of the boolean property that enables using of [FastServiceLoader].
13+
*/
14+
private const val FAST_SERVICE_LOADER_PROPERTY_NAME = "kotlinx.coroutines.fast.service.loader"
15+
16+
// Lazy loader for the main dispatcher
17+
internal object MainDispatcherLoader {
18+
19+
private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)
20+
21+
@JvmField
22+
val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()
23+
24+
private fun loadMainDispatcher(): MainCoroutineDispatcher {
25+
return try {
26+
val factories = if (FAST_SERVICE_LOADER_ENABLED) {
27+
FastServiceLoader.loadMainDispatcherFactory()
28+
} else {
29+
// We are explicitly using the
30+
// `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
31+
// form of the ServiceLoader call to enable R8 optimization when compiled on Android.
32+
ServiceLoader.load(
33+
MainDispatcherFactory::class.java,
34+
MainDispatcherFactory::class.java.classLoader
35+
).iterator().asSequence().toList()
36+
}
37+
@Suppress("ConstantConditionIf")
38+
factories.maxByOrNull { it.loadPriority }?.tryCreateDispatcher(factories)
39+
?: createMissingDispatcher()
40+
} catch (e: Throwable) {
41+
// Service loader can throw an exception as well
42+
createMissingDispatcher(e)
43+
}
44+
}
45+
}
46+

kotlinx-coroutines-test/api/kotlinx-coroutines-test.api

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public final class kotlinx/coroutines/test/TestCoroutineDispatcher : kotlinx/cor
3636

3737
public final class kotlinx/coroutines/test/TestCoroutineExceptionHandler : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/CoroutineExceptionHandler, kotlinx/coroutines/test/UncaughtExceptionCaptor {
3838
public fun <init> ()V
39-
public fun cleanupTestCoroutines ()V
39+
public fun cleanupTestCoroutinesCaptor ()V
4040
public fun getUncaughtExceptions ()Ljava/util/List;
4141
public fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V
4242
}
@@ -56,12 +56,11 @@ public final class kotlinx/coroutines/test/TestDispatchers {
5656
}
5757

5858
public abstract interface class kotlinx/coroutines/test/UncaughtExceptionCaptor {
59-
public abstract fun cleanupTestCoroutines ()V
59+
public abstract fun cleanupTestCoroutinesCaptor ()V
6060
public abstract fun getUncaughtExceptions ()Ljava/util/List;
6161
}
6262

6363
public final class kotlinx/coroutines/test/UncompletedCoroutinesError : java/lang/AssertionError {
64-
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
65-
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
64+
public fun <init> (Ljava/lang/String;)V
6665
}
6766

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
/*
22
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4+
import java.net.URL
45

5-
dependencies {
6-
implementation(project(":kotlinx-coroutines-debug"))
6+
val experimentalAnnotations = listOf(
7+
"kotlin.Experimental",
8+
"kotlin.experimental.ExperimentalTypeInference",
9+
"kotlin.ExperimentalMultiplatform",
10+
"kotlinx.coroutines.ExperimentalCoroutinesApi",
11+
"kotlinx.coroutines.ObsoleteCoroutinesApi",
12+
"kotlinx.coroutines.InternalCoroutinesApi",
13+
"kotlinx.coroutines.FlowPreview"
14+
)
15+
16+
kotlin {
17+
sourceSets.all {
18+
val srcDir = if (name.endsWith("Main")) "src" else "test"
19+
val platform = name.dropLast(4)
20+
kotlin.srcDir("$platform/$srcDir")
21+
if (name == "jvmMain") {
22+
resources.srcDir("$platform/resources")
23+
} else if (name == "jvmTest") {
24+
resources.srcDir("$platform/test-resources")
25+
}
26+
languageSettings {
27+
progressiveMode = true
28+
experimentalAnnotations.forEach { useExperimentalAnnotation(it) }
29+
}
30+
}
731
}

kotlinx-coroutines-test/src/DelayController.kt renamed to kotlinx-coroutines-test/common/src/DelayController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,4 @@ public interface DelayController {
126126
*/
127127
// todo: maybe convert into non-public class in 1.3.0 (need use-cases for a public exception type)
128128
@ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0
129-
public class UncompletedCoroutinesError(message: String, cause: Throwable? = null): AssertionError(message, cause)
129+
public class UncompletedCoroutinesError(message: String): AssertionError(message)

kotlinx-coroutines-test/src/TestBuilders.kt renamed to kotlinx-coroutines-test/common/src/TestBuilders.kt

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,16 @@ public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineS
8080
runBlockingTest(this, block)
8181

8282
private fun CoroutineContext.checkArguments(): Pair<CoroutineContext, DelayController> {
83-
// TODO optimize it
84-
val dispatcher = get(ContinuationInterceptor).run {
85-
this?.let { require(this is DelayController) { "Dispatcher must implement DelayController: $this" } }
86-
this ?: TestCoroutineDispatcher()
83+
val dispatcher = when (val dispatcher = get(ContinuationInterceptor)) {
84+
is DelayController -> dispatcher
85+
null -> TestCoroutineDispatcher()
86+
else -> throw IllegalArgumentException("Dispatcher must implement DelayController: $dispatcher")
8787
}
88-
89-
val exceptionHandler = get(CoroutineExceptionHandler).run {
90-
this?.let {
91-
require(this is UncaughtExceptionCaptor) { "coroutineExceptionHandler must implement UncaughtExceptionCaptor: $this" }
92-
}
93-
this ?: TestCoroutineExceptionHandler()
88+
val exceptionHandler = when (val handler = get(CoroutineExceptionHandler)) {
89+
is UncaughtExceptionCaptor -> handler
90+
null -> TestCoroutineExceptionHandler()
91+
else -> throw IllegalArgumentException("coroutineExceptionHandler must implement UncaughtExceptionCaptor: $handler")
9492
}
95-
9693
val job = get(Job) ?: SupervisorJob()
97-
return Pair(this + dispatcher + exceptionHandler + job, dispatcher as DelayController)
94+
return Pair(this + dispatcher + exceptionHandler + job, dispatcher)
9895
}

kotlinx-coroutines-test/src/TestCoroutineDispatcher.kt renamed to kotlinx-coroutines-test/common/src/TestCoroutineDispatcher.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import kotlinx.atomicfu.*
88
import kotlinx.coroutines.*
99
import kotlinx.coroutines.internal.*
1010
import kotlin.coroutines.*
11+
import kotlin.jvm.*
1112
import kotlin.math.*
1213

1314
/**

0 commit comments

Comments
 (0)