From 7c09b1b374afc4365d29d75f4160dc2d128fad9d Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 19 Feb 2026 00:58:34 +0100 Subject: [PATCH 1/6] Add new graph implementation that checks for dependencies across multiple configurations --- .../internal/ConfigurationDependencyGraph.kt | 102 +++++++++++ .../internal/DependencyConfiguration.kt | 10 +- .../plugin/internal/DependencyGraph.kt | 158 ++++++++++++------ .../plugin/internal/DependencyGraphBuilder.kt | 12 +- .../internal/DependencyRestrictionFinder.kt | 4 +- .../internal/task/TaskDependencyDump.kt | 8 +- .../plugin/DependencyPluginSimulator.kt | 6 +- .../plugin/ProjectGuardFixtures.kt | 6 +- .../plugin/TaskDependencyDumpTest.kt | 12 +- .../internal/DependencyGraphBuilderTest.kt | 8 +- .../plugin/internal/DependencyGraphTest.kt | 90 +++++++++- .../internal/DependencyRestrictionTest.kt | 4 +- .../plugin/internal/GuardRestrictionTest.kt | 4 +- .../plugin/internal/ModuleRestrictionTest.kt | 2 +- 14 files changed, 336 insertions(+), 90 deletions(-) create mode 100644 projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt new file mode 100644 index 0000000..8f3a68e --- /dev/null +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2026 Rúben Sousa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.rubensousa.projectguard.plugin.internal + +import java.io.Serializable + +internal class ConfigurationDependencyGraph( + val id: String, + val nodes: MutableMap> = mutableMapOf(), +) : Serializable { + + private val libraries = mutableSetOf() + + fun addInternalDependency(module: String, dependency: String) { + addDependency(module, dependency) + } + + fun addExternalDependency(module: String, dependency: String) { + addDependency(module, dependency) + libraries.add(dependency) + } + + private fun addDependency(module: String, dependency: String) { + val existingDependencies = nodes.getOrPut(module) { mutableSetOf() } + existingDependencies.add(dependency) + } + + fun isExternalLibrary(dependency: String): Boolean { + return libraries.contains(dependency) + } + + fun getDependencies(module: String): Set { + return nodes[module] ?: emptySet() + } + + fun getAllDependencies(module: String): List { + /** + * Until https://github.com/rubensousa/ProjectGuard/issues/3 is resolved, + * exclude transitive dependency traversals for test configurations + */ + if (id.contains("test")) { + return getDependencies(module).map { + DirectDependency(it) + } + } + val visitedDependencies = mutableSetOf() + val paths = mutableMapOf() + val stack = ArrayDeque() + stack.addAll(getDependencies(module).map { dependency -> + TraversalState(dependency, emptyList()) + }) + while (stack.isNotEmpty()) { + val currentModule = stack.removeFirst() + val currentDependency = currentModule.dependency + if (visitedDependencies.contains(currentDependency)) { + continue + } + paths[currentDependency] = if (currentModule.path.isEmpty()) { + DirectDependency(currentDependency) + } else { + TransitiveDependency( + currentDependency, + currentModule.path + currentDependency + ) + } + visitedDependencies.add(currentDependency) + getDependencies(currentDependency).forEach { nextDependency -> + stack.addFirst( + TraversalState( + nextDependency, + currentModule.path + currentDependency + ) + ) + } + } + return paths.values.toList() + } + + override fun toString(): String { + return "ConfigurationDependencyGraph(configurationId='$id', nodes=$nodes)" + } + + private data class TraversalState( + val dependency: String, + val path: List, + ) + +} diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt index bae17b9..741deab 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt @@ -18,11 +18,15 @@ package com.rubensousa.projectguard.plugin.internal internal object DependencyConfiguration { + const val COMPILE = "compileClasspath" + const val TEST = "testCompileClasspath" + const val TEST_FIXTURE = "testFixturesCompileClasspath" + private val supportedConfigurations = mutableSetOf( "androidTestUtil", - "compileClasspath", - "testCompileClasspath", - "testFixturesCompileClasspath", + COMPILE, + TEST, + TEST_FIXTURE, ) fun isConfigurationSupported(configurationId: String): Boolean { diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt index 78dd55b..50878c3 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt @@ -18,85 +18,147 @@ package com.rubensousa.projectguard.plugin.internal import java.io.Serializable -internal class DependencyGraph( - val configurationId: String, - val nodes: MutableMap> = mutableMapOf(), -) : Serializable { +internal class DependencyGraph : Serializable { + private val configurations = mutableMapOf() private val libraries = mutableSetOf() - fun addInternalDependency(module: String, dependency: String) { - addDependency(module, dependency) + fun addInternalDependency( + module: String, + dependency: String, + configurationId: String = DependencyConfiguration.COMPILE, + ) { + addDependency( + module = module, + dependency = dependency, + configurationId = configurationId + ) } - fun addExternalDependency(module: String, dependency: String) { - addDependency(module, dependency) + fun addExternalDependency( + module: String, + dependency: String, + configurationId: String = DependencyConfiguration.COMPILE, + ) { + addDependency( + module = module, + dependency = dependency, + configurationId = configurationId + ) libraries.add(dependency) } - private fun addDependency(module: String, dependency: String) { - val existingDependencies = nodes.getOrPut(module) { mutableSetOf() } - existingDependencies.add(dependency) - } - - fun isExternalLibrary(dependency: String): Boolean { - return libraries.contains(dependency) - } - - fun getDependencies(module: String): Set { - return nodes[module] ?: emptySet() + private fun addDependency( + module: String, + dependency: String, + configurationId: String, + ) { + val configuration = configurations.getOrPut(configurationId) { + ConfigurationGraph(configurationId) + } + configuration.add(module = module, dependency = dependency) } - fun getAllDependencies(module: String): List { - /** - * Until https://github.com/rubensousa/ProjectGuard/issues/3 is resolved, - * exclude transitive dependency traversals for test configurations - */ - if (configurationId.contains("test")) { - return getDependencies(module).map { - DirectDependency(it) - } - } + fun getDependencies(module: String): List { val visitedDependencies = mutableSetOf() val paths = mutableMapOf() - val stack = ArrayDeque() - stack.addAll(getDependencies(module).map { dependency -> - TraversalState(dependency, emptyList()) - }) - while (stack.isNotEmpty()) { - val currentModule = stack.removeFirst() - val currentDependency = currentModule.dependency + val queue = ArrayDeque() + configurations.values.forEach { configuration -> + configuration.getDependencies(module).forEach { dependency -> + queue.addFirst( + TraversalState( + configurationId = configuration.id, + dependency = dependency, + isFromTestDependency = !DependencyConfiguration.isReleaseConfiguration(configuration.id), + path = emptyList() + ) + ) + } + } + while (queue.isNotEmpty()) { + val currentTraversal = queue.removeFirst() + val currentDependency = currentTraversal.dependency if (visitedDependencies.contains(currentDependency)) { continue } - paths[currentDependency] = if (currentModule.path.isEmpty()) { + paths[currentDependency] = if (currentTraversal.path.isEmpty()) { DirectDependency(currentDependency) } else { TransitiveDependency( currentDependency, - currentModule.path + currentDependency + currentTraversal.path + currentDependency ) } visitedDependencies.add(currentDependency) - getDependencies(currentDependency).forEach { nextDependency -> - stack.addFirst( - TraversalState( - nextDependency, - currentModule.path + currentDependency + val targetConfigurations = if (currentTraversal.isFromTestDependency) { + // We've gone through a test dependency, search only for release configurations + configurations.values.filter { configuration -> + DependencyConfiguration.isReleaseConfiguration(configuration.id) + } + } else { + configurations.values + } + targetConfigurations.forEach { configuration -> + configuration.getDependencies(currentDependency).forEach { nextDependency -> + queue.addFirst( + TraversalState( + configurationId = configuration.id, + isFromTestDependency = !DependencyConfiguration.isReleaseConfiguration(configuration.id), + dependency = nextDependency, + path = currentTraversal.path + currentDependency + ) ) - ) + } } } return paths.values.toList() } - override fun toString(): String { - return "DependencyGraph(configurationId='$configurationId', nodes=$nodes)" - } - private data class TraversalState( + val configurationId: String, + val isFromTestDependency: Boolean, val dependency: String, val path: List, ) + private class ConfigurationGraph(val id: String) : Serializable { + + private val nodes = mutableMapOf>() + + fun add(module: String, dependency: String) { + val existingDependencies = nodes.getOrPut(module) { mutableSetOf() } + existingDependencies.add(dependency) + } + + fun getDependencies(module: String): Set { + return nodes[module] ?: emptySet() + } + + override fun equals(other: Any?): Boolean { + return other is ConfigurationGraph + && other.id == this.id + && other.nodes == this.nodes + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + nodes.hashCode() + return result + } + + } + + override fun equals(other: Any?): Boolean { + return other is DependencyGraph + && other.libraries == this.libraries + && other.configurations == this.configurations + } + + override fun hashCode(): Int { + var result = configurations.hashCode() + result = 31 * result + libraries.hashCode() + return result + } + + } diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index 2c3c096..3c8d3f6 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -23,12 +23,12 @@ import org.gradle.api.artifacts.ProjectDependency internal class DependencyGraphBuilder { - fun buildFromDump(projectDump: DependencyGraphDump): List { - val graphs = mutableMapOf() + fun buildFromDump(projectDump: DependencyGraphDump): List { + val graphs = mutableMapOf() projectDump.modules.forEach { report -> report.configurations.forEach { configuration -> val graph = graphs.getOrPut(configuration.id) { - DependencyGraph(configurationId = configuration.id) + ConfigurationDependencyGraph(id = configuration.id) } configuration.dependencies.forEach { dependency -> if (dependency.isLibrary) { @@ -48,12 +48,12 @@ internal class DependencyGraphBuilder { return graphs.values.toList() } - fun buildFromProject(project: Project): List { + fun buildFromProject(project: Project): List { return project.configurations .filter { config -> config.isCanBeResolved && DependencyConfiguration.isConfigurationSupported(config.name) } .map { config -> - val graph = DependencyGraph( - configurationId = config.name, + val graph = ConfigurationDependencyGraph( + id = config.name, ) val moduleId = project.path config.incoming.dependencies diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt index 49229f0..8afc253 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt @@ -20,7 +20,7 @@ internal class DependencyRestrictionFinder { fun find( moduleId: String, - graph: DependencyGraph, + graph: ConfigurationDependencyGraph, spec: ProjectGuardSpec, ): List { return find( @@ -32,7 +32,7 @@ internal class DependencyRestrictionFinder { fun find( moduleId: String, - graphs: List, + graphs: List, spec: ProjectGuardSpec, ): List { val restrictions = mutableListOf() diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt index fc2bf29..7915e82 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt @@ -16,7 +16,7 @@ package com.rubensousa.projectguard.plugin.internal.task -import com.rubensousa.projectguard.plugin.internal.DependencyGraph +import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph import com.rubensousa.projectguard.plugin.internal.report.ConfigurationDependencies import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphModuleDump @@ -39,7 +39,7 @@ internal abstract class TaskDependencyDump : DefaultTask() { internal abstract val projectPath: Property @get:Input - internal abstract val dependencies: ListProperty + internal abstract val dependencies: ListProperty @get:OutputFile internal abstract val outputFile: RegularFileProperty @@ -59,7 +59,7 @@ internal abstract class TaskDependencyDump : DefaultTask() { internal class DependencyDumpExecutor( private val moduleId: String, private val outputFile: File, - private val dependencyGraphs: List, + private val dependencyGraphs: List, ) { private val jsonWriter = JsonFileWriter() @@ -71,7 +71,7 @@ internal class DependencyDumpExecutor( module = moduleId, configurations = dependencyGraphs.map { graph -> ConfigurationDependencies( - id = graph.configurationId, + id = graph.id, dependencies = graph.getDependencies(moduleId).toList().map { dependencyId -> DependencyReferenceDump(dependencyId, graph.isExternalLibrary(dependencyId)) } diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt index 6c72d3b..ffe16b4 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt @@ -16,7 +16,7 @@ package com.rubensousa.projectguard.plugin -import com.rubensousa.projectguard.plugin.internal.DependencyGraph +import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec import com.rubensousa.projectguard.plugin.internal.task.AggregateDependencyDumpExecutor import com.rubensousa.projectguard.plugin.internal.task.AggregateRestrictionDumpExecutor @@ -33,9 +33,9 @@ internal class DependencyPluginSimulator( fun dumpDependencies( moduleId: String, - action: DependencyGraph.() -> Unit = {}, + action: ConfigurationDependencyGraph.() -> Unit = {}, ): File { - val graph = DependencyGraph(configurationId = "compileClasspath") + val graph = ConfigurationDependencyGraph(id = "compileClasspath") graph.action() val outputFile = getDependencyFile(moduleId) val executor = DependencyDumpExecutor( diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt index 7c196aa..b9b2ad1 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt @@ -16,7 +16,7 @@ package com.rubensousa.projectguard.plugin -import com.rubensousa.projectguard.plugin.internal.DependencyGraph +import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec import org.gradle.testfixtures.ProjectBuilder @@ -30,8 +30,8 @@ internal fun projectGuard(scope: ProjectGuardScope.() -> Unit): ProjectGuardSpec return extension.getSpec() } -internal fun buildDependencyGraph(scope: DependencyGraph.() -> Unit): DependencyGraph { - val graph = DependencyGraph("implementation") +internal fun buildDependencyGraph(scope: ConfigurationDependencyGraph.() -> Unit): ConfigurationDependencyGraph { + val graph = ConfigurationDependencyGraph("implementation") graph.apply(scope) return graph } diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt index b43e7ec..35ca712 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt @@ -17,7 +17,7 @@ package com.rubensousa.projectguard.plugin import com.google.common.truth.Truth.assertThat -import com.rubensousa.projectguard.plugin.internal.DependencyGraph +import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph import com.rubensousa.projectguard.plugin.internal.report.ConfigurationDependencies import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphModuleDump @@ -36,7 +36,7 @@ class TaskDependencyDumpTest { val temporaryFolder = TemporaryFolder() private val inputModule = "module" - private val dependencies = mutableListOf() + private val dependencies = mutableListOf() private lateinit var outputFile: File private lateinit var executor: DependencyDumpExecutor @@ -56,15 +56,15 @@ class TaskDependencyDumpTest { val firstDependency = "domain:a" val secondDependency = "domain:b" dependencies.add( - DependencyGraph( - configurationId = "implementation", + ConfigurationDependencyGraph( + id = "implementation", ).apply { addInternalDependency(inputModule, firstDependency) }, ) dependencies.add( - DependencyGraph( - configurationId = "testImplementation", + ConfigurationDependencyGraph( + id = "testImplementation", ).apply { addInternalDependency(inputModule, secondDependency) }, diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index c2b80d3..c4416b0 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -78,12 +78,12 @@ class DependencyGraphBuilderTest { ) } - private fun List.findCompilationGraph(): DependencyGraph? { - return this.find { it.configurationId == "compileClasspath" } + private fun List.findCompilationGraph(): ConfigurationDependencyGraph? { + return this.find { it.id == "compileClasspath" } } - private fun List.findTestGraph(): DependencyGraph? { - return this.find { it.configurationId == "testCompileClasspath" } + private fun List.findTestGraph(): ConfigurationDependencyGraph? { + return this.find { it.id == "testCompileClasspath" } } private fun Project.addLegacyDependency(dependency: String): Project { diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt index 00e1cea..1e590e2 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt @@ -17,13 +17,15 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream import kotlin.test.Test class DependencyGraphTest { - private val graph = DependencyGraph( - configurationId = "implementation" - ) + private val graph = DependencyGraph() @Test fun `transitive dependencies are returned`() { @@ -45,7 +47,7 @@ class DependencyGraphTest { graph.addInternalDependency(module = transitiveDependencyC, dependency = transitiveDependencyE) // when - val dependencies = graph.getAllDependencies(consumer).map { it.id } + val dependencies = graph.getDependencies(consumer).map { it.id } // then assertThat(dependencies).containsExactly( @@ -74,7 +76,7 @@ class DependencyGraphTest { graph.addInternalDependency(module = transitiveDependencyC, dependency = transitiveDependencyD) // when - val dependencies = graph.getAllDependencies(consumer) + val dependencies = graph.getDependencies(consumer) // then val dependency = dependencies.find { it.id == transitiveDependencyB }!! as TransitiveDependency @@ -101,7 +103,7 @@ class DependencyGraphTest { graph.addInternalDependency(module = directDependencyA, dependency = transitiveDependencyD) // when - val dependencies = graph.getAllDependencies(consumer) + val dependencies = graph.getDependencies(consumer) // then val dependency = dependencies.find { it.id == transitiveDependencyD }!! as TransitiveDependency @@ -109,4 +111,80 @@ class DependencyGraphTest { .isEqualTo(listOf(directDependencyA, transitiveDependencyD)) } + @Test + fun `implementation of a test dependency is considered a transitive dependency`() { + // given + val consumer = "consumer" + val consumerDependency = "dependencyA" + val dependencyOfConsumerDependency = "dependencyB" + graph.addInternalDependency( + configurationId = DependencyConfiguration.TEST, + module = consumer, + dependency = consumerDependency + ) + graph.addInternalDependency( + configurationId = DependencyConfiguration.COMPILE, + module = consumerDependency, + dependency = dependencyOfConsumerDependency + ) + + // when + val dependencies = graph.getDependencies(consumer) + + // then + assertThat(dependencies).isEqualTo( + listOf( + DirectDependency(consumerDependency), + TransitiveDependency(dependencyOfConsumerDependency, listOf(consumerDependency, dependencyOfConsumerDependency)) + ) + ) + } + + @Test + fun `test implementation of a test dependency is not considered a transitive dependency`() { + // given + val consumer = "consumer" + val consumerDependency = "dependencyA" + val dependencyOfConsumerDependency = "dependencyB" + graph.addInternalDependency( + configurationId = DependencyConfiguration.TEST, + module = consumer, + dependency = consumerDependency + ) + graph.addInternalDependency( + configurationId = DependencyConfiguration.TEST, + module = consumerDependency, + dependency = dependencyOfConsumerDependency + ) + + // when + val dependencies = graph.getDependencies(consumer) + + // then + assertThat(dependencies).isEqualTo( + listOf( + DirectDependency(consumerDependency), + ) + ) + } + + @Test + fun `graph can be deserialized`() { + // given + graph.addInternalDependency( + module = "consumer", + dependency = "dependencyA" + ) + + // when + val byteArray = ByteArrayOutputStream() + ObjectOutputStream(byteArray).use { stream -> stream.writeObject(graph) } + + // then + val deserializedGraph = ObjectInputStream(ByteArrayInputStream(byteArray.toByteArray())).use { stream -> + stream.readObject() + } as DependencyGraph + assertThat(graph).isEqualTo(deserializedGraph) + } + } diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt index cc2c9a2..112b1ae 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt @@ -23,8 +23,8 @@ import kotlin.test.Test class DependencyRestrictionTest { - private val graph = DependencyGraph( - configurationId = "implementation" + private val graph = ConfigurationDependencyGraph( + id = "implementation" ) private val finder = DependencyRestrictionFinder() diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt index 219120a..848b62d 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt @@ -23,8 +23,8 @@ import kotlin.test.Test class GuardRestrictionTest { - private val graph = DependencyGraph( - configurationId = "implementation" + private val graph = ConfigurationDependencyGraph( + id = "implementation" ) private val finder = DependencyRestrictionFinder() diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt index 721ca46..ebdabd3 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt @@ -23,7 +23,7 @@ import kotlin.test.Test class ModuleRestrictionTest { - private val graph = DependencyGraph(configurationId = "implementation") + private val graph = ConfigurationDependencyGraph(id = "implementation") private val finder = DependencyRestrictionFinder() @Test From 1a2f668ef305ba47a7343113cddfdfe73caf8d41 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 19 Feb 2026 01:16:44 +0100 Subject: [PATCH 2/6] Use new graph implementation everywhere --- .../projectguard/plugin/ProjectGuardPlugin.kt | 2 +- .../plugin/internal/DependencyGraph.kt | 32 ++++++++------ .../plugin/internal/DependencyGraphBuilder.kt | 23 +++++----- .../internal/DependencyRestrictionFinder.kt | 33 +++++--------- .../internal/task/TaskDependencyDump.kt | 17 ++++--- .../internal/task/TaskRestrictionDump.kt | 7 ++- .../plugin/DependencyPluginSimulator.kt | 8 ++-- .../plugin/ProjectGuardFixtures.kt | 7 --- .../plugin/TaskDependencyDumpTest.kt | 31 ++++++------- .../internal/DependencyGraphBuilderTest.kt | 28 +++++------- .../internal/DependencyRestrictionTest.kt | 28 ++++-------- .../plugin/internal/GuardRestrictionTest.kt | 43 ++++-------------- .../plugin/internal/ModuleRestrictionTest.kt | 44 +++++++------------ 13 files changed, 113 insertions(+), 190 deletions(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt index 780aaea..1eed2c9 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardPlugin.kt @@ -290,7 +290,7 @@ class ProjectGuardPlugin : Plugin { group = "other" description = "Generates a JSON containing the dependencies of this module." projectPath.set(project.path) - dependencies.set(graphBuilder.buildFromProject(project)) + dependencyGraph.set(graphBuilder.buildFromProject(project)) outputFile.set( project.layout.buildDirectory.file(dependenciesFilePath) ) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt index 50878c3..ba652cf 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt @@ -20,9 +20,11 @@ import java.io.Serializable internal class DependencyGraph : Serializable { - private val configurations = mutableMapOf() + private val configurations = mutableMapOf() private val libraries = mutableSetOf() + fun getConfigurations() = configurations.values.toList() + fun addInternalDependency( module: String, dependency: String, @@ -48,15 +50,8 @@ internal class DependencyGraph : Serializable { libraries.add(dependency) } - private fun addDependency( - module: String, - dependency: String, - configurationId: String, - ) { - val configuration = configurations.getOrPut(configurationId) { - ConfigurationGraph(configurationId) - } - configuration.add(module = module, dependency = dependency) + fun isExternalLibrary(dependency: String): Boolean { + return libraries.contains(dependency) } fun getDependencies(module: String): List { @@ -111,7 +106,18 @@ internal class DependencyGraph : Serializable { } } } - return paths.values.toList() + return paths.values.sortedBy { it.id } + } + + private fun addDependency( + module: String, + dependency: String, + configurationId: String, + ) { + val configuration = configurations.getOrPut(configurationId) { + Configuration(configurationId) + } + configuration.add(module = module, dependency = dependency) } private data class TraversalState( @@ -121,7 +127,7 @@ internal class DependencyGraph : Serializable { val path: List, ) - private class ConfigurationGraph(val id: String) : Serializable { + class Configuration(val id: String) : Serializable { private val nodes = mutableMapOf>() @@ -135,7 +141,7 @@ internal class DependencyGraph : Serializable { } override fun equals(other: Any?): Boolean { - return other is ConfigurationGraph + return other is Configuration && other.id == this.id && other.nodes == this.nodes } diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index 3c8d3f6..ed3cb5c 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -23,21 +23,20 @@ import org.gradle.api.artifacts.ProjectDependency internal class DependencyGraphBuilder { - fun buildFromDump(projectDump: DependencyGraphDump): List { - val graphs = mutableMapOf() + fun buildFromDump(projectDump: DependencyGraphDump): DependencyGraph { + val graph = DependencyGraph() projectDump.modules.forEach { report -> report.configurations.forEach { configuration -> - val graph = graphs.getOrPut(configuration.id) { - ConfigurationDependencyGraph(id = configuration.id) - } configuration.dependencies.forEach { dependency -> if (dependency.isLibrary) { graph.addExternalDependency( + configurationId = configuration.id, module = report.module, dependency = dependency.id, ) } else { graph.addInternalDependency( + configurationId = configuration.id, module = report.module, dependency = dependency.id, ) @@ -45,16 +44,14 @@ internal class DependencyGraphBuilder { } } } - return graphs.values.toList() + return graph } - fun buildFromProject(project: Project): List { - return project.configurations + fun buildFromProject(project: Project): DependencyGraph { + val graph = DependencyGraph() + project.configurations .filter { config -> config.isCanBeResolved && DependencyConfiguration.isConfigurationSupported(config.name) } - .map { config -> - val graph = ConfigurationDependencyGraph( - id = config.name, - ) + .forEach { config -> val moduleId = project.path config.incoming.dependencies .forEach { dependency -> @@ -76,7 +73,7 @@ internal class DependencyGraphBuilder { } } } - graph } + return graph } } \ No newline at end of file diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt index 8afc253..72bafe3 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionFinder.kt @@ -16,35 +16,22 @@ package com.rubensousa.projectguard.plugin.internal -internal class DependencyRestrictionFinder { +internal class DependencyRestrictionFinder( + private val graph: DependencyGraph, +) { fun find( moduleId: String, - graph: ConfigurationDependencyGraph, - spec: ProjectGuardSpec, - ): List { - return find( - moduleId = moduleId, - graphs = listOf(graph), - spec = spec - ) - } - - fun find( - moduleId: String, - graphs: List, spec: ProjectGuardSpec, ): List { val restrictions = mutableListOf() - graphs.forEach { graph -> - graph.getAllDependencies(moduleId).forEach { dependency -> - fillRestrictions( - restrictions = restrictions, - moduleId = moduleId, - dependency = dependency, - spec = spec, - ) - } + graph.getDependencies(moduleId).forEach { dependency -> + fillRestrictions( + restrictions = restrictions, + moduleId = moduleId, + dependency = dependency, + spec = spec, + ) } // We might find multiple restrictions to the same dependency, just filter them out return filterRestrictions(moduleId, restrictions) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt index 7915e82..3072a5f 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskDependencyDump.kt @@ -16,7 +16,7 @@ package com.rubensousa.projectguard.plugin.internal.task -import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph +import com.rubensousa.projectguard.plugin.internal.DependencyGraph import com.rubensousa.projectguard.plugin.internal.report.ConfigurationDependencies import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphModuleDump @@ -24,7 +24,6 @@ import com.rubensousa.projectguard.plugin.internal.report.DependencyReferenceDum import com.rubensousa.projectguard.plugin.internal.report.JsonFileWriter import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputFile @@ -39,7 +38,7 @@ internal abstract class TaskDependencyDump : DefaultTask() { internal abstract val projectPath: Property @get:Input - internal abstract val dependencies: ListProperty + internal abstract val dependencyGraph: Property @get:OutputFile internal abstract val outputFile: RegularFileProperty @@ -49,7 +48,7 @@ internal abstract class TaskDependencyDump : DefaultTask() { val executor = DependencyDumpExecutor( moduleId = projectPath.get(), outputFile = outputFile.get().asFile, - dependencyGraphs = dependencies.get() + dependencyGraph = dependencyGraph.get() ) executor.execute() } @@ -59,7 +58,7 @@ internal abstract class TaskDependencyDump : DefaultTask() { internal class DependencyDumpExecutor( private val moduleId: String, private val outputFile: File, - private val dependencyGraphs: List, + private val dependencyGraph: DependencyGraph, ) { private val jsonWriter = JsonFileWriter() @@ -69,11 +68,11 @@ internal class DependencyDumpExecutor( modules = listOf( DependencyGraphModuleDump( module = moduleId, - configurations = dependencyGraphs.map { graph -> + configurations = dependencyGraph.getConfigurations().map { configuration -> ConfigurationDependencies( - id = graph.id, - dependencies = graph.getDependencies(moduleId).toList().map { dependencyId -> - DependencyReferenceDump(dependencyId, graph.isExternalLibrary(dependencyId)) + id = configuration.id, + dependencies = configuration.getDependencies(moduleId).toList().map { dependencyId -> + DependencyReferenceDump(dependencyId, dependencyGraph.isExternalLibrary(dependencyId)) } ) } diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt index cfa3e4e..01a14ec 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskRestrictionDump.kt @@ -16,11 +16,11 @@ package com.rubensousa.projectguard.plugin.internal.task +import com.rubensousa.projectguard.plugin.internal.DependencyGraphBuilder import com.rubensousa.projectguard.plugin.internal.DependencyRestrictionFinder import com.rubensousa.projectguard.plugin.internal.DirectDependencyRestriction import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec import com.rubensousa.projectguard.plugin.internal.TransitiveDependencyRestriction -import com.rubensousa.projectguard.plugin.internal.DependencyGraphBuilder import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import com.rubensousa.projectguard.plugin.internal.report.JsonFileWriter import com.rubensousa.projectguard.plugin.internal.report.RestrictionDependencyReport @@ -74,11 +74,10 @@ internal class RestrictionDumpExecutor( fun execute() { val dependencyGraphDump = Json.decodeFromString(dependenciesFile.readText()) val graphBuilder = DependencyGraphBuilder() - val graphs = graphBuilder.buildFromDump(dependencyGraphDump) - val restrictionFinder = DependencyRestrictionFinder() + val graph = graphBuilder.buildFromDump(dependencyGraphDump) + val restrictionFinder = DependencyRestrictionFinder(graph) val restrictions = restrictionFinder.find( moduleId = moduleId, - graphs = graphs, spec = spec ) val module = RestrictionModuleReport( diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt index ffe16b4..05fa95e 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/DependencyPluginSimulator.kt @@ -16,7 +16,7 @@ package com.rubensousa.projectguard.plugin -import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph +import com.rubensousa.projectguard.plugin.internal.DependencyGraph import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec import com.rubensousa.projectguard.plugin.internal.task.AggregateDependencyDumpExecutor import com.rubensousa.projectguard.plugin.internal.task.AggregateRestrictionDumpExecutor @@ -33,15 +33,15 @@ internal class DependencyPluginSimulator( fun dumpDependencies( moduleId: String, - action: ConfigurationDependencyGraph.() -> Unit = {}, + action: DependencyGraph.() -> Unit = {}, ): File { - val graph = ConfigurationDependencyGraph(id = "compileClasspath") + val graph = DependencyGraph() graph.action() val outputFile = getDependencyFile(moduleId) val executor = DependencyDumpExecutor( moduleId = moduleId, outputFile = outputFile, - dependencyGraphs = listOf(graph), + dependencyGraph = graph, ) executor.execute() return outputFile diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt index b9b2ad1..5bb7945 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/ProjectGuardFixtures.kt @@ -16,7 +16,6 @@ package com.rubensousa.projectguard.plugin -import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec import org.gradle.testfixtures.ProjectBuilder @@ -29,9 +28,3 @@ internal fun projectGuard(scope: ProjectGuardScope.() -> Unit): ProjectGuardSpec extension.scope() return extension.getSpec() } - -internal fun buildDependencyGraph(scope: ConfigurationDependencyGraph.() -> Unit): ConfigurationDependencyGraph { - val graph = ConfigurationDependencyGraph("implementation") - graph.apply(scope) - return graph -} diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt index 35ca712..cdad1fa 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskDependencyDumpTest.kt @@ -17,7 +17,8 @@ package com.rubensousa.projectguard.plugin import com.google.common.truth.Truth.assertThat -import com.rubensousa.projectguard.plugin.internal.ConfigurationDependencyGraph +import com.rubensousa.projectguard.plugin.internal.DependencyConfiguration +import com.rubensousa.projectguard.plugin.internal.DependencyGraph import com.rubensousa.projectguard.plugin.internal.report.ConfigurationDependencies import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphModuleDump @@ -36,7 +37,7 @@ class TaskDependencyDumpTest { val temporaryFolder = TemporaryFolder() private val inputModule = "module" - private val dependencies = mutableListOf() + private val graph = DependencyGraph() private lateinit var outputFile: File private lateinit var executor: DependencyDumpExecutor @@ -46,7 +47,7 @@ class TaskDependencyDumpTest { executor = DependencyDumpExecutor( moduleId = inputModule, outputFile = outputFile, - dependencyGraphs = dependencies, + dependencyGraph = graph ) } @@ -55,19 +56,15 @@ class TaskDependencyDumpTest { // given val firstDependency = "domain:a" val secondDependency = "domain:b" - dependencies.add( - ConfigurationDependencyGraph( - id = "implementation", - ).apply { - addInternalDependency(inputModule, firstDependency) - }, + graph.addInternalDependency( + module = inputModule, + dependency = firstDependency, + configurationId = DependencyConfiguration.COMPILE ) - dependencies.add( - ConfigurationDependencyGraph( - id = "testImplementation", - ).apply { - addInternalDependency(inputModule, secondDependency) - }, + graph.addInternalDependency( + module = inputModule, + dependency = secondDependency, + configurationId = DependencyConfiguration.TEST ) // when @@ -81,11 +78,11 @@ class TaskDependencyDumpTest { module = inputModule, configurations = listOf( ConfigurationDependencies( - id = "implementation", + id = DependencyConfiguration.COMPILE, dependencies = listOf(DependencyReferenceDump(firstDependency, false)) ), ConfigurationDependencies( - id = "testImplementation", + id = DependencyConfiguration.TEST, dependencies = listOf(DependencyReferenceDump(secondDependency, false)) ) ) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index c4416b0..0ea9c20 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -53,12 +53,14 @@ class DependencyGraphBuilderTest { val legacyProjectB = consumerProject.addLegacyDependency("b") // when - val graphs = graphBuilder.buildFromProject(consumerProject) + val graph = graphBuilder.buildFromProject(consumerProject) // then - val compileGraph = graphs.findCompilationGraph()!! - assertThat(compileGraph.getDependencies(consumerProject.path)).isEqualTo( - setOf(legacyProjectA.path, legacyProjectB.path) + assertThat(graph.getDependencies(consumerProject.path)).isEqualTo( + listOf( + DirectDependency(legacyProjectA.path), + DirectDependency(legacyProjectB.path) + ) ) } @@ -69,23 +71,17 @@ class DependencyGraphBuilderTest { val legacyProjectC = consumerProject.addLegacyTestDependency("c") // when - val graphs = graphBuilder.buildFromProject(consumerProject) + val graph = graphBuilder.buildFromProject(consumerProject) // then - val testGraph = graphs.findTestGraph()!! - assertThat(testGraph.getDependencies(consumerProject.path)).isEqualTo( - setOf(legacyProjectA.path, legacyProjectC.path) + assertThat(graph.getDependencies(consumerProject.path)).isEqualTo( + listOf( + DirectDependency(legacyProjectA.path), + DirectDependency(legacyProjectC.path) + ) ) } - private fun List.findCompilationGraph(): ConfigurationDependencyGraph? { - return this.find { it.id == "compileClasspath" } - } - - private fun List.findTestGraph(): ConfigurationDependencyGraph? { - return this.find { it.id == "testCompileClasspath" } - } - private fun Project.addLegacyDependency(dependency: String): Project { val legacyProject = createLegacySubProject(dependency) dependencies.add("implementation", legacyProject) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt index 112b1ae..387eaff 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyRestrictionTest.kt @@ -17,16 +17,13 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat -import com.rubensousa.projectguard.plugin.buildDependencyGraph import com.rubensousa.projectguard.plugin.projectGuard import kotlin.test.Test class DependencyRestrictionTest { - private val graph = ConfigurationDependencyGraph( - id = "implementation" - ) - private val finder = DependencyRestrictionFinder() + private val graph = DependencyGraph() + private val finder = DependencyRestrictionFinder(graph) @Test fun `there is a restriction for a direct match`() { @@ -39,7 +36,6 @@ class DependencyRestrictionTest { // when val restrictions = finder.find( moduleId = ":domain", - graph = graph, spec = spec ) @@ -58,7 +54,6 @@ class DependencyRestrictionTest { // when val restrictions = finder.find( moduleId = ":domain:a", - graph = graph, spec = spec ) @@ -73,15 +68,13 @@ class DependencyRestrictionTest { restrictDependency(":legacy") restrictDependency(":deprecated") } - val graph = buildDependencyGraph { - addInternalDependency(":domain", ":legacy") - addInternalDependency(":domain", ":deprecated") - } + + graph.addInternalDependency(":domain", ":legacy") + graph.addInternalDependency(":domain", ":deprecated") // when val restrictions = finder.find( moduleId = ":domain", - graph = graph, spec = spec ) @@ -104,7 +97,6 @@ class DependencyRestrictionTest { // when val restrictions = finder.find( moduleId = ":data:a", - graph = graph, spec = spec ) @@ -135,7 +127,6 @@ class DependencyRestrictionTest { // when val restrictions = finder.find( moduleId = ":domain:a", - graph = graph, spec = spec ) @@ -166,10 +157,10 @@ class DependencyRestrictionTest { graph.addInternalDependency(":legacy:d", ":legacy:e") // when - val legacyARestrictions = finder.find(moduleId = ":legacy:a", graph = graph, spec = spec) - val legacyBRestrictions = finder.find(moduleId = ":legacy:b", graph = graph, spec = spec) - val legacyCRestrictions = finder.find(moduleId = ":legacy:c", graph = graph, spec = spec) - val legacyDRestrictions = finder.find(moduleId = ":legacy:d", graph = graph, spec = spec) + val legacyARestrictions = finder.find(moduleId = ":legacy:a", spec = spec) + val legacyBRestrictions = finder.find(moduleId = ":legacy:b", spec = spec) + val legacyCRestrictions = finder.find(moduleId = ":legacy:c", spec = spec) + val legacyDRestrictions = finder.find(moduleId = ":legacy:d", spec = spec) // then @@ -211,7 +202,6 @@ class DependencyRestrictionTest { // when val restrictions = finder.find( moduleId = ":domain:a", - graph = graph, spec = spec ) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt index 848b62d..8fd3dfc 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/GuardRestrictionTest.kt @@ -17,16 +17,13 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat -import com.rubensousa.projectguard.plugin.buildDependencyGraph import com.rubensousa.projectguard.plugin.projectGuard import kotlin.test.Test class GuardRestrictionTest { - private val graph = ConfigurationDependencyGraph( - id = "implementation" - ) - private val finder = DependencyRestrictionFinder() + private val graph = DependencyGraph() + private val finder = DependencyRestrictionFinder(graph) @Test fun `module is restricted to concrete child but not its sibling`() { @@ -44,7 +41,6 @@ class GuardRestrictionTest { // when val restrictions = finder.find( moduleId = ":domain:a", - graph = graph, spec = spec ) @@ -67,7 +63,6 @@ class GuardRestrictionTest { // when val restrictions = finder.find( moduleId = ":domain:a", - graph = graph, spec = spec ) @@ -89,11 +84,7 @@ class GuardRestrictionTest { } // when - val restrictions = finder.find( - moduleId = ":another", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":another", spec = spec) // then assertThat(restrictions).isEmpty() @@ -112,11 +103,7 @@ class GuardRestrictionTest { } // when - val restrictions = finder.find( - moduleId = ":domain", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain", spec = spec) // then assertThat(restrictions).containsExactly(DirectDependencyRestriction(":legacy")) @@ -133,11 +120,7 @@ class GuardRestrictionTest { graph.addInternalDependency(":domain:a", ":legacy:a") // when - val restrictions = finder.find( - moduleId = ":domain:a", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly(DirectDependencyRestriction(":legacy:a")) @@ -147,13 +130,11 @@ class GuardRestrictionTest { fun `there is no restriction by default`() { // given val spec = projectGuard {} + graph.addInternalDependency(":domain", ":legacy") // when val restrictions = finder.find( moduleId = ":domain", - graph = buildDependencyGraph { - addInternalDependency(":domain", ":legacy") - }, spec = spec ) @@ -178,11 +159,7 @@ class GuardRestrictionTest { } // when - val restrictions = finder.find( - moduleId = ":domain:a", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly( @@ -206,11 +183,7 @@ class GuardRestrictionTest { // when - val restrictions = finder.find( - moduleId = ":domain:a", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly(DirectDependencyRestriction(":legacy:a", reason)) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt index ebdabd3..4d5bd96 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/ModuleRestrictionTest.kt @@ -17,14 +17,13 @@ package com.rubensousa.projectguard.plugin.internal import com.google.common.truth.Truth.assertThat -import com.rubensousa.projectguard.plugin.buildDependencyGraph import com.rubensousa.projectguard.plugin.projectGuard import kotlin.test.Test class ModuleRestrictionTest { - private val graph = ConfigurationDependencyGraph(id = "implementation") - private val finder = DependencyRestrictionFinder() + private val graph = DependencyGraph() + private val finder = DependencyRestrictionFinder(graph) @Test fun `restrictModule denies all by default`() { @@ -37,11 +36,7 @@ class ModuleRestrictionTest { // when - val restrictions = finder.find( - moduleId = ":domain", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain", spec = spec) // then assertThat(restrictions).containsExactly( @@ -59,11 +54,7 @@ class ModuleRestrictionTest { graph.addInternalDependency(":domain:a", ":legacy:a") // when - val restrictions = finder.find( - moduleId = ":domain:a", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly(DirectDependencyRestriction(":legacy:a")) @@ -76,14 +67,13 @@ class ModuleRestrictionTest { restrictModule(":domain") restrictModule(":data") } - val graph = buildDependencyGraph { - addInternalDependency(":domain", ":legacy") - addInternalDependency(":data", ":legacy") - } + + graph.addInternalDependency(":domain", ":legacy") + graph.addInternalDependency(":data", ":legacy") // when - val domainRestrictions = finder.find(moduleId = ":domain", graph = graph, spec = spec) - val dataRestrictions = finder.find(moduleId = ":data", graph = graph, spec = spec) + val domainRestrictions = finder.find(moduleId = ":domain", spec = spec) + val dataRestrictions = finder.find(moduleId = ":data", spec = spec) // then assertThat(domainRestrictions).containsExactly(DirectDependencyRestriction(":legacy")) @@ -102,7 +92,7 @@ class ModuleRestrictionTest { graph.addInternalDependency(":domain:b", ":legacy:a") // when - val restrictions = finder.find(moduleId = ":domain:a", graph = graph, spec = spec) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly( @@ -128,7 +118,7 @@ class ModuleRestrictionTest { graph.addInternalDependency(":domain:a", ":legacy:a") // when - val restrictions = finder.find(moduleId = ":domain:a", graph = graph, spec = spec) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly( @@ -156,9 +146,9 @@ class ModuleRestrictionTest { graph.addInternalDependency(":domain:d", ":domain:c") // when - val restrictionsB = finder.find(moduleId = ":domain:b", graph = graph, spec = spec) - val restrictionsC = finder.find(moduleId = ":domain:c", graph = graph, spec = spec) - val restrictionsD = finder.find(moduleId = ":domain:d", graph = graph, spec = spec) + val restrictionsB = finder.find(moduleId = ":domain:b", spec = spec) + val restrictionsC = finder.find(moduleId = ":domain:c", spec = spec) + val restrictionsD = finder.find(moduleId = ":domain:d", spec = spec) // then @@ -181,11 +171,7 @@ class ModuleRestrictionTest { graph.addInternalDependency(":domain:a", ":legacy:a") // when - val restrictions = finder.find( - moduleId = ":domain:a", - graph = graph, - spec = spec - ) + val restrictions = finder.find(moduleId = ":domain:a", spec = spec) // then assertThat(restrictions).containsExactly(DirectDependencyRestriction(":legacy:a")) From a647c7c6c0679f20409847dcb308d7b875759a6c Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 19 Feb 2026 01:21:11 +0100 Subject: [PATCH 3/6] Print description of fatal matches in verification exception --- .../projectguard/plugin/internal/task/TaskCheck.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskCheck.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskCheck.kt index 6b58a3a..0a8b35d 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskCheck.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskCheck.kt @@ -98,7 +98,10 @@ internal class CheckExecutor( val htmlReportGenerator = HtmlReportGenerator() htmlReportGenerator.generate(report, reportDir) if (fatalMatches.isNotEmpty()) { - throw VerificationException("Found ${fatalMatches.size} fatal match(es). See report at file:///$reportFilePath") + throw VerificationException( + "${fatalMatches.take(10).joinToString("\n\n") { it.getDescription() }} \n " + + "Found ${fatalMatches.size} fatal match(es). See full report at file:///$reportFilePath" + ) } else { println("No fatal matches found. See report at file:///$reportFilePath") } From 7629c0f5cf9f64ffa919c473e17533380a485a70 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 19 Feb 2026 01:39:48 +0100 Subject: [PATCH 4/6] Do not count test implementations of transitive dependencies --- .../internal/ConfigurationDependencyGraph.kt | 102 ------------------ .../internal/DependencyConfiguration.kt | 4 + .../plugin/internal/DependencyGraph.kt | 30 ++---- .../plugin/internal/DependencyGraphTest.kt | 28 +++++ sample/legacy/a/build.gradle.kts | 1 + 5 files changed, 44 insertions(+), 121 deletions(-) delete mode 100644 projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt deleted file mode 100644 index 8f3a68e..0000000 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/ConfigurationDependencyGraph.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2026 Rúben Sousa - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.rubensousa.projectguard.plugin.internal - -import java.io.Serializable - -internal class ConfigurationDependencyGraph( - val id: String, - val nodes: MutableMap> = mutableMapOf(), -) : Serializable { - - private val libraries = mutableSetOf() - - fun addInternalDependency(module: String, dependency: String) { - addDependency(module, dependency) - } - - fun addExternalDependency(module: String, dependency: String) { - addDependency(module, dependency) - libraries.add(dependency) - } - - private fun addDependency(module: String, dependency: String) { - val existingDependencies = nodes.getOrPut(module) { mutableSetOf() } - existingDependencies.add(dependency) - } - - fun isExternalLibrary(dependency: String): Boolean { - return libraries.contains(dependency) - } - - fun getDependencies(module: String): Set { - return nodes[module] ?: emptySet() - } - - fun getAllDependencies(module: String): List { - /** - * Until https://github.com/rubensousa/ProjectGuard/issues/3 is resolved, - * exclude transitive dependency traversals for test configurations - */ - if (id.contains("test")) { - return getDependencies(module).map { - DirectDependency(it) - } - } - val visitedDependencies = mutableSetOf() - val paths = mutableMapOf() - val stack = ArrayDeque() - stack.addAll(getDependencies(module).map { dependency -> - TraversalState(dependency, emptyList()) - }) - while (stack.isNotEmpty()) { - val currentModule = stack.removeFirst() - val currentDependency = currentModule.dependency - if (visitedDependencies.contains(currentDependency)) { - continue - } - paths[currentDependency] = if (currentModule.path.isEmpty()) { - DirectDependency(currentDependency) - } else { - TransitiveDependency( - currentDependency, - currentModule.path + currentDependency - ) - } - visitedDependencies.add(currentDependency) - getDependencies(currentDependency).forEach { nextDependency -> - stack.addFirst( - TraversalState( - nextDependency, - currentModule.path + currentDependency - ) - ) - } - } - return paths.values.toList() - } - - override fun toString(): String { - return "ConfigurationDependencyGraph(configurationId='$id', nodes=$nodes)" - } - - private data class TraversalState( - val dependency: String, - val path: List, - ) - -} diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt index 741deab..6aa9b46 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyConfiguration.kt @@ -42,4 +42,8 @@ internal object DependencyConfiguration { && !lowerCaseConfiguration.contains("metadata") } + fun isTestConfiguration(configurationId: String): Boolean { + return configurationId.lowercase().contains("test") + } + } diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt index ba652cf..7f1e3a3 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraph.kt @@ -64,7 +64,6 @@ internal class DependencyGraph : Serializable { TraversalState( configurationId = configuration.id, dependency = dependency, - isFromTestDependency = !DependencyConfiguration.isReleaseConfiguration(configuration.id), path = emptyList() ) ) @@ -85,24 +84,18 @@ internal class DependencyGraph : Serializable { ) } visitedDependencies.add(currentDependency) - val targetConfigurations = if (currentTraversal.isFromTestDependency) { - // We've gone through a test dependency, search only for release configurations - configurations.values.filter { configuration -> - DependencyConfiguration.isReleaseConfiguration(configuration.id) - } - } else { - configurations.values - } - targetConfigurations.forEach { configuration -> - configuration.getDependencies(currentDependency).forEach { nextDependency -> - queue.addFirst( - TraversalState( - configurationId = configuration.id, - isFromTestDependency = !DependencyConfiguration.isReleaseConfiguration(configuration.id), - dependency = nextDependency, - path = currentTraversal.path + currentDependency + configurations.values.forEach { configuration -> + // Search only for non-test configurations as they're not considered transitive at this point + if (!DependencyConfiguration.isTestConfiguration(configuration.id)) { + configuration.getDependencies(currentDependency).forEach { nextDependency -> + queue.addFirst( + TraversalState( + configurationId = configuration.id, + dependency = nextDependency, + path = currentTraversal.path + currentDependency + ) ) - ) + } } } } @@ -122,7 +115,6 @@ internal class DependencyGraph : Serializable { private data class TraversalState( val configurationId: String, - val isFromTestDependency: Boolean, val dependency: String, val path: List, ) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt index 1e590e2..056ab71 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphTest.kt @@ -168,6 +168,34 @@ class DependencyGraphTest { ) } + @Test + fun `test implementation of a direct dependency is not considered a transitive dependency`() { + // given + val consumer = "consumer" + val consumerDependency = "dependencyA" + val dependencyOfConsumerDependency = "dependencyB" + graph.addInternalDependency( + configurationId = DependencyConfiguration.COMPILE, + module = consumer, + dependency = consumerDependency + ) + graph.addInternalDependency( + configurationId = DependencyConfiguration.TEST, + module = consumerDependency, + dependency = dependencyOfConsumerDependency + ) + + // when + val dependencies = graph.getDependencies(consumer) + + // then + assertThat(dependencies).isEqualTo( + listOf( + DirectDependency(consumerDependency), + ) + ) + } + @Test fun `graph can be deserialized`() { // given diff --git a/sample/legacy/a/build.gradle.kts b/sample/legacy/a/build.gradle.kts index 3b43358..b93bfcb 100644 --- a/sample/legacy/a/build.gradle.kts +++ b/sample/legacy/a/build.gradle.kts @@ -4,4 +4,5 @@ plugins { dependencies { implementation(project(":legacy:b")) + testImplementation(libs.mockk) } \ No newline at end of file From d1a25c4e43db50965c2c62fbce3697d989523d8f Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 19 Feb 2026 01:44:30 +0100 Subject: [PATCH 5/6] Fix configuration id not being used to initialize default graph --- .../projectguard/plugin/internal/DependencyGraphBuilder.kt | 4 +++- .../plugin/internal/DependencyGraphBuilderTest.kt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt index ed3cb5c..22ca7d2 100644 --- a/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt +++ b/projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilder.kt @@ -60,7 +60,8 @@ internal class DependencyGraphBuilder { if (dependency.path != moduleId) { graph.addInternalDependency( module = moduleId, - dependency = dependency.path + dependency = dependency.path, + configurationId = config.name ) } } @@ -69,6 +70,7 @@ internal class DependencyGraphBuilder { graph.addExternalDependency( module = moduleId, dependency = "${dependency.group}:${dependency.name}", + configurationId = config.name ) } } diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index 0ea9c20..62474c2 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -56,6 +56,7 @@ class DependencyGraphBuilderTest { val graph = graphBuilder.buildFromProject(consumerProject) // then + assertThat(graph.getConfigurations()).hasSize(1) assertThat(graph.getDependencies(consumerProject.path)).isEqualTo( listOf( DirectDependency(legacyProjectA.path), @@ -74,6 +75,7 @@ class DependencyGraphBuilderTest { val graph = graphBuilder.buildFromProject(consumerProject) // then + assertThat(graph.getConfigurations()).hasSize(2) assertThat(graph.getDependencies(consumerProject.path)).isEqualTo( listOf( DirectDependency(legacyProjectA.path), From a3355c194bfd48f0e3d9b5e889bd6d326213d94a Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 19 Feb 2026 01:55:46 +0100 Subject: [PATCH 6/6] Ensure tests search for the correct configuration --- .../internal/DependencyGraphBuilderTest.kt | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt index 62474c2..6933cc9 100644 --- a/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt +++ b/projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/DependencyGraphBuilderTest.kt @@ -56,13 +56,9 @@ class DependencyGraphBuilderTest { val graph = graphBuilder.buildFromProject(consumerProject) // then - assertThat(graph.getConfigurations()).hasSize(1) - assertThat(graph.getDependencies(consumerProject.path)).isEqualTo( - listOf( - DirectDependency(legacyProjectA.path), - DirectDependency(legacyProjectB.path) - ) - ) + val compileConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.COMPILE }!! + assertThat(compileConfiguration.getDependencies(consumerProject.path)) + .isEqualTo(setOf(legacyProjectA.path, legacyProjectB.path)) } @Test @@ -75,13 +71,9 @@ class DependencyGraphBuilderTest { val graph = graphBuilder.buildFromProject(consumerProject) // then - assertThat(graph.getConfigurations()).hasSize(2) - assertThat(graph.getDependencies(consumerProject.path)).isEqualTo( - listOf( - DirectDependency(legacyProjectA.path), - DirectDependency(legacyProjectC.path) - ) - ) + val testConfiguration = graph.getConfigurations().find { it.id == DependencyConfiguration.TEST }!! + assertThat(testConfiguration.getDependencies(consumerProject.path)) + .isEqualTo(setOf(legacyProjectA.path, legacyProjectC.path)) } private fun Project.addLegacyDependency(dependency: String): Project {