Skip to content

Commit 301186f

Browse files
committed
[Gradle] Fix passing process environment when launching KotlinKarma tests
Correctly forward environment variables when launching Kotlin/JS tests. Update ExecAsyncHandle to log exec parameters. Add test to verify env can be customised. ^KT-73142 ^KT-77119 (cherry picked from commit 231d6bc)
1 parent 12b4021 commit 301186f

File tree

4 files changed

+107
-15
lines changed

4 files changed

+107
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
3+
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
4+
*/
5+
6+
package org.jetbrains.kotlin.gradle.js
7+
8+
import org.gradle.api.logging.LogLevel
9+
import org.gradle.testkit.runner.GradleRunner
10+
import org.gradle.util.GradleVersion
11+
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
12+
import org.jetbrains.kotlin.gradle.testbase.*
13+
import org.jetbrains.kotlin.gradle.uklibs.applyMultiplatform
14+
import kotlin.test.assertContains
15+
16+
@JsGradlePluginTests
17+
class JsBrowserTestsIT : KGPBaseTest() {
18+
19+
@GradleTest
20+
fun `verify custom custom KotlinJsTest environment variables are used to launch tests`(gradleVersion: GradleVersion) {
21+
project(
22+
"buildScriptInjection",
23+
gradleVersion = gradleVersion,
24+
buildOptions = defaultBuildOptions.copy(
25+
logLevel = LogLevel.DEBUG,
26+
)
27+
) {
28+
addKgpToBuildScriptCompilationClasspath()
29+
buildScriptInjection {
30+
project.applyMultiplatform {
31+
js().browser()
32+
sourceSets.commonTest.dependencies {
33+
implementation(kotlin("test"))
34+
}
35+
}
36+
37+
project.projectDir.resolve("src/jsTest/kotlin/DummyTest.kt").apply {
38+
parentFile.mkdirs()
39+
writeText(
40+
"""
41+
class DummyTest {
42+
@kotlin.test.Test
43+
fun dummy() {
44+
println("dummy test")
45+
}
46+
}
47+
""".trimIndent()
48+
)
49+
}
50+
51+
project.tasks.withType(KotlinJsTest::class.java).configureEach { task ->
52+
task.environment("CUSTOM_ENV", "custom-env-value")
53+
54+
// KT-77134 verify doFirst {} workaround,
55+
// which is necessary because KotlinJsTest doesn't use Provider API.
56+
val lazyValue = project.provider { "lazy-custom-env-value" }
57+
task.doFirst { _ ->
58+
task.environment("CUSTOM_ENV_LAZY", lazyValue.get())
59+
}
60+
}
61+
}
62+
63+
build(
64+
":jsBrowserTest",
65+
// :jsBrowserTest might fail if no browsers are installed (e.g. on CI).
66+
// For this test we don't care if the task passes or fails, only if the custom environment variables are set correctly.
67+
// So, use `GradleRunner.run()` to ignore the build outcome.
68+
gradleRunnerAction = GradleRunner::run,
69+
) {
70+
val execAsyncHandleLogs = output.lineSequence()
71+
.mapNotNull {
72+
it
73+
.substringAfter(" [DEBUG] [org.jetbrains.kotlin.gradle.utils.processes.ExecAsyncHandle] ", "")
74+
.ifBlank { null }
75+
}
76+
77+
val createdExecSpecLog = execAsyncHandleLogs
78+
.singleOrNull { it.startsWith("[ExecAsyncHandle :jsBrowserTest] created ExecSpec.") }
79+
80+
requireNotNull(createdExecSpecLog) {
81+
"Could not find 'created ExecSpec' log in build output:\n${execAsyncHandleLogs.joinToString("\n").prependIndent()}"
82+
}
83+
84+
val env = createdExecSpecLog.substringAfter("Environment: {").substringBefore("},")
85+
assertContains(env, "CUSTOM_ENV=custom-env-value")
86+
assertContains(env, "CUSTOM_ENV_LAZY=lazy-custom-env-value")
87+
}
88+
}
89+
}
90+
}

libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ fun TestProject.build(
173173
environmentVariables: EnvironmentalVariables = this.environmentVariables,
174174
inputStream: InputStream? = null,
175175
forwardBuildOutput: Boolean = enableGradleDebug.toBooleanFlag(overridingEnvironmentVariablesInstantiationBacktrace = environmentVariables.overridingEnvironmentVariablesInstantiationBacktrace),
176+
gradleRunnerAction: GradleRunner.() -> BuildResult = GradleRunner::build,
176177
assertions: BuildResult.() -> Unit = {},
177178
) {
178179
if (enableBuildScan) agreeToBuildScanService()
@@ -205,7 +206,7 @@ fun TestProject.build(
205206
}
206207

207208
withBuildSummary(allBuildArguments) {
208-
val buildResult = gradleRunnerForBuild.build()
209+
val buildResult = gradleRunnerForBuild.gradleRunnerAction()
209210
if (enableBuildScan) buildResult.printBuildScanUrl()
210211
assertions(buildResult)
211212
buildResult.additionalAssertions(buildOptions)

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -467,13 +467,8 @@ class KotlinKarma internal constructor(
467467
listOf("start", karmaConfigAbsolutePath)
468468
}
469469

470-
val processLaunchOpts = objects.processLaunchOptions {
471-
this.workingDir.set(this@KotlinKarma.workingDir)
472-
this.executable.set(this@KotlinKarma.executable)
473-
}
474-
475470
return object : JSServiceMessagesTestExecutionSpec(
476-
processLaunchOpts = processLaunchOpts,
471+
processLaunchOpts = launchOpts,
477472
processArgs = args,
478473
checkExitCode = true,
479474
clientSettings = clientSettings

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/processes/ExecAsyncHandle.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,9 @@ internal sealed interface ExecAsyncHandle {
6868
return ExecAsyncHandleImpl(
6969
displayName = displayName,
7070
abortTimeout = abortTimeout,
71-
) {
72-
exec { exec ->
73-
exec.isIgnoreExitValue = true
74-
configure(exec)
75-
}
76-
}
71+
execOperations = this,
72+
configure = configure,
73+
)
7774
}
7875
}
7976
}
@@ -82,7 +79,8 @@ internal sealed interface ExecAsyncHandle {
8279
private class ExecAsyncHandleImpl(
8380
override val displayName: String,
8481
private val abortTimeout: Duration,
85-
private val run: () -> ExecResult,
82+
private val execOperations: ExecOperations,
83+
private val configure: (execSpec: ExecSpec) -> Unit,
8684
) : ExecAsyncHandle {
8785
private val logTag: String = "[ExecAsyncHandle $displayName]"
8886

@@ -96,14 +94,22 @@ private class ExecAsyncHandleImpl(
9694
) {
9795
logger.info("$logTag started")
9896
try {
99-
result.set(run())
97+
result.set(exec())
10098
logger.info("$logTag finished ${result.get()}")
10199
} catch (e: Exception) {
102100
failure.set(e)
103101
logger.info("$logTag failed $e")
104102
}
105103
}
106104

105+
private fun exec(): ExecResult {
106+
return execOperations.exec { exec ->
107+
exec.isIgnoreExitValue = true
108+
configure(exec)
109+
logger.debug("$logTag created ExecSpec. Command: ${exec.commandLine.joinToString()}, Environment: ${exec.environment}, WorkingDir: ${exec.workingDir}")
110+
}
111+
}
112+
107113
override fun start(): ExecAsyncHandle {
108114
thread.start()
109115
return this

0 commit comments

Comments
 (0)