Skip to content

Commit c08a991

Browse files
seregamorphsergei
andauthored
fix: Reason explanation id ambiguity (#1125)
Co-authored-by: sergei <sergei.chernov@adyen.com>
1 parent e218292 commit c08a991

File tree

4 files changed

+150
-4
lines changed

4 files changed

+150
-4
lines changed

src/functionalTest/groovy/com/autonomousapps/jvm/JvmReasonSpec.groovy

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
package com.autonomousapps.jvm
44

55
import com.autonomousapps.AbstractFunctionalSpec
6+
import com.autonomousapps.jvm.projects.BundleKmpProject2
67
import com.autonomousapps.jvm.projects.NestedSubprojectsProject
78
import com.autonomousapps.utils.Colors
89
import org.gradle.testkit.runner.BuildResult
10+
import org.gradle.util.GradleVersion
911
import spock.lang.PendingFeature
1012

1113
import static com.autonomousapps.utils.Runner.build
14+
import static com.autonomousapps.utils.Runner.buildAndFail
1215
import static com.google.common.truth.Truth.assertThat
1316

1417
final class JvmReasonSpec extends AbstractFunctionalSpec {
@@ -44,6 +47,36 @@ final class JvmReasonSpec extends AbstractFunctionalSpec {
4447
gradleVersion << gradleVersions()
4548
}
4649
50+
def "reason fails when there is dependency filtering ambiguity"() {
51+
given:
52+
def project = new BundleKmpProject2()
53+
gradleProject = project.gradleProject
54+
55+
when:
56+
def result = buildAndFail(gradleVersion, gradleProject.rootDir, ':consumer:reason', '--id', 'com.squareup.okio:oki')
57+
58+
then:
59+
assertThat(result.output).contains("> Coordinates 'com.squareup.okio:oki' matches more than 1 dependency [com.squareup.okio:okio-jvm:3.0.0, com.squareup.okio:okio:3.0.0]")
60+
61+
where:
62+
gradleVersion << [GradleVersion.current()]
63+
}
64+
65+
def "reason matches startsWith when there is no ambiguity"() {
66+
given:
67+
def project = new BundleKmpProject2()
68+
gradleProject = project.gradleProject
69+
70+
when:
71+
def result = build(gradleVersion, gradleProject.rootDir, ':consumer:reason', '--id', 'com.squareup.okio:okio-')
72+
73+
then:
74+
assertThat(result.output).contains("You asked about the dependency 'com.squareup.okio:okio-jvm:3.0.0'.")
75+
76+
where:
77+
gradleVersion << [GradleVersion.current()]
78+
}
79+
4780
private static void outputMatchesForProject(BuildResult result, String id) {
4881
def lines = Colors.decolorize(result.output).readLines()
4982
def asked = lines.find { it.startsWith('You asked about') }

src/main/kotlin/com/autonomousapps/internal/utils/coordinatesStrings.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,16 @@ internal fun <T> String.matchesKey(mapEntry: Map.Entry<String, T>): Boolean {
3434
return false
3535
}
3636

37-
internal fun <T> String.equalsKey(mapEntry: Map.Entry<String, T>) =
38-
mapEntry.key.firstCoordinatesKeySegment() == this || mapEntry.key.secondCoordinatesKeySegment() == this
37+
internal fun <T> String.equalsKey(mapEntry: Map.Entry<String, T>): Boolean {
38+
val tokens = mapEntry.key.firstCoordinatesKeySegment().split(":")
39+
if (tokens.size == 3) {
40+
// "groupId:artifactId:version" => "groupId:artifactId"
41+
if ("${tokens[0]}:${tokens[1]}" == this) {
42+
return true
43+
}
44+
}
45+
return mapEntry.key.firstCoordinatesKeySegment() == this || mapEntry.key.secondCoordinatesKeySegment() == this
46+
}
3947

4048
private fun <T> String.startsWithKey(mapEntry: Map.Entry<String, T>) =
4149
mapEntry.key.firstCoordinatesKeySegment().startsWith(this) || mapEntry.key.secondCoordinatesKeySegment()?.startsWith(this) == true

src/main/kotlin/com/autonomousapps/tasks/ReasonTask.kt

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ abstract class ReasonTask @Inject constructor(
216216
}?.gav()
217217

218218
// Guaranteed to find full GAV or throw
219-
val gavKey = dependencyUsages.entries.find(requestedId::matchesKey)?.key
220-
?: annotationProcessorUsages.entries.find(requestedId::matchesKey)?.key
219+
val gavKey = findFilteredDependencyKey(dependencyUsages.entries, requestedId)
220+
?: findFilteredDependencyKey(annotationProcessorUsages.entries, requestedId)
221221
?: findInGraph()
222222
?: throw InvalidUserDataException("There is no dependency with coordinates '$requestedId' in this project.")
223223

@@ -253,6 +253,29 @@ abstract class ReasonTask @Inject constructor(
253253
}
254254

255255
private fun wasFiltered(): Boolean = finalAdvice == null && unfilteredAdvice != null
256+
257+
internal companion object {
258+
internal fun findFilteredDependencyKey(dependencies: Set<Map.Entry<String, Any>>, requestedId: String): String? {
259+
val filteredKeys = LinkedHashSet<String>()
260+
for (entry in dependencies) {
261+
if (requestedId.equalsKey(entry)) {
262+
// for exact equal - return immediately
263+
return entry.key
264+
}
265+
if (requestedId.matchesKey(entry)) {
266+
filteredKeys.add(entry.key)
267+
}
268+
}
269+
return if (filteredKeys.isEmpty()) {
270+
null
271+
} else if (filteredKeys.size == 1) {
272+
filteredKeys.iterator().next()
273+
} else {
274+
throw InvalidUserDataException("Coordinates '$requestedId' matches more than 1 dependency " +
275+
"${filteredKeys.map { it.secondCoordinatesKeySegment() ?: it }}")
276+
}
277+
}
278+
}
256279
}
257280

258281
interface ExplainModuleAdviceParams : WorkParameters {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.autonomousapps.tasks
2+
3+
import com.autonomousapps.model.declaration.Bucket
4+
import com.autonomousapps.model.declaration.Variant
5+
import com.autonomousapps.model.intermediates.Usage
6+
import com.autonomousapps.tasks.ReasonTask.ExplainDependencyAdviceAction.Companion.findFilteredDependencyKey
7+
import org.gradle.api.InvalidUserDataException
8+
import org.junit.jupiter.api.Assertions.assertEquals
9+
import org.junit.jupiter.api.Assertions.assertThrows
10+
import org.junit.jupiter.api.Test
11+
12+
class ReasonTaskTest {
13+
14+
private val usage = Usage(null, null, Variant.MAIN, Bucket.NONE, emptySet())
15+
16+
private val entries = mapOf(
17+
"demo-gradle-multi-module:list|:list" to usage,
18+
"demo-gradle-multi-module:list:sublist|:list:sublist" to usage,
19+
"demo-gradle-multi-module:list-default|:list-default" to usage,
20+
"demo-gradle-multi-module:list-impl|:list-impl" to usage,
21+
"com.squareup.okio:okio-jvm:3.0.0" to usage,
22+
"com.squareup.okio:okio:3.0.0" to usage
23+
).entries
24+
25+
@Test
26+
fun shouldThrowOnProjectModuleAmbiguity() {
27+
val ex = assertThrows(InvalidUserDataException::class.java) { findFilteredDependencyKey(entries, ":li") }
28+
assertEquals("Coordinates ':li' matches more than 1 dependency " +
29+
"[:list, :list:sublist, :list-default, :list-impl]", ex.message)
30+
}
31+
32+
@Test
33+
fun shouldMatchEqualProjectModule() {
34+
val key = findFilteredDependencyKey(entries, ":list")
35+
assertEquals("demo-gradle-multi-module:list|:list", key)
36+
}
37+
38+
@Test
39+
fun shouldMatchPrefixProjectModuleColon() {
40+
val key = findFilteredDependencyKey(entries, ":list:")
41+
assertEquals("demo-gradle-multi-module:list:sublist|:list:sublist", key)
42+
}
43+
44+
@Test
45+
fun shouldMatchPrefixProjectModule() {
46+
val key = findFilteredDependencyKey(entries, ":list-d")
47+
assertEquals("demo-gradle-multi-module:list-default|:list-default", key)
48+
}
49+
50+
@Test
51+
fun shouldThrowOnLibraryAmbiguity() {
52+
val ex = assertThrows(InvalidUserDataException::class.java) {
53+
findFilteredDependencyKey(entries, "com.squareup.okio:oki")
54+
}
55+
assertEquals("Coordinates 'com.squareup.okio:oki' matches more than 1 dependency " +
56+
"[com.squareup.okio:okio-jvm:3.0.0, com.squareup.okio:okio:3.0.0]", ex.message)
57+
}
58+
59+
@Test
60+
fun shouldMatchEqualLibrary() {
61+
val key = findFilteredDependencyKey(entries, "com.squareup.okio:okio")
62+
assertEquals("com.squareup.okio:okio:3.0.0", key)
63+
}
64+
65+
@Test
66+
fun shouldMatchPrefixLibraryColon() {
67+
val key = findFilteredDependencyKey(entries, "com.squareup.okio:okio:")
68+
assertEquals("com.squareup.okio:okio:3.0.0", key)
69+
}
70+
71+
@Test
72+
fun shouldMatchLibraryVersion() {
73+
val key = findFilteredDependencyKey(entries, "com.squareup.okio:okio:3.0.0")
74+
assertEquals("com.squareup.okio:okio:3.0.0", key)
75+
}
76+
77+
@Test
78+
fun shouldMatchPrefixLibrary() {
79+
val key = findFilteredDependencyKey(entries, "com.squareup.okio:okio-")
80+
assertEquals("com.squareup.okio:okio-jvm:3.0.0", key)
81+
}
82+
}

0 commit comments

Comments
 (0)