## Integration Tests for Dependency Resolver ## ## These tests verify the complete end-to-end resolution pipeline: ## - Graph construction ## - CNF translation ## - CDCL solving ## - Solution conversion ## - Installation order computation ## ## Requirements: 5.1, 5.4, 5.5 import std/[unittest, tables, sets, options, strutils, sequtils, times] import ../src/nip/resolver/[ resolver_integration, dependency_graph, variant_types, cnf_translator, cdcl_solver ] import ../src/nip/manifest_parser suite "Resolver Integration Tests": # Helper to create a simple variant profile proc makeVariant(flags: seq[string] = @[]): VariantProfile = var domains = initTable[string, VariantDomain]() if flags.len > 0: var flagSet = initHashSet[string]() for flag in flags: flagSet.incl(flag) domains["features"] = VariantDomain( name: "features", exclusivity: NonExclusive, flags: flagSet ) result = VariantProfile( domains: domains, hash: "test-hash-" & flags.join("-") ) # Helper to create a package spec proc makeSpec(name: string, flags: seq[string] = @[]): PackageSpec = PackageSpec( packageName: name, versionConstraint: VersionConstraint( operator: OpAny, version: SemanticVersion(major: 1, minor: 0, patch: 0) ), variantProfile: makeVariant(flags) ) test "End-to-end resolution - simple chain": ## Test: A -> B -> C ## Expected: Resolves successfully with installation order [C, B, A] ## ## Requirements: 5.1, 5.4, 5.5 # Build graph: A -> B -> C var graph = newDependencyGraph() let variantA = makeVariant() let variantB = makeVariant() let variantC = makeVariant() let termIdA = createTermId("A", variantA.hash) let termIdB = createTermId("B", variantB.hash) let termIdC = createTermId("C", variantC.hash) graph.addTerm(PackageTerm( id: termIdA, packageName: "A", variantProfile: variantA, optional: false, source: "test" )) graph.addTerm(PackageTerm( id: termIdB, packageName: "B", variantProfile: variantB, optional: false, source: "test" )) graph.addTerm(PackageTerm( id: termIdC, packageName: "C", variantProfile: variantC, optional: false, source: "test" )) graph.addEdge(DependencyEdge( fromTerm: termIdA, toTerm: termIdB, dependencyType: Required )) graph.addEdge(DependencyEdge( fromTerm: termIdB, toTerm: termIdC, dependencyType: Required )) # Create resolution request let request = ResolutionRequest( rootPackages: @[makeSpec("A")], constraints: @[] ) # Resolve let result = resolve(request, graph) # Verify success check result.success check result.packages.len == 3 # Verify installation order (dependencies first) check result.installOrder.len == 3 check result.installOrder[0] == "C" # No dependencies check result.installOrder[1] == "B" # Depends on C check result.installOrder[2] == "A" # Depends on B test "End-to-end resolution - diamond dependency": ## Test: A -> B, A -> C, B -> D, C -> D ## Expected: Resolves successfully with D first, then B and C, then A ## ## Requirements: 5.1, 5.4, 5.5 var graph = newDependencyGraph() let variantA = makeVariant() let variantB = makeVariant() let variantC = makeVariant() let variantD = makeVariant() let termIdA = createTermId("A", variantA.hash) let termIdB = createTermId("B", variantB.hash) let termIdC = createTermId("C", variantC.hash) let termIdD = createTermId("D", variantD.hash) # Add terms for (id, name, variant) in [(termIdA, "A", variantA), (termIdB, "B", variantB), (termIdC, "C", variantC), (termIdD, "D", variantD)]: graph.addTerm(PackageTerm( id: id, packageName: name, variantProfile: variant, optional: false, source: "test" )) # Add edges graph.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdB, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdC, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdB, toTerm: termIdD, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdC, toTerm: termIdD, dependencyType: Required)) let request = ResolutionRequest( rootPackages: @[makeSpec("A")], constraints: @[] ) let result = resolve(request, graph) check result.success check result.packages.len == 4 # D must come before B and C let dIndex = result.installOrder.find("D") let bIndex = result.installOrder.find("B") let cIndex = result.installOrder.find("C") let aIndex = result.installOrder.find("A") check dIndex < bIndex check dIndex < cIndex check bIndex < aIndex check cIndex < aIndex test "Conflict reporting - circular dependency": ## Test: A -> B -> C -> A (cycle) ## Expected: Reports circular dependency conflict ## ## Requirements: 5.1, 5.5 var graph = newDependencyGraph() let variantA = makeVariant() let variantB = makeVariant() let variantC = makeVariant() let termIdA = createTermId("A", variantA.hash) let termIdB = createTermId("B", variantB.hash) let termIdC = createTermId("C", variantC.hash) # Add terms for (id, name, variant) in [(termIdA, "A", variantA), (termIdB, "B", variantB), (termIdC, "C", variantC)]: graph.addTerm(PackageTerm( id: id, packageName: name, variantProfile: variant, optional: false, source: "test" )) # Create cycle: A -> B -> C -> A graph.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdB, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdB, toTerm: termIdC, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdC, toTerm: termIdA, dependencyType: Required)) let request = ResolutionRequest( rootPackages: @[makeSpec("A")], constraints: @[] ) let result = resolve(request, graph) check not result.success check result.conflict.conflictType == CircularDependency check result.conflict.packages.len > 0 test "Solution correctness - all dependencies satisfied": ## Test: Verify that the solution satisfies all dependencies ## A -> B, A -> C, B -> D ## ## Requirements: 5.1, 5.4 var graph = newDependencyGraph() let variantA = makeVariant() let variantB = makeVariant() let variantC = makeVariant() let variantD = makeVariant() let termIdA = createTermId("A", variantA.hash) let termIdB = createTermId("B", variantB.hash) let termIdC = createTermId("C", variantC.hash) let termIdD = createTermId("D", variantD.hash) # Add terms for (id, name, variant) in [(termIdA, "A", variantA), (termIdB, "B", variantB), (termIdC, "C", variantC), (termIdD, "D", variantD)]: graph.addTerm(PackageTerm( id: id, packageName: name, variantProfile: variant, optional: false, source: "test" )) # Add edges graph.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdB, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdC, dependencyType: Required)) graph.addEdge(DependencyEdge(fromTerm: termIdB, toTerm: termIdD, dependencyType: Required)) let request = ResolutionRequest( rootPackages: @[makeSpec("A")], constraints: @[] ) let result = resolve(request, graph) check result.success # Verify all packages are present let packageNames = result.packages.mapIt(it.packageName).toHashSet check "A" in packageNames check "B" in packageNames check "C" in packageNames check "D" in packageNames # Verify installation order is valid (dependencies before dependents) var orderMap = initTable[string, int]() for i, name in result.installOrder: orderMap[name] = i # A depends on B and C check orderMap["B"] < orderMap["A"] check orderMap["C"] < orderMap["A"] # B depends on D check orderMap["D"] < orderMap["B"] test "Performance - large graph (50 packages)": ## Test: Resolution should complete in reasonable time for large graphs ## Create a chain of 50 packages ## ## Requirements: 5.1, 5.4 var graph = newDependencyGraph() # Create chain: P0 -> P1 -> P2 -> ... -> P49 var prevTermId: Option[PackageTermId] = none(PackageTermId) for i in 0..<50: let variant = makeVariant() let termId = createTermId("P" & $i, variant.hash) graph.addTerm(PackageTerm( id: termId, packageName: "P" & $i, variantProfile: variant, optional: false, source: "test" )) if prevTermId.isSome: graph.addEdge(DependencyEdge( fromTerm: prevTermId.get(), toTerm: termId, dependencyType: Required )) prevTermId = some(termId) let request = ResolutionRequest( rootPackages: @[makeSpec("P0")], constraints: @[] ) # Measure resolution time let startTime = cpuTime() let result = resolve(request, graph) let endTime = cpuTime() let elapsedMs = (endTime - startTime) * 1000.0 check result.success check result.packages.len == 50 check result.installOrder.len == 50 # Should complete in under 1 second for 50 packages check elapsedMs < 1000.0 echo "Resolution time for 50 packages: ", elapsedMs.formatFloat(ffDecimal, 2), " ms" test "Empty graph resolution": ## Test: Resolving with an empty graph should fail ## The root package must be in the graph to be resolved ## ## Requirements: 5.1, 5.5 let graph = newDependencyGraph() let request = ResolutionRequest( rootPackages: @[makeSpec("A")], constraints: @[] ) let result = resolve(request, graph) # Should fail because root package is not in graph check not result.success check result.conflict.conflictType == Unsatisfiable check result.conflict.packages.len == 1 check result.conflict.packages[0] == "A" test "Single package resolution": ## Test: Resolving a single package with no dependencies ## ## Requirements: 5.1, 5.4 var graph = newDependencyGraph() let variant = makeVariant() let termId = createTermId("A", variant.hash) graph.addTerm(PackageTerm( id: termId, packageName: "A", variantProfile: variant, optional: false, source: "test" )) let request = ResolutionRequest( rootPackages: @[makeSpec("A")], constraints: @[] ) let result = resolve(request, graph) check result.success check result.packages.len == 1 check result.packages[0].packageName == "A" check result.installOrder == @["A"]