1 import org.gradle.api.tasks.testing.*
2 import org.gradle.kotlin.dsl.*
3 import org.jetbrains.kotlin.gradle.plugin.mpp.*
4 import org.jetbrains.kotlin.gradle.targets.native.tasks.*
5 import org.jetbrains.kotlin.gradle.tasks.*
6 import org.jetbrains.kotlin.gradle.testing.*
7
<lambda>null8 plugins {
9 kotlin("multiplatform")
10 id("org.jetbrains.kotlinx.benchmark")
11 id("org.jetbrains.dokka")
12 id("org.jetbrains.kotlinx.kover")
13 }
14
15 apply(plugin = "pub-conventions")
16
17 /* ==========================================================================
18 Configure source sets structure for kotlinx-coroutines-core:
19
20 TARGETS SOURCE SETS
21 ------- ----------------------------------------------
22 wasmJs \----------> jsAndWasmShared --------------------+
23 js / |
24 V
25 jvmCore\ --------> jvm ---------> concurrent -------> common
26 jdk8 / ^
27 |
28 ios \ |
29 macos | ---> nativeDarwin ---> native ---+
30 tvos | ^
31 watchos / |
32 |
33 linux \ ---> nativeOther -------+
34 mingw /
35
36
37 Explanation of JVM source sets structure:
38
39 The overall structure is just a hack to support the scenario we are interested in:
40
41 * We would like to have two source-sets "core" and "jdk8"
42 * "jdk8" is allowed to use API from Java 8 and from "core"
43 * "core" is prohibited to use any API from "jdk8"
44 * It is okay to have tests in a single test source-set
45 * And we want to publish a **single** artifact kotlinx-coroutines-core.jar that contains classes from both source-sets
46 * Current limitation: only classes from "core" are checked with animal-sniffer
47
48 For that, we have following compilations:
49 * jvmMain compilation: [jvmCoreMain, jdk8Main]
50 * jvmCore compilation: [commonMain]
51 * jdk8 compilation: [commonMain, jvmCoreMain]
52
53 Theoretically, "jvmCore" could've been "jvmMain", it is not for technical reasons,
54 here is the explanation from Seb:
55
56 """
57 The jvmCore is theoretically not necessary. All code for jdk6 compatibility can be in jvmMain and jdk8 dependent code can be in jdk8Main.
58 Effectively there is no reason for ever putting code into jvmCoreMain.
59 However, when creating a new compilation, we have to take care of creating a defaultSourceSet. Without creating the jvmCoreMain source set,
60 the creation of the compilation fails. That is the only reason for this source set.
61 """
62 ========================================================================== */
63
<lambda>null64 kotlin {
65 sourceSets {
66 // using the source set names from <https://kotlinlang.org/docs/multiplatform-hierarchy.html#see-the-full-hierarchy-template>
67 groupSourceSets("concurrent", listOf("jvm", "native"), listOf("common"))
68 if (project.nativeTargetsAreEnabled) {
69 // TODO: 'nativeDarwin' behaves exactly like 'apple', we can remove it
70 groupSourceSets("nativeDarwin", listOf("apple"), listOf("native"))
71 groupSourceSets("nativeOther", listOf("linux", "mingw", "androidNative"), listOf("native"))
72 }
73 jvmMain {
74 dependencies {
75 compileOnly("com.google.android:annotations:4.1.1.4")
76 }
77 }
78 jvmTest {
79 dependencies {
80 api("org.jetbrains.kotlinx:lincheck:${version("lincheck")}")
81 api("org.jetbrains.kotlinx:kotlinx-knit-test:${version("knit")}")
82 implementation(project(":android-unit-tests"))
83 implementation("org.openjdk.jol:jol-core:0.16")
84 }
85 }
86 }
87 /*
88 * Configure two test runs for Native:
89 * 1) Main thread
90 * 2) BG thread (required for Dispatchers.Main tests on Darwin)
91 *
92 * All new MM targets are build with optimize = true to have stress tests properly run.
93 */
94 targets.withType(KotlinNativeTargetWithTests::class).configureEach {
95 binaries.getTest(DEBUG).apply {
96 optimized = true
97 }
98
99 binaries.test("workerTest", listOf(DEBUG)) {
100 val thisTest = this
101 optimized = true
102 freeCompilerArgs = freeCompilerArgs + listOf("-e", "kotlinx.coroutines.mainBackground")
103 testRuns.create("workerTest") {
104 this as KotlinTaskTestRun<*, *>
105 setExecutionSourceFrom(thisTest)
106 executionTask.configure {
107 this as KotlinNativeTest
108 targetName = "$targetName worker with new MM"
109 }
110 }
111 }
112 }
113
114 /**
115 * See: https://youtrack.jetbrains.com/issue/KTIJ-25959
116 * The introduction of jvmCore is only for CLI builds and not intended for the IDE.
117 * In the current setup there are two tooling unfriendly configurations used:
118 * 1: - jvmMain, despite being a platform source set, is not a leaf (jvmCoreMain and jdk8Main dependOn it)
119 * 2: - jvmMain effectively becomes a 'shared jvm' source set
120 *
121 * Using this kludge here, will prevent issue 2 from being visible to the IDE.
122 * Therefore jvmMain will resolve using the 'single' compilation it participates in (from the perspective of the IDE)
123 */
124 val jvmCoreMain = if (Idea.active) null else sourceSets.create("jvmCoreMain") {
125 dependsOn(sourceSets.jvmMain.get())
126 }
127 val jdk8Main = sourceSets.create("jdk8Main") {
128 dependsOn(sourceSets.jvmMain.get())
129 }
130
131 jvm {
132 compilations.named("main") {
133 jvmCoreMain?.let { source(it) }
134 source(jdk8Main)
135 }
136
137 /* Create compilation for jvmCore to prove that jvmMain does not rely on jdk8 */
138 compilations.create("CoreMain") {
139 /* jvmCore is automatically matched as 'defaultSourceSet' for the compilation, due to its name */
140 tasks.getByName("check").dependsOn(compileTaskProvider)
141 }
142
143 // For animal sniffer
144 withJava()
145 compilations.create("benchmark") { associateWith(this@jvm.compilations.getByName("main")) }
146 }
147 }
148
<lambda>null149 benchmark {
150 targets {
151 register("jvmBenchmark")
152 }
153 }
154
155 // Update module name for metadata artifact to avoid conflicts
156 // see https://github.com/Kotlin/kotlinx.coroutines/issues/1797
<lambda>null157 val compileKotlinMetadata by tasks.getting(KotlinCompilationTask::class) {
158 compilerOptions {
159 freeCompilerArgs.addAll("-module-name", "kotlinx-coroutines-core-common")
160 }
161 }
162
<lambda>null163 val jvmTest by tasks.getting(Test::class) {
164 minHeapSize = "1g"
165 maxHeapSize = "1g"
166 enableAssertions = true
167 if (!Idea.active) {
168 // We should not set this security manager when `jvmTest`
169 // is invoked by IntelliJ IDEA since we need to pass
170 // system properties for Lincheck and stress tests.
171 // TODO Remove once IDEA is smart enough to select between `jvmTest`/`jvmStressTest`/`jvmLincheckTest` #KTIJ-599
172 systemProperty("java.security.manager", "kotlinx.coroutines.TestSecurityManager")
173 }
174 // 'stress' is required to be able to run all subpackage tests like ":jvmTests --tests "*channels*" -Pstress=true"
175 if (!Idea.active && rootProject.properties["stress"] == null) {
176 exclude("**/*LincheckTest*")
177 exclude("**/*StressTest.*")
178 }
179 if (Idea.active) {
180 // Configure the IDEA runner for Lincheck
181 configureJvmForLincheck()
182 }
183 }
184
185 // Setup manifest for kotlinx-coroutines-core-jvm.jar
<lambda>null186 val jvmJar by tasks.getting(Jar::class) { setupManifest(this) }
187
188 /*
189 * Setup manifest for kotlinx-coroutines-core.jar
190 * This is convenient for users that pass -javaagent arg manually and also is a workaround #2619 and KTIJ-5659.
191 * This manifest contains reference to AgentPremain that belongs to
192 * kotlinx-coroutines-core-jvm, but our resolving machinery guarantees that
193 * any JVM project that depends on -core artifact also depends on -core-jvm one.
194 */
<lambda>null195 val allMetadataJar by tasks.getting(Jar::class) { setupManifest(this) }
196
setupManifestnull197 fun setupManifest(jar: Jar) {
198 jar.manifest {
199 attributes(mapOf(
200 "Premain-Class" to "kotlinx.coroutines.debug.AgentPremain",
201 "Can-Retransform-Classes" to "true",
202 ))
203 }
204 }
205
206 val compileTestKotlinJvm by tasks.getting(KotlinJvmCompile::class)
207 val jvmTestClasses by tasks.getting
208
<lambda>null209 val jvmStressTest by tasks.registering(Test::class) {
210 dependsOn(compileTestKotlinJvm)
211 classpath = jvmTest.classpath
212 testClassesDirs = jvmTest.testClassesDirs
213 minHeapSize = "1g"
214 maxHeapSize = "1g"
215 include("**/*StressTest.*")
216 enableAssertions = true
217 testLogging.showStandardStreams = true
218 systemProperty("kotlinx.coroutines.scheduler.keep.alive.sec", 100000) // any unpark problem hangs test
219 // Adjust internal algorithmic parameters to increase the testing quality instead of performance.
220 systemProperty("kotlinx.coroutines.semaphore.segmentSize", 1)
221 systemProperty("kotlinx.coroutines.semaphore.maxSpinCycles", 10)
222 systemProperty("kotlinx.coroutines.bufferedChannel.segmentSize", 2)
223 systemProperty("kotlinx.coroutines.bufferedChannel.expandBufferCompletionWaitIterations", 1)
224 }
225
<lambda>null226 val jvmLincheckTest by tasks.registering(Test::class) {
227 dependsOn(compileTestKotlinJvm)
228 classpath = jvmTest.classpath
229 testClassesDirs = jvmTest.testClassesDirs
230 include("**/*LincheckTest*")
231 enableAssertions = true
232 testLogging.showStandardStreams = true
233 configureJvmForLincheck()
234 }
235
236 // Additional Lincheck tests with `segmentSize = 2`.
237 // Some bugs cannot be revealed when storing one request per segment,
238 // and some are hard to detect when storing multiple requests.
<lambda>null239 val jvmLincheckTestAdditional by tasks.registering(Test::class) {
240 dependsOn(compileTestKotlinJvm)
241 classpath = jvmTest.classpath
242 testClassesDirs = jvmTest.testClassesDirs
243 include("**/RendezvousChannelLincheckTest*")
244 include("**/Buffered1ChannelLincheckTest*")
245 include("**/Semaphore*LincheckTest*")
246 enableAssertions = true
247 testLogging.showStandardStreams = true
248 configureJvmForLincheck(segmentSize = 2)
249 }
250
Testnull251 fun Test.configureJvmForLincheck(segmentSize: Int = 1) {
252 minHeapSize = "1g"
253 maxHeapSize = "4g" // we may need more space for building an interleaving tree in the model checking mode
254 // https://github.com/JetBrains/lincheck#java-9
255 jvmArgs = listOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", // required for transformation
256 "--add-exports", "java.base/sun.security.action=ALL-UNNAMED",
257 "--add-exports", "java.base/jdk.internal.util=ALL-UNNAMED") // in the model checking mode
258 // Adjust internal algorithmic parameters to increase the testing quality instead of performance.
259 systemProperty("kotlinx.coroutines.semaphore.segmentSize", segmentSize)
260 systemProperty("kotlinx.coroutines.semaphore.maxSpinCycles", 1) // better for the model checking mode
261 systemProperty("kotlinx.coroutines.bufferedChannel.segmentSize", segmentSize)
262 systemProperty("kotlinx.coroutines.bufferedChannel.expandBufferCompletionWaitIterations", 1)
263 }
264
265 // Always check additional test sets
<lambda>null266 val moreTest by tasks.registering {
267 dependsOn(listOf(jvmStressTest, jvmLincheckTest, jvmLincheckTestAdditional))
268 }
269
<lambda>null270 val check by tasks.getting {
271 dependsOn(moreTest)
272 }
273
<lambda>null274 kover {
275 currentProject {
276 instrumentation {
277 // Always disabled, lincheck doesn't really support coverage
278 disabledForTestTasks.addAll("jvmLincheckTest")
279
280 // lincheck has NPE error on `ManagedStrategyStateHolder` class
281 excludedClasses.addAll("org.jetbrains.kotlinx.lincheck.*")
282 }
283 }
284
285 reports {
286 filters {
287 excludes {
288 classes(
289 "kotlinx.coroutines.debug.*", // Tested by debug module
290 "kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt*", // Deprecated
291 "kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated
292 "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher", // Deprecated
293 "kotlinx.coroutines.flow.FlowKt__MigrationKt*", // Migrations
294 "kotlinx.coroutines.flow.LintKt*", // Migrations
295 "kotlinx.coroutines.internal.WeakMapCtorCache", // Fallback implementation that we never test
296 "_COROUTINE._CREATION", // For IDE navigation
297 "_COROUTINE._BOUNDARY", // For IDE navigation
298 )
299 }
300 }
301 }
302 }
303
<lambda>null304 val testsJar by tasks.registering(Jar::class) {
305 dependsOn(jvmTestClasses)
306 archiveClassifier = "tests"
307 from(compileTestKotlinJvm.destinationDirectory)
308 }
309
<lambda>null310 artifacts {
311 archives(testsJar)
312 }
313
314 // Workaround for https://github.com/Kotlin/dokka/issues/1833: make implicit dependency explicit
<lambda>null315 tasks.named("dokkaHtmlPartial") {
316 dependsOn(jvmJar)
317 }
318