Skip to content

Commit

Permalink
Merge pull request #36 from leandroBorgesFerreira/CycleDetenction
Browse files Browse the repository at this point in the history
Cycle detection when parsing project
  • Loading branch information
leandroBorgesFerreira authored Mar 17, 2024
2 parents 9a3c084 + da29d69 commit ef7c97e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 13 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.konan.properties.Properties
import java.io.FileInputStream

val artifactIdVal = "dag-command"
val versionVal = "1.10.0"
val versionVal = "1.11.0"
val publicationName = "dagCommand"

group = "io.github.leandroborgesferreira"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.github.leandroborgesferreira.dagcommand.domain.exception

class CycleDetectedException(message: String) : IllegalStateException(message)
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.leandroborgesferreira.dagcommand.logic

import io.github.leandroborgesferreira.dagcommand.domain.AdjacencyList
import io.github.leandroborgesferreira.dagcommand.domain.DagProject
import io.github.leandroborgesferreira.dagcommand.domain.exception.CycleDetectedException
import java.util.TreeSet

/**
Expand Down Expand Up @@ -42,7 +44,7 @@ private fun traverseGraph(
) {
if (visited.contains(module)) {
val graphString = "${visited.joinToString(separator = "->")}->$module"
throw IllegalStateException(
throw CycleDetectedException(
"A cycle was detected in your graph. The part of the graph with the cycle is: $graphString"
)
}
Expand All @@ -57,3 +59,47 @@ private fun traverseGraph(
traverseGraph(adjacencyList, dependentModule, visited + module, resultSet)
}
}

internal fun <T> Iterable<T>.iterableToDagProjectT(
filterModules: Set<String>,
visitedT: Set<String>,
getNext: (T) -> Iterable<T>,
getName: (T) -> String
) = map { project -> project.toDagProjectT(filterModules, visitedT, getNext, getName) }

internal fun <T> T.toDagProjectT(
filterModules: Set<String>,
visitedT: Set<String>,
getNext: (T) -> Iterable<T>,
getName: (T) -> String,
): DagProject {
val nextDependencies = getNext(this)
val nextNames = getNext(this).map(getName)
val name = getName(this)

println(
"""
Current module: $name
Next names: ${nextNames.joinToString()}
"""
)

if (nextNames.any(visitedT::contains)) {
val visitedAlready = nextNames.filter(visitedT::contains).distinct().joinToString()

throw CycleDetectedException(
"""
A cycle was detected in the graph of dependencies of your project.
These modules appeared more than once: $visitedAlready. Trying to visit from: $name.
All nodes visited already in this path: ${visitedT.joinToString()}
"""
)
}

return DagProject(
fullName = name,
dependencies = nextDependencies.map { project ->
project.toDagProjectT(filterModules, visitedT + name, getNext, getName)
}.toSet()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,11 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency

fun Project.toDagProjectList(filterModules: Set<String>): List<DagProject> =
project.subprojects.map { project ->
project.toDagProject(filterModules)
}

private fun Project.toDagProject(filterModules: Set<String>): DagProject =
DagProject(
fullName = this.path,
dependencies = parseDependencies(filterModules).map { project ->
project.toDagProject(filterModules)
}.toSet()
project.subprojects.iterableToDagProjectT(
filterModules = filterModules,
visitedT = emptySet(),
getNext = { project -> project.parseDependencies(filterModules) },
getName = { project -> project.name },
)

private fun Project.parseDependencies(filterModules: Set<String>): List<Project> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.github.leandroborgesferreira.dagcommand.logic

import io.github.leandroborgesferreira.dagcommand.domain.Node
import io.github.leandroborgesferreira.dagcommand.domain.exception.CycleDetectedException
import io.github.leandroborgesferreira.dagcommand.utils.cyclicalObjectGraph
import io.github.leandroborgesferreira.dagcommand.utils.disconnectedGraph
import io.github.leandroborgesferreira.dagcommand.utils.graphWithCycle
import io.github.leandroborgesferreira.dagcommand.utils.objectGraph
import io.github.leandroborgesferreira.dagcommand.utils.simpleGraph
import org.junit.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -60,8 +63,31 @@ class GraphKtTest {
assertEquals(expected, result)
}

@Test(expected = IllegalStateException::class)
@Test(expected = CycleDetectedException::class)
fun `proves that cycles are detected`() {
affectedModules(graphWithCycle(), listOf(":A", ":B", ":C"))
}

@Test(expected = CycleDetectedException::class)
fun `cycles are detected when building DagProject`() {
cyclicalObjectGraph()
.iterableToDagProjectT(
filterModules = emptySet(),
visitedT = emptySet(),
getNext = { project -> project.next },
getName = { project -> project.name },
)
}

@Test
fun `iterableToDagProjectT should work`() {
objectGraph()
.iterableToDagProjectT(
filterModules = emptySet(),
visitedT = emptySet(),
getNext = { project -> project.next },
getName = { project -> project.name },
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,61 @@ fun disconnectedGraph(): Map<String, Set<String>> =
":E" to emptySet(),
":F" to emptySet()
)

fun cyclicalObjectGraph(): List<GraphNode> =
listOf(
GraphNode(
name = "A",
next = listOf(
GraphNode(
name = "B",
next = listOf(
GraphNode(name = "A")
)
)
)
)
)


fun objectGraph(): List<GraphNode> =
listOf(
GraphNode(
name = "A",
next = listOf(
GraphNode(
name = "B",
next = listOf(
GraphNode(name = "E")
)
)
)
),
GraphNode(
name = "C",
next = listOf(
GraphNode(
name = "B",
next = listOf(
GraphNode(name = "E")
)
)
)
),
GraphNode(
name = "D",
next = listOf(
GraphNode(
name = "B",
next = listOf(
GraphNode(name = "E")
)
)
)
),
)

data class GraphNode(
val name: String,
val next: Iterable<GraphNode> = emptySet()
)

0 comments on commit ef7c97e

Please sign in to comment.