313 lines
9.9 KiB
Nim
313 lines
9.9 KiB
Nim
## Tests for Dependency Graph
|
|
##
|
|
## Tests the core graph operations: node/edge addition, cycle detection,
|
|
## and graph analysis functions.
|
|
|
|
import unittest
|
|
import tables
|
|
import sets
|
|
import options
|
|
import ../src/nip/resolver/dependency_graph
|
|
import ../src/nip/resolver/variant_types
|
|
import ../src/nip/manifest_parser
|
|
|
|
suite "Dependency Graph Tests":
|
|
|
|
# Helper to create a dummy variant profile
|
|
proc makeVariant(hash: string): VariantProfile =
|
|
VariantProfile(
|
|
domains: initTable[string, VariantDomain](),
|
|
hash: hash
|
|
)
|
|
|
|
# Helper to create a dummy term
|
|
proc makeTerm(name, ver, hash: string): PackageTerm =
|
|
let id = createTermId(name, hash)
|
|
PackageTerm(
|
|
id: id,
|
|
packageName: name,
|
|
version: parseSemanticVersion(ver),
|
|
variantHash: hash,
|
|
variantProfile: makeVariant(hash),
|
|
optional: false,
|
|
source: "test"
|
|
)
|
|
|
|
test "Create empty graph":
|
|
let graph = newDependencyGraph()
|
|
check graph.nodeCount() == 0
|
|
check graph.edgeCount() == 0
|
|
check graph.getRoots().len == 0
|
|
check graph.getLeaves().len == 0
|
|
|
|
test "Add single node":
|
|
var graph = newDependencyGraph()
|
|
let term = makeTerm("nginx", "1.24.0", "abc123")
|
|
graph.addTerm(term)
|
|
|
|
check graph.nodeCount() == 1
|
|
check graph.edgeCount() == 0
|
|
check graph.getRoots().len == 1
|
|
check graph.getLeaves().len == 1
|
|
|
|
test "Add multiple nodes":
|
|
var graph = newDependencyGraph()
|
|
let nginx = makeTerm("nginx", "1.24.0", "abc123")
|
|
let openssl = makeTerm("openssl", "3.0.0", "def456")
|
|
let zlib = makeTerm("zlib", "1.2.13", "ghi789")
|
|
|
|
graph.addTerm(nginx)
|
|
graph.addTerm(openssl)
|
|
graph.addTerm(zlib)
|
|
|
|
check graph.nodeCount() == 3
|
|
check graph.getRoots().len == 3
|
|
|
|
test "Add edge between nodes":
|
|
var graph = newDependencyGraph()
|
|
let nginx = makeTerm("nginx", "1.24.0", "abc123")
|
|
let openssl = makeTerm("openssl", "3.0.0", "def456")
|
|
|
|
graph.addTerm(nginx)
|
|
graph.addTerm(openssl)
|
|
|
|
# Use explicit edge construction
|
|
graph.addEdge(DependencyEdge(
|
|
fromTerm: nginx.id,
|
|
toTerm: openssl.id,
|
|
dependencyType: Required
|
|
))
|
|
|
|
check graph.nodeCount() == 2
|
|
check graph.edgeCount() == 1
|
|
check graph.getRoots().len == 1 # nginx is root
|
|
check graph.getLeaves().len == 1 # openssl is leaf
|
|
|
|
# Verify edge properties
|
|
let edges = graph.getOutgoingEdges(nginx.id)
|
|
check edges.len == 1
|
|
check edges[0].fromTerm == nginx.id
|
|
check edges[0].toTerm == openssl.id
|
|
|
|
test "Get incoming edges":
|
|
var graph = newDependencyGraph()
|
|
let nginx = makeTerm("nginx", "1.24.0", "abc123")
|
|
let openssl = makeTerm("openssl", "3.0.0", "def456")
|
|
|
|
graph.addTerm(nginx)
|
|
graph.addTerm(openssl)
|
|
graph.addEdge(DependencyEdge(fromTerm: nginx.id, toTerm: openssl.id, dependencyType: Required))
|
|
|
|
let incoming = graph.getIncomingEdges(openssl.id)
|
|
check incoming.len == 1
|
|
check incoming[0].fromTerm == nginx.id
|
|
check incoming[0].toTerm == openssl.id
|
|
|
|
test "Get outgoing edges":
|
|
var graph = newDependencyGraph()
|
|
let nginx = makeTerm("nginx", "1.24.0", "abc123")
|
|
let openssl = makeTerm("openssl", "3.0.0", "def456")
|
|
|
|
graph.addTerm(nginx)
|
|
graph.addTerm(openssl)
|
|
graph.addEdge(DependencyEdge(fromTerm: nginx.id, toTerm: openssl.id, dependencyType: Required))
|
|
|
|
let outgoing = graph.getOutgoingEdges(nginx.id)
|
|
check outgoing.len == 1
|
|
check outgoing[0].fromTerm == nginx.id
|
|
check outgoing[0].toTerm == openssl.id
|
|
|
|
test "Get dependencies":
|
|
var graph = newDependencyGraph()
|
|
let nginx = makeTerm("nginx", "1.24.0", "abc123")
|
|
let openssl = makeTerm("openssl", "3.0.0", "def456")
|
|
let zlib = makeTerm("zlib", "1.2.13", "ghi789")
|
|
|
|
graph.addTerm(nginx)
|
|
graph.addTerm(openssl)
|
|
graph.addTerm(zlib)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: nginx.id, toTerm: openssl.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: nginx.id, toTerm: zlib.id, dependencyType: Required))
|
|
|
|
let deps = graph.getDependencies(nginx.id)
|
|
check deps.len == 2
|
|
check openssl in deps
|
|
check zlib in deps
|
|
|
|
test "Get dependents":
|
|
var graph = newDependencyGraph()
|
|
let nginx = makeTerm("nginx", "1.24.0", "abc123")
|
|
let openssl = makeTerm("openssl", "3.0.0", "def456")
|
|
let curl = makeTerm("curl", "8.0.0", "jkl012")
|
|
|
|
graph.addTerm(nginx)
|
|
graph.addTerm(openssl)
|
|
graph.addTerm(curl)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: nginx.id, toTerm: openssl.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: curl.id, toTerm: openssl.id, dependencyType: Required))
|
|
|
|
let dependents = graph.getDependents(openssl.id)
|
|
check dependents.len == 2
|
|
check nginx in dependents
|
|
check curl in dependents
|
|
|
|
test "Detect no cycle in simple chain":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
let c = makeTerm("c", "1.0.0", "c")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
graph.addTerm(c)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: b.id, toTerm: c.id, dependencyType: Required))
|
|
|
|
check graph.hasCycle() == false
|
|
|
|
test "Detect cycle in simple loop":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: b.id, toTerm: a.id, dependencyType: Required))
|
|
|
|
check graph.hasCycle() == true
|
|
|
|
test "Detect cycle in self-loop":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
|
|
graph.addTerm(a)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: a.id, dependencyType: Required))
|
|
|
|
check graph.hasCycle() == true
|
|
|
|
test "Detect cycle in complex graph":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
let c = makeTerm("c", "1.0.0", "c")
|
|
let d = makeTerm("d", "1.0.0", "d")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
graph.addTerm(c)
|
|
graph.addTerm(d)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: b.id, toTerm: c.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: c.id, toTerm: d.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: d.id, toTerm: b.id, dependencyType: Required)) # Creates cycle: b -> c -> d -> b
|
|
|
|
check graph.hasCycle() == true
|
|
|
|
test "Find cycle path":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: b.id, toTerm: a.id, dependencyType: Required))
|
|
|
|
let cycle = graph.findCycle()
|
|
check cycle.len >= 2
|
|
# Check if terms are in cycle (by checking if any term in cycle has same ID)
|
|
var foundA = false
|
|
var foundB = false
|
|
for term in cycle:
|
|
if term.id == a.id: foundA = true
|
|
if term.id == b.id: foundB = true
|
|
|
|
check foundA
|
|
check foundB
|
|
|
|
test "Get roots":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
let c = makeTerm("c", "1.0.0", "c")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
graph.addTerm(c)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: c.id, dependencyType: Required))
|
|
|
|
let roots = graph.getRoots()
|
|
check roots.len == 1
|
|
check roots[0].id == a.id
|
|
|
|
test "Get leaves":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
let c = makeTerm("c", "1.0.0", "c")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
graph.addTerm(c)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: c.id, dependencyType: Required))
|
|
|
|
let leaves = graph.getLeaves()
|
|
check leaves.len == 2
|
|
# Check IDs
|
|
let leafIds = @[leaves[0].id, leaves[1].id]
|
|
check b.id in leafIds
|
|
check c.id in leafIds
|
|
|
|
test "Get depth":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
let c = makeTerm("c", "1.0.0", "c")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
graph.addTerm(c)
|
|
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: b.id, toTerm: c.id, dependencyType: Required))
|
|
|
|
check graph.getDepth(a.id) == 2
|
|
check graph.getDepth(b.id) == 1
|
|
check graph.getDepth(c.id) == 0
|
|
|
|
test "Diamond dependency graph":
|
|
var graph = newDependencyGraph()
|
|
let a = makeTerm("a", "1.0.0", "a")
|
|
let b = makeTerm("b", "1.0.0", "b")
|
|
let c = makeTerm("c", "1.0.0", "c")
|
|
let d = makeTerm("d", "1.0.0", "d")
|
|
|
|
graph.addTerm(a)
|
|
graph.addTerm(b)
|
|
graph.addTerm(c)
|
|
graph.addTerm(d)
|
|
|
|
# Diamond: a -> b,c -> d
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: b.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: a.id, toTerm: c.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: b.id, toTerm: d.id, dependencyType: Required))
|
|
graph.addEdge(DependencyEdge(fromTerm: c.id, toTerm: d.id, dependencyType: Required))
|
|
|
|
check graph.nodeCount() == 4
|
|
check graph.edgeCount() == 4
|
|
check graph.hasCycle() == false
|
|
check graph.getDependencies(a.id).len == 2
|
|
check graph.getDependents(d.id).len == 2
|