## Property-Based Tests for Resolution Completeness ## ## **Feature: 02-nip-dependency-resolution, Property 5: Resolution Completeness** ## **Validates: Requirements 5.1, 5.4** ## ## Property: For any satisfiable dependency graph, the resolver SHALL find a solution ## ## This property verifies that: ## 1. If a solution exists, the resolver finds it ## 2. The solution satisfies all dependencies ## 3. The installation order respects dependency relationships ## 4. The resolver is deterministic (same input → same output) import std/[unittest, tables, sets, options, random, sequtils] import ../src/nip/resolver/[ resolver_integration, dependency_graph, variant_types, cnf_translator, cdcl_solver ] import ../src/nip/manifest_parser # --- Test Data Generators --- proc generateVariant(rng: var Rand, numFlags: int = 0): VariantProfile = ## Generate a random variant profile var domains = initTable[string, VariantDomain]() if numFlags > 0: var flagSet = initHashSet[string]() for i in 0..= layerSize: packages.add(currentLayer) currentLayer = @[] # Add remaining packages to last layer if currentLayer.len > 0: packages.add(currentLayer) # Add edges between layers (only from higher layer to lower layer) for layerIdx in 1.. P1 -> P2 -> ... -> Pn result = newDependencyGraph() var prevTermId: Option[PackageTermId] = none(PackageTermId) for i in 0.. B, A -> C, B -> D, C -> D result = newDependencyGraph() let variantA = VariantProfile(domains: initTable[string, VariantDomain](), hash: "hash-A") let variantB = VariantProfile(domains: initTable[string, VariantDomain](), hash: "hash-B") let variantC = VariantProfile(domains: initTable[string, VariantDomain](), hash: "hash-C") let variantD = VariantProfile(domains: initTable[string, VariantDomain](), hash: "hash-D") let termIdA = createTermId("A", variantA.hash) let termIdB = createTermId("B", variantB.hash) let termIdC = createTermId("C", variantC.hash) let termIdD = createTermId("D", variantD.hash) for (id, name, variant) in [(termIdA, "A", variantA), (termIdB, "B", variantB), (termIdC, "C", variantC), (termIdD, "D", variantD)]: result.addTerm(PackageTerm( id: id, packageName: name, variantProfile: variant, optional: false, source: "test" )) result.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdB, dependencyType: Required)) result.addEdge(DependencyEdge(fromTerm: termIdA, toTerm: termIdC, dependencyType: Required)) result.addEdge(DependencyEdge(fromTerm: termIdB, toTerm: termIdD, dependencyType: Required)) result.addEdge(DependencyEdge(fromTerm: termIdC, toTerm: termIdD, dependencyType: Required)) # --- Property Tests --- suite "Resolution Completeness Properties": test "Property 5: Resolution Completeness - Chain graphs": ## **Feature: 02-nip-dependency-resolution, Property 5: Resolution Completeness** ## **Validates: Requirements 5.1, 5.4** ## ## Property: For any chain graph (no cycles), resolution SHALL succeed ## and produce a valid installation order var rng = initRand(12345) var successCount = 0 var totalTests = 0 # Test with various chain lengths for chainLength in [1, 2, 5, 10, 20]: for iteration in 0..<4: # 4 iterations per length totalTests += 1 let graph = generateChainGraph(chainLength) # Create resolution request for first package let request = ResolutionRequest( rootPackages: @[PackageSpec( packageName: "pkg0", versionConstraint: VersionConstraint( operator: OpAny, version: SemanticVersion(major: 1, minor: 0, patch: 0) ), variantProfile: VariantProfile( domains: initTable[string, VariantDomain](), hash: "hash-0" ) )], constraints: @[] ) let result = resolve(request, graph) # Property: Resolution should succeed if not result.success: echo "FAILED: Chain length ", chainLength, " iteration ", iteration echo " Conflict: ", result.conflict.details check false continue # Property: All packages should be in solution check result.packages.len == chainLength # Property: Installation order should respect dependencies # In a chain, pkg(i) depends on pkg(i+1), so pkg(i+1) must come first var orderMap = initTable[string, int]() for i, name in result.installOrder: orderMap[name] = i for i in 0..<(chainLength - 1): let dependent = "pkg" & $i let dependency = "pkg" & $(i + 1) if orderMap.hasKey(dependent) and orderMap.hasKey(dependency): if orderMap[dependency] >= orderMap[dependent]: echo "FAILED: Dependency order violated" echo " ", dependent, " (index ", orderMap[dependent], ") depends on ", dependency, " (index ", orderMap[dependency], ")" check false continue successCount += 1 echo "Property 5 (Chain): ", successCount, "/", totalTests, " tests passed" check successCount == totalTests test "Property 5: Resolution Completeness - Diamond graphs": ## **Feature: 02-nip-dependency-resolution, Property 5: Resolution Completeness** ## **Validates: Requirements 5.1, 5.4** ## ## Property: For diamond graphs, resolution SHALL succeed and D SHALL come before B and C var successCount = 0 var totalTests = 20 for iteration in 0..= orderMap["B"] or orderMap["D"] >= orderMap["C"]: echo "FAILED: D should come before B and C" echo " Order: ", result.installOrder check false continue # Property: B and C must come before A if orderMap["B"] >= orderMap["A"] or orderMap["C"] >= orderMap["A"]: echo "FAILED: B and C should come before A" echo " Order: ", result.installOrder check false continue successCount += 1 echo "Property 5 (Diamond): ", successCount, "/", totalTests, " tests passed" check successCount == totalTests test "Property 5: Resolution Completeness - Random acyclic graphs": ## **Feature: 02-nip-dependency-resolution, Property 5: Resolution Completeness** ## **Validates: Requirements 5.1, 5.4** ## ## Property: For any acyclic graph, resolution SHALL succeed var rng = initRand(54321) var successCount = 0 var totalTests = 20 for iteration in 0..= 1 # Property: Installation order should be valid (no package before its dependencies) var orderMap = initTable[string, int]() for i, name in result.installOrder: orderMap[name] = i var orderValid = true for edge in graph.edges: let fromTerm = graph.getTerm(edge.fromTerm) let toTerm = graph.getTerm(edge.toTerm) if fromTerm.isSome and toTerm.isSome: let fromName = fromTerm.get().packageName let toName = toTerm.get().packageName if orderMap.hasKey(fromName) and orderMap.hasKey(toName): if orderMap[toName] >= orderMap[fromName]: echo "FAILED: Dependency order violated in random graph" echo " ", fromName, " depends on ", toName echo " But ", toName, " comes after ", fromName, " in install order" orderValid = false break if not orderValid: check false continue successCount += 1 echo "Property 5 (Random): ", successCount, "/", totalTests, " tests passed" check successCount == totalTests test "Property 5: Determinism - Same input produces same output": ## **Feature: 02-nip-dependency-resolution, Property 5: Resolution Completeness** ## **Validates: Requirements 5.1, 5.4** ## ## Property: Resolving the same graph multiple times SHALL produce identical results let graph = generateDiamondGraph() let request = ResolutionRequest( rootPackages: @[PackageSpec( packageName: "A", versionConstraint: VersionConstraint( operator: OpAny, version: SemanticVersion(major: 1, minor: 0, patch: 0) ), variantProfile: VariantProfile( domains: initTable[string, VariantDomain](), hash: "hash-A" ) )], constraints: @[] ) # Resolve multiple times var results: seq[ResolutionResult] = @[] for _ in 0..<10: results.add(resolve(request, graph)) # Property: All results should be identical let firstResult = results[0] for i in 1..