Skip to content

Commit 1a3b7b2

Browse files
TapchicomaSpace Team
authored andcommitted
[Gradle] Fix testFixtures compilation classpath may miss declared dependencies
Gradle test-fixtures plugin creates the own set of configurations which are not connected to the Kotlin compilation created for test fixtures. This leads to a different set of dependencies depending on which configuration they were declared. Fix ensures both compile and runtime classpaths are synced between the Java and Kotlin set of configurations. ^KT-77466 Verification Pending
1 parent b7eda79 commit 1a3b7b2

File tree

6 files changed

+210
-24
lines changed

6 files changed

+210
-24
lines changed

libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kmp-jvm-test-fixtures/lib/src/testFixtures/kotlin/LibTestFixtures.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.foo
33
import com.example.CommonMain
44
import com.example.JvmMain
55
import com.example.JvmTestFixtures
6+
import com.example.JavaTestFixtures
67
import kotlinx.coroutines.*
78
import kotlinx.serialization.json.Json
89

@@ -19,3 +20,11 @@ suspend fun shouldWork() = coroutineScope {
1920
fun shouldAlsoWork() {
2021
val s = Json.decodeFromString<String>("")
2122
}
23+
24+
fun another() {
25+
JvmTestFixtures().helperForTest()
26+
}
27+
28+
fun anotherJava() {
29+
JavaTestFixtures().createJsonSerializer()
30+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example;
2+
3+
import kotlinx.serialization.json.Json;
4+
5+
public class JavaTestFixtures {
6+
public Json createJsonSerializer() {
7+
return new JvmTestFixtures().createJsonSerializer();
8+
}
9+
}
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
package com.example
22

3-
class JvmTestFixtures
3+
import kotlinx.coroutines.*
4+
import kotlinx.serialization.*
5+
import kotlinx.serialization.json.*
6+
7+
class JvmTestFixtures {
8+
fun helperForTest() = runBlocking {
9+
CommonMain()
10+
JvmMain()
11+
}
12+
13+
fun createJsonSerializer() = Json { ignoreUnknownKeys = true }
14+
}

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmCompilationWireJavaSourcesSideEffect.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ internal val KotlinTarget.kotlinSourceSetDslName: String
103103
else -> KOTLIN_DSL_NAME
104104
}
105105

106+
// see https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph
106107
internal fun setupDependenciesCrossInclusionForJava(
107108
compilation: KotlinJvmCompilation,
108109
javaSourceSet: SourceSet,

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmTargetTestFixturesSideEffect.kt

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ package org.jetbrains.kotlin.gradle.targets.jvm
88
import org.gradle.api.file.ConfigurableFileCollection
99
import org.gradle.internal.component.external.model.TestFixturesSupport.TEST_FIXTURE_SOURCESET_NAME
1010
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
11+
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
12+
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
13+
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
1114
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
1215
import org.jetbrains.kotlin.gradle.targets.KotlinTargetSideEffect
16+
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
1317
import org.jetbrains.kotlin.gradle.utils.javaSourceSets
1418

1519
private const val JAVA_TEST_FIXTURES_PLUGIN_ID = "java-test-fixtures"
@@ -33,13 +37,25 @@ internal val ConfigureJavaTestFixturesSideEffect = KotlinTargetSideEffect { targ
3337
testCompilation.associateWith(testFixturesCompilation)
3438
if (target is KotlinJvmTarget) {
3539
// Only applicable for KMP
36-
val testFixturesApiElements = target.project.configurations.getByName(testFixturesSourceSet.apiElementsConfigurationName)
37-
testFixturesApiElements.extendsFrom(
38-
target.project.configurations.getByName(mainCompilation.apiConfigurationName)
39-
)
40-
testFixturesApiElements.extendsFrom(
41-
target.project.configurations.getByName(testFixturesCompilation.apiConfigurationName)
40+
setupDependenciesCrossInclusionForJava(
41+
testFixturesCompilation as KotlinJvmCompilation,
42+
testFixturesSourceSet
4243
)
44+
45+
// Publishing
46+
listOfNotNull(
47+
testFixturesCompilation.apiConfigurationName
48+
).forEach { configurationName ->
49+
target.project.addExtendsFromRelation(testFixturesSourceSet.apiElementsConfigurationName, configurationName)
50+
}
51+
52+
listOfNotNull(
53+
testFixturesCompilation.implementationConfigurationName,
54+
testFixturesCompilation.runtimeOnlyConfigurationName
55+
).forEach { configurationName ->
56+
target.project.addExtendsFromRelation(testFixturesSourceSet.runtimeElementsConfigurationName, configurationName)
57+
}
58+
4359
(testFixturesSourceSet.output.classesDirs as? ConfigurableFileCollection)?.from(
4460
testFixturesCompilation.output.classesDirs
4561
) ?: target.project.logger.warn(
@@ -55,4 +71,4 @@ internal val ConfigureJavaTestFixturesSideEffect = KotlinTargetSideEffect { targ
5571
"source set has been associated with the $defaultSourceSetNames source sets to provide `internal` declarations access."
5672
)
5773
}
58-
}
74+
}

libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/TestFixturesTest.kt

Lines changed: 156 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55

66
package org.jetbrains.kotlin.gradle.unitTests
77

8+
import org.gradle.api.artifacts.Configuration
89
import org.gradle.api.internal.project.ProjectInternal
910
import org.gradle.api.tasks.bundling.Jar
1011
import org.gradle.kotlin.dsl.dependencies
12+
import org.gradle.kotlin.dsl.get
1113
import org.gradle.api.tasks.testing.Test as TestTask
1214
import org.gradle.kotlin.dsl.repositories
1315
import org.gradle.language.jvm.tasks.ProcessResources
1416
import org.jetbrains.kotlin.gradle.dependencyResolutionTests.mavenCentralCacheRedirector
1517
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
18+
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
1619
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
1720
import org.jetbrains.kotlin.gradle.tasks.withType
1821
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
@@ -74,7 +77,7 @@ class TestFixturesTest {
7477
}
7578

7679
@Test
77-
fun kt75808testFixtureDependenciesAreExposedCorrectly() {
80+
fun kt77466testFixturesDependenciesAreCorrectlyPropagatedToCompilation() {
7881
val project = buildProjectWithMPP(
7982
preApplyCode = { plugins.apply("java-test-fixtures") }
8083
) {
@@ -83,28 +86,142 @@ class TestFixturesTest {
8386
}
8487

8588
dependencies {
86-
"testFixturesApi"("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
87-
"jvmTestFixturesApi"("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
89+
"testFixturesApi"("example:A:1.0.0")
90+
"jvmTestFixturesApi"("example:B:1.0.0")
91+
"testFixturesImplementation"("example:C:1.0.0")
92+
"jvmTestFixturesImplementation"("example:D:1.0.0")
93+
"testFixturesCompileOnly"("example:E:1.0.0")
94+
"jvmTestFixturesCompileOnly"("example:F:1.0.0")
8895
}
8996
}
9097

9198
project.evaluate()
9299

93-
val testFixturesApiElements = project.configurations.getByName("testFixturesApiElements")
94-
val allDeps = testFixturesApiElements.allDependencies
95-
assertEquals(2, testFixturesApiElements.allDependencies.count())
96-
assertTrue(
97-
actual = allDeps.any { dependency ->
98-
dependency.group == "org.jetbrains.kotlinx" && dependency.name == "kotlinx-coroutines-core"
99-
},
100-
message = "Outgoing configuration does not contain 'org.jetbrains.kotlinx:kotlinx-coroutines-core' dependency"
100+
val expectedDeps = setOf(
101+
Triple("example", "A", "1.0.0"),
102+
Triple("example", "B", "1.0.0"),
103+
Triple("example", "C", "1.0.0"),
104+
Triple("example", "D", "1.0.0"),
105+
Triple("example", "E", "1.0.0"),
106+
Triple("example", "F", "1.0.0"),
101107
)
102-
assertTrue(
103-
actual = allDeps.any { dependency ->
104-
dependency.group == "org.jetbrains.kotlinx" && dependency.name == "kotlinx-serialization-json"
105-
},
106-
message = "Outgoing configuration does not contain 'org.jetbrains.kotlinx:kotlinx-serialization-json' dependency"
108+
109+
project.configurations
110+
.getByName(project.javaSourceSets["testFixtures"].compileClasspathConfigurationName)
111+
.assertDependenciesPresent(expectedDeps)
112+
113+
project.configurations
114+
.getByName(
115+
project.multiplatformExtension.targets.getByName("jvm").compilations.getByName("testFixtures").compileDependencyConfigurationName
116+
)
117+
.assertDependenciesPresent(expectedDeps)
118+
}
119+
120+
@Test
121+
fun kt77466testFixturesDependenciesAreCorrectlyPropagatedToRuntime() {
122+
val project = buildProjectWithMPP(
123+
preApplyCode = { plugins.apply("java-test-fixtures") }
124+
) {
125+
kotlin {
126+
jvm()
127+
}
128+
129+
dependencies {
130+
"testFixturesApi"("example:A:1.0.0")
131+
"jvmTestFixturesApi"("example:B:1.0.0")
132+
"testFixturesImplementation"("example:C:1.0.0")
133+
"jvmTestFixturesImplementation"("example:D:1.0.0")
134+
"testFixturesRuntimeOnly"("example:E:1.0.0")
135+
"jvmTestFixturesRuntimeOnly"("example:F:1.0.0")
136+
}
137+
}
138+
139+
project.evaluate()
140+
141+
val expectedDeps = setOf(
142+
Triple("example", "A", "1.0.0"),
143+
Triple("example", "B", "1.0.0"),
144+
Triple("example", "C", "1.0.0"),
145+
Triple("example", "D", "1.0.0"),
146+
Triple("example", "E", "1.0.0"),
147+
Triple("example", "F", "1.0.0"),
107148
)
149+
150+
project.configurations
151+
.getByName(project.javaSourceSets["testFixtures"].runtimeClasspathConfigurationName)
152+
.assertDependenciesPresent(expectedDeps)
153+
154+
project.configurations
155+
.getByName(
156+
project.multiplatformExtension.targets.getByName("jvm").compilations.getByName("testFixtures").runtimeDependencyConfigurationName!!
157+
)
158+
.assertDependenciesPresent(expectedDeps)
159+
}
160+
161+
@Test
162+
fun kt75808testFixtureDependenciesAreCorrectlyPropagatedToApiElements() {
163+
val project = buildProjectWithMPP(
164+
preApplyCode = { plugins.apply("java-test-fixtures") }
165+
) {
166+
kotlin {
167+
jvm()
168+
}
169+
170+
dependencies {
171+
"testFixturesApi"("example:A:1.0.0")
172+
"jvmTestFixturesApi"("example:B:1.0.0")
173+
"testFixturesImplementation"("example:C:1.0.0")
174+
"jvmTestFixturesImplementation"("example:D:1.0.0")
175+
"testFixturesRuntimeOnly"("example:E:1.0.0")
176+
"jvmTestFixturesRuntimeOnly"("example:F:1.0.0")
177+
}
178+
}
179+
180+
project.evaluate()
181+
182+
val expectedDeps = setOf(
183+
Triple("example", "A", "1.0.0"),
184+
Triple("example", "B", "1.0.0"),
185+
)
186+
187+
project.configurations
188+
.getByName(project.javaSourceSets["testFixtures"].apiElementsConfigurationName)
189+
.assertDependenciesPresent(expectedDeps, shouldIncludeProjectDependencies = false)
190+
}
191+
192+
@Test
193+
fun kt75808testFixtureDependenciesAreCorrectlyPropagatedToRuntimeElements() {
194+
val project = buildProjectWithMPP(
195+
preApplyCode = { plugins.apply("java-test-fixtures") }
196+
) {
197+
kotlin {
198+
jvm()
199+
}
200+
201+
dependencies {
202+
"testFixturesApi"("example:A:1.0.0")
203+
"jvmTestFixturesApi"("example:B:1.0.0")
204+
"testFixturesImplementation"("example:C:1.0.0")
205+
"jvmTestFixturesImplementation"("example:D:1.0.0")
206+
"testFixturesRuntimeOnly"("example:E:1.0.0")
207+
"jvmTestFixturesRuntimeOnly"("example:F:1.0.0")
208+
}
209+
}
210+
211+
project.evaluate()
212+
213+
val expectedDeps = setOf(
214+
Triple("example", "A", "1.0.0"),
215+
Triple("example", "B", "1.0.0"),
216+
Triple("example", "C", "1.0.0"),
217+
Triple("example", "D", "1.0.0"),
218+
Triple("example", "E", "1.0.0"),
219+
Triple("example", "F", "1.0.0"),
220+
)
221+
222+
project.configurations
223+
.getByName(project.javaSourceSets["testFixtures"].runtimeElementsConfigurationName)
224+
.assertDependenciesPresent(expectedDeps)
108225
}
109226

110227
@Test
@@ -155,4 +272,27 @@ class TestFixturesTest {
155272
"Expected to see the main resources entry once, but got:\n${testClasspath.joinToString(separator = "\n")}"
156273
}
157274
}
275+
276+
277+
private fun Configuration.assertDependenciesPresent(
278+
expectedDeps: Set<Triple<String, String, String>>,
279+
shouldIncludeProjectDependencies: Boolean = true
280+
) {
281+
val kotlinTestFixturesAllDeps = allDependencies
282+
assertEquals(
283+
expectedDeps.size + if (shouldIncludeProjectDependencies) 2 else 0,
284+
kotlinTestFixturesAllDeps.size,
285+
message = "Actual content of configuration: ${kotlinTestFixturesAllDeps.joinToString { "${it.group}:${it.name}" }}"
286+
)
287+
expectedDeps.forEach { expectedDependency ->
288+
assertTrue(
289+
actual = kotlinTestFixturesAllDeps.any { dependency ->
290+
dependency.group == expectedDependency.first &&
291+
dependency.name == expectedDependency.second &&
292+
dependency.version == expectedDependency.third
293+
},
294+
message = "Outgoing configuration does not contain ''$expectedDependency' dependency"
295+
)
296+
}
297+
}
158298
}

0 commit comments

Comments
 (0)