Repository: vanniktech/gradle-dependency-graph-generator-plugin
Pull request #184 (2022-07-09 08:21:55)

Fix destination project might not be included
When filtering out projects with `ProjectGenerator.includeProject`, the process will throw a `NoSuchElementException("Collection contains no element matching the predicate.")` because the destination project has been filtered out of the root nodes.

I'm making it as a draft because I'd like to create a test for it (there might be the same issue as well for the `DependencyGraphGenerator`).
The regression was introduced in https://github.com/vanniktech/gradle-dependency-graph-generator-plugin/commit/8f7c8296b704d84dde35a0d92b22f4b5d66a5a9b.

SimonMarquis: Hi @vanniktech, I finally found some time to add a test case for that bug.
I'm not sure how to re-trigger GitHub action for the first commit though (the test case without the fix).

Patch:
diff --git a/src/main/kotlin/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGenerator.kt b/src/main/kotlin/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGenerator.kt
index 61d1898..d02299a 100644
--- a/src/main/kotlin/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGenerator.kt
+++ b/src/main/kotlin/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGenerator.kt
@@ -12,6 +12,7 @@ import guru.nidi.graphviz.model.Factory.mutGraph
 import guru.nidi.graphviz.model.Factory.mutNode
 import guru.nidi.graphviz.model.Link
 import guru.nidi.graphviz.model.MutableGraph
+import guru.nidi.graphviz.model.MutableNode
 import org.gradle.api.Project
 import org.gradle.api.artifacts.ProjectDependency
 
@@ -71,18 +72,15 @@ internal class ProjectDependencyGraphGenerator(
   }
 
   private fun addDependencies(dependencies: MutableList<ProjectDependencyContainer>, graph: MutableGraph) {
-    val rootNodes = graph.rootNodes().filter { it.links().isEmpty() }
+    val rootNodes: List<MutableNode> = graph.rootNodes().filter { it.links().isEmpty() }
     dependencies
       .filterNot { (from, to, _) -> from == to }
       .distinctBy { (from, to, _) -> from to to }
       .forEach { (from, to, isImplementation) ->
         val fromNode = rootNodes.single { it.name().toString() == from.path }
-        val toNode = rootNodes.single { it.name().toString() == to.path }
-
-        if (fromNode != null && toNode != null) {
-          val link = Link.to(toNode)
-          graph.add(fromNode.addLink(if (isImplementation) link.with(Style.DOTTED) else link))
-        }
+        val toNode = rootNodes.singleOrNull { it.name().toString() == to.path } ?: return@forEach
+        val link = Link.to(toNode)
+        graph.add(fromNode.addLink(if (isImplementation) link.with(Style.DOTTED) else link))
       }
   }
 


Test patch:
diff --git a/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorPluginTest.kt b/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorPluginTest.kt
index 08ca178..a67ecb2 100644
--- a/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorPluginTest.kt
+++ b/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorPluginTest.kt
@@ -8,6 +8,7 @@ import org.gradle.testfixtures.ProjectBuilder
 import org.gradle.testkit.runner.BuildResult
 import org.gradle.testkit.runner.GradleRunner
 import org.gradle.testkit.runner.TaskOutcome
+import org.intellij.lang.annotations.Language
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
@@ -60,6 +61,7 @@ class DependencyGraphGeneratorPluginTest {
   @Suppress("Detekt.LongMethod") private fun integrationTest(gradleVersion: String) {
     val buildFile = testProjectDir.newFile("build.gradle")
     buildFile.writeText(
+      // language=groovy
       """
         |plugins {
         |  id "java"
@@ -107,6 +109,7 @@ class DependencyGraphGeneratorPluginTest {
     val dependencyGraphDot = File(testProjectDir.root, "build/reports/dependency-graph/dependency-graph.dot")
     assertEquals(true, dependencyGraphDot.absolutePath in result.output)
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -141,6 +144,7 @@ class DependencyGraphGeneratorPluginTest {
     val projectDependencyGraphDot = File(testProjectDir.root, "build/reports/project-dependency-graph/project-dependency-graph.dot")
     assertEquals(true, projectDependencyGraphDot.absolutePath in result.output)
     assertEquals(
+      // language=dot
       """
         digraph {
         edge ["dir"="forward"]
@@ -160,6 +164,7 @@ class DependencyGraphGeneratorPluginTest {
     assertEquals(TaskOutcome.UP_TO_DATE, secondResult.task(":generateProjectDependencyGraph")?.outcome)
 
     buildFile.appendText(
+      // language=groovy
       """
       |import guru.nidi.graphviz.engine.Format
       |dependencyGraphGenerator {
@@ -185,6 +190,7 @@ class DependencyGraphGeneratorPluginTest {
 
   @Test @Suppress("Detekt.LongMethod") fun multiProjectIntegrationTest() {
     testProjectDir.newFile("build.gradle").writeText(
+      // language=groovy
       """
         |plugins {
         |  id "com.vanniktech.dependency.graph.generator"
@@ -194,6 +200,7 @@ class DependencyGraphGeneratorPluginTest {
     )
 
     testProjectDir.newFile("settings.gradle").writeText(
+      // language=groovy
       """
         |include ":lib"
         |include ":lib1"
@@ -206,6 +213,7 @@ class DependencyGraphGeneratorPluginTest {
 
     val lib = testProjectDir.newFolder("lib").run { parentFile.name + name }
     testProjectDir.newFile("lib/build.gradle").writeText(
+      // language=groovy
       """
         |plugins { id "java-library" }
         |
@@ -220,6 +228,7 @@ class DependencyGraphGeneratorPluginTest {
 
     val lib1 = testProjectDir.newFolder("lib1").run { parentFile.name + name }
     testProjectDir.newFile("lib1/build.gradle").writeText(
+      // language=groovy
       """
         |plugins { id "java-library" }
         |
@@ -235,6 +244,7 @@ class DependencyGraphGeneratorPluginTest {
 
     val lib2 = testProjectDir.newFolder("lib2").run { parentFile.name + name }
     testProjectDir.newFile("lib2/build.gradle").writeText(
+      // language=groovy
       """
         |plugins { id "java-library" }
         |
@@ -249,6 +259,7 @@ class DependencyGraphGeneratorPluginTest {
 
     val app = testProjectDir.newFolder("app").run { parentFile.name + name }
     testProjectDir.newFile("app/build.gradle").writeText(
+      // language=groovy
       """
         |plugins {
         |  id "java-library"
@@ -282,6 +293,7 @@ class DependencyGraphGeneratorPluginTest {
     assertEquals(true, File(testProjectDir.root, "build/reports/dependency-graph/dependency-graph.svg").exists())
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -317,6 +329,7 @@ class DependencyGraphGeneratorPluginTest {
     // We don't want to assert the content of the image, just that it exists.
     assertEquals(true, File(testProjectDir.root, "build/reports/project-dependency-graph/project-dependency-graph.svg").exists())
 
+    @Language("dot")
     fun projectDependencyGraph(label: String) = """
         digraph {
         edge ["dir"="forward"]
diff --git a/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorTest.kt b/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorTest.kt
index 55907c0..b1d40d0 100644
--- a/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorTest.kt
+++ b/src/test/java/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorTest.kt
@@ -77,6 +77,7 @@ class DependencyGraphGeneratorTest {
     singleEmpty.dependencies.add("testImplementation", "junit:junit:4.12")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -95,6 +96,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectEmptyAllNoProjects() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         graph ["dpi"="100"]
@@ -106,6 +108,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectEmptyAll() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -124,6 +127,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectEmptyAllMutateGraph() {
     assertEquals(
+      // language=dot
       """
         graph "G" {
         graph ["dpi"="100"]
@@ -140,6 +144,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectEmptyAllHeader() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -158,6 +163,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectEmptyAllRootFormatted() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -176,6 +182,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectAll() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -210,6 +217,7 @@ class DependencyGraphGeneratorTest {
     }
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -236,6 +244,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectNoChildren() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -258,6 +267,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun singleProjectFilterRxJavaOut() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -282,6 +292,7 @@ class DependencyGraphGeneratorTest {
     singleEmpty.dependencies.add("implementation", "org.apache.xmlgraphics:batik-gvt:1.7")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -381,6 +392,7 @@ class DependencyGraphGeneratorTest {
     singleProject.dependencies.add("implementation", "io.reactivex.rxjava2:rxandroid:2.0.2")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -410,6 +422,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun multiProjectAll() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -446,6 +459,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("implementation", "android.arch.persistence.room:runtime:1.0.0")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -493,6 +507,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("implementation", "com.squareup.sqldelight:runtime:0.6.1")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -527,6 +542,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("flavor2ReleaseImplementation", "org.jetbrains.kotlin:kotlin-stdlib:1.2.30")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -566,6 +582,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("stagingImplementation", "org.jetbrains.kotlin:kotlin-stdlib:1.2.30")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -605,6 +622,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("stagingImplementation", "org.jetbrains.kotlin:kotlin-stdlib:1.2.30")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -631,6 +649,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("testImplementation", "junit:junit:4.12")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -653,6 +672,7 @@ class DependencyGraphGeneratorTest {
     androidProject.dependencies.add("androidTestImplementation", "junit:junit:4.12")
 
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
@@ -671,6 +691,7 @@ class DependencyGraphGeneratorTest {
 
   @Test fun projectNamedLikeDependencyName() {
     assertEquals(
+      // language=dot
       """
         digraph "G" {
         edge ["dir"="forward"]
diff --git a/src/test/java/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGeneratorIncludeProjectTest.kt b/src/test/java/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGeneratorIncludeProjectTest.kt
new file mode 100644
index 0000000..51ef22a
--- /dev/null
+++ b/src/test/java/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGeneratorIncludeProjectTest.kt
@@ -0,0 +1,79 @@
+package com.vanniktech.dependency.graph.generator
+
+import com.vanniktech.dependency.graph.generator.DependencyGraphGeneratorExtension.ProjectGenerator.Companion.ALL
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaLibraryPlugin
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+class ProjectDependencyGraphGeneratorIncludeProjectTest {
+
+  private lateinit var root: Project
+  private lateinit var app: Project
+  private lateinit var lib1: Project
+  private lateinit var lib2: Project
+
+  @Before fun setUp() {
+    root = ProjectBuilder.builder().withName("root").build()
+    root.plugins.apply(JavaLibraryPlugin::class.java)
+
+    app = ProjectBuilder.builder().withParent(root).withName("app").build()
+    app.plugins.apply(JavaLibraryPlugin::class.java)
+
+    lib1 = ProjectBuilder.builder().withParent(root).withName("lib1").build()
+    lib1.plugins.apply(JavaLibraryPlugin::class.java)
+
+    lib2 = ProjectBuilder.builder().withParent(root).withName("lib2").build()
+    lib2.plugins.apply(JavaLibraryPlugin::class.java)
+
+    app.dependencies.add("implementation", lib1)
+    app.dependencies.add("implementation", lib2)
+    lib1.dependencies.add("implementation", lib2)
+  }
+
+  @Test fun excludeNonLeafProject() {
+    assertEquals(
+      // language=dot
+      """
+      digraph {
+      edge ["dir"="forward"]
+      graph ["dpi"="100","label"="root","labelloc"="t","fontsize"="35"]
+      node ["style"="filled"]
+      ":app" ["shape"="rectangle","fillcolor"="#FF7043"]
+      ":lib2" ["fillcolor"="#FF7043"]
+      {
+      edge ["dir"="none"]
+      graph ["rank"="same"]
+      ":app"
+      }
+      ":app" -> ":lib2" ["style"="dotted"]
+      }
+      """.trimIndent(),
+      ProjectDependencyGraphGenerator(root, ALL.copy(includeProject = { it != lib1 })).generateGraph().toString()
+    )
+  }
+
+  @Test fun excludeLeafProject() {
+    assertEquals(
+      // language=dot
+      """
+      digraph {
+      edge ["dir"="forward"]
+      graph ["dpi"="100","label"="root","labelloc"="t","fontsize"="35"]
+      node ["style"="filled"]
+      ":app" ["shape"="rectangle","fillcolor"="#FF7043"]
+      ":lib1" ["fillcolor"="#FF7043"]
+      {
+      edge ["dir"="none"]
+      graph ["rank"="same"]
+      ":app"
+      }
+      ":app" -> ":lib1" ["style"="dotted"]
+      }
+      """.trimIndent(),
+      ProjectDependencyGraphGenerator(root, ALL.copy(includeProject = { it != lib2 })).generateGraph().toString()
+    )
+  }
+}