## Unit Tests for CNF Translation ## ## Tests for translating dependency constraints into Conjunctive Normal Form (CNF) ## for use with CDCL-based SAT solving. ## ## Requirements tested: ## - 6.1: Create boolean variables for each term ## - 6.2: Create implication clauses for dependencies (A → B) ## - 6.3: Create mutual exclusion clauses (¬(A ∧ B)) ## - 6.4: Create satisfaction clauses for variants ## - 6.5: Validate CNF is ready for CDCL solving import std/[unittest, options, strutils, tables] import ../src/nip/resolver/cnf_translator import ../src/nip/resolver/solver_types import ../src/nip/resolver/variant_types import ../src/nip/manifest_parser suite "CNF Translation Tests": test "Create boolean variable": ## Test creating a boolean variable from package+version+variant ## Requirements: 6.1 var variant = newVariantProfile() variant.addFlag("optimization", "lto") variant.calculateHash() let boolVar = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: variant ) check boolVar.package == "nginx" check boolVar.version.major == 1 check boolVar.variant.hash == variant.hash # Test string representation let varStr = $boolVar check varStr.contains("nginx") check varStr.contains("1.24.0") test "Create literal": ## Test creating positive and negative literals ## Requirements: 6.1 let variable = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: newVariantProfile() ) let positiveLit = makeLiteral(variable, isNegated = false) let negativeLit = makeLiteral(variable, isNegated = true) check not positiveLit.isNegated check negativeLit.isNegated # Test negation let doubleNegated = negate(negativeLit) check not doubleNegated.isNegated test "Create clause": ## Test creating a clause from literals ## Requirements: 6.1 let var1 = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: newVariantProfile() ) let var2 = BoolVar( package: "zlib", version: SemanticVersion(major: 1, minor: 2, patch: 13), variant: newVariantProfile() ) let clause = makeClause( @[ makeLiteral(var1, isNegated = true), makeLiteral(var2, isNegated = false) ], reason = "nginx depends on zlib" ) check clause.literals.len == 2 check clause.literals[0].isNegated check not clause.literals[1].isNegated check clause.reason == "nginx depends on zlib" test "Create CNF formula": ## Test creating an empty CNF formula ## Requirements: 6.1 var formula = newCNFFormula() check formula.clauses.len == 0 check formula.variables.len() == 0 check formula.nextVarId == 1 test "Register variables in formula": ## Test variable registration and ID assignment ## Requirements: 6.1 var formula = newCNFFormula() let var1 = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: newVariantProfile() ) let var2 = BoolVar( package: "zlib", version: SemanticVersion(major: 1, minor: 2, patch: 13), variant: newVariantProfile() ) let id1 = formula.getOrCreateVarId(var1) let id2 = formula.getOrCreateVarId(var2) let id1Again = formula.getOrCreateVarId(var1) check id1 == 1 check id2 == 2 check id1Again == 1 # Same variable, same ID check formula.variables.len() == 2 test "Translate dependency to implication clause": ## Test translating "A depends on B" to "¬A ∨ B" ## Requirements: 6.2 - Create implication clauses (A → B) var formula = newCNFFormula() let clause = formula.translateDependency( dependent = "nginx", dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0), dependentVariant = newVariantProfile(), dependency = "zlib", dependencyVersion = SemanticVersion(major: 1, minor: 2, patch: 13), dependencyVariant = newVariantProfile() ) # Check clause structure: ¬A ∨ B check clause.literals.len == 2 check clause.literals[0].isNegated # ¬nginx check not clause.literals[1].isNegated # zlib check clause.literals[0].variable.package == "nginx" check clause.literals[1].variable.package == "zlib" # Check formula was updated check formula.clauses.len == 1 check formula.variables.len() == 2 test "Translate multiple dependencies": ## Test translating multiple dependencies ## Requirements: 6.2 var formula = newCNFFormula() # nginx depends on zlib discard formula.translateDependency( dependent = "nginx", dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0), dependentVariant = newVariantProfile(), dependency = "zlib", dependencyVersion = SemanticVersion(major: 1, minor: 2, patch: 13), dependencyVariant = newVariantProfile() ) # nginx depends on pcre discard formula.translateDependency( dependent = "nginx", dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0), dependentVariant = newVariantProfile(), dependency = "pcre", dependencyVersion = SemanticVersion(major: 8, minor: 45, patch: 0), dependencyVariant = newVariantProfile() ) check formula.clauses.len == 2 check formula.variables.len() == 3 # nginx, zlib, pcre test "Translate mutual exclusion": ## Test translating "A and B are mutually exclusive" to "¬A ∨ ¬B" ## Requirements: 6.3 - Create mutual exclusion clauses (¬(A ∧ B)) var formula = newCNFFormula() var variantWayland = newVariantProfile() variantWayland.addFlag("display", "wayland") variantWayland.calculateHash() var variantX11 = newVariantProfile() variantX11.addFlag("display", "x11") variantX11.calculateHash() let clause = formula.translateExclusivity( packageA = "firefox", versionA = SemanticVersion(major: 120, minor: 0, patch: 0), variantA = variantWayland, packageB = "firefox", versionB = SemanticVersion(major: 120, minor: 0, patch: 0), variantB = variantX11, reason = "wayland and x11 are mutually exclusive" ) # Check clause structure: ¬A ∨ ¬B check clause.literals.len == 2 check clause.literals[0].isNegated # ¬firefox+wayland check clause.literals[1].isNegated # ¬firefox+x11 check clause.literals[0].variable.package == "firefox" check clause.literals[1].variable.package == "firefox" # Check formula was updated check formula.clauses.len == 1 check formula.variables.len() == 2 test "Translate variant satisfaction": ## Test translating variant satisfaction constraints ## Requirements: 6.4 - Create satisfaction clauses var formula = newCNFFormula() var requiredVariant = newVariantProfile() requiredVariant.addFlag("optimization", "lto") requiredVariant.calculateHash() var availableVariant = newVariantProfile() availableVariant.addFlag("optimization", "lto") availableVariant.addFlag("security", "hardened") availableVariant.calculateHash() let clause = formula.translateVariantSatisfaction( package = "nginx", version = SemanticVersion(major: 1, minor: 24, patch: 0), requiredVariant = requiredVariant, availableVariant = availableVariant ) # Available variant satisfies required, so: ¬required ∨ available check clause.literals.len == 2 check clause.literals[0].isNegated # ¬required check not clause.literals[1].isNegated # available # Check formula was updated check formula.clauses.len == 1 check formula.variables.len() == 2 test "Translate variant non-satisfaction": ## Test translating variant that doesn't satisfy requirements ## Requirements: 6.4 var formula = newCNFFormula() var requiredVariant = newVariantProfile() requiredVariant.addFlag("optimization", "lto") requiredVariant.addFlag("security", "hardened") requiredVariant.calculateHash() var availableVariant = newVariantProfile() availableVariant.addFlag("optimization", "lto") # Missing "security: hardened" availableVariant.calculateHash() let clause = formula.translateVariantSatisfaction( package = "nginx", version = SemanticVersion(major: 1, minor: 24, patch: 0), requiredVariant = requiredVariant, availableVariant = availableVariant ) # Available doesn't satisfy required, so: ¬required ∨ ¬available check clause.literals.len == 2 check clause.literals[0].isNegated # ¬required check clause.literals[1].isNegated # ¬available (incompatible) # Check formula was updated check formula.clauses.len == 1 check formula.variables.len() == 2 test "Translate root requirement": ## Test translating user requirements to unit clauses ## Requirements: 6.1 var formula = newCNFFormula() var variant = newVariantProfile() variant.addFlag("optimization", "lto") variant.calculateHash() let clause = formula.translateRootRequirement( package = "nginx", version = SemanticVersion(major: 1, minor: 24, patch: 0), variant = variant ) # Root requirement is a unit clause: P check clause.literals.len == 1 check not clause.literals[0].isNegated check clause.literals[0].variable.package == "nginx" # Check formula was updated check formula.clauses.len == 1 check formula.variables.len() == 1 test "Translate incompatibility": ## Test translating an incompatibility to CNF ## Requirements: 6.1, 6.2, 6.3 var formula = newCNFFormula() let incomp = createDependencyIncompatibility( dependent = "nginx", dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0), dependency = "zlib", dependencyConstraint = Constraint( versionRange: VersionConstraint( operator: OpGreaterEq, version: SemanticVersion(major: 1, minor: 2, patch: 0) ), variantReq: newVariantProfile(), isNegative: false ) ) let clause = formula.translateIncompatibility(incomp) # Incompatibility should be translated to a clause check clause.literals.len == 2 check formula.clauses.len == 1 test "Validate CNF formula": ## Test CNF formula validation ## Requirements: 6.5 - CNF is ready for CDCL solving var formula = newCNFFormula() # Empty formula is invalid check not formula.isValidCNF() # Add a valid clause discard formula.translateRootRequirement( package = "nginx", version = SemanticVersion(major: 1, minor: 24, patch: 0), variant = newVariantProfile() ) # Now formula is valid check formula.isValidCNF() test "Complex CNF formula": ## Test building a complex CNF formula with multiple clause types ## Requirements: 6.1, 6.2, 6.3, 6.4, 6.5 var formula = newCNFFormula() # Root requirement: user wants nginx discard formula.translateRootRequirement( package = "nginx", version = SemanticVersion(major: 1, minor: 24, patch: 0), variant = newVariantProfile() ) # Dependency: nginx depends on zlib discard formula.translateDependency( dependent = "nginx", dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0), dependentVariant = newVariantProfile(), dependency = "zlib", dependencyVersion = SemanticVersion(major: 1, minor: 2, patch: 13), dependencyVariant = newVariantProfile() ) # Dependency: nginx depends on pcre discard formula.translateDependency( dependent = "nginx", dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0), dependentVariant = newVariantProfile(), dependency = "pcre", dependencyVersion = SemanticVersion(major: 8, minor: 45, patch: 0), dependencyVariant = newVariantProfile() ) # Exclusivity: wayland and x11 are mutually exclusive var variantWayland = newVariantProfile() variantWayland.addFlag("display", "wayland") variantWayland.calculateHash() var variantX11 = newVariantProfile() variantX11.addFlag("display", "x11") variantX11.calculateHash() discard formula.translateExclusivity( packageA = "firefox", versionA = SemanticVersion(major: 120, minor: 0, patch: 0), variantA = variantWayland, packageB = "firefox", versionB = SemanticVersion(major: 120, minor: 0, patch: 0), variantB = variantX11 ) # Check formula structure check formula.clauses.len == 4 check formula.variables.len() >= 4 check formula.isValidCNF() # Test string representation let formulaStr = $formula check formulaStr.contains("CNF Formula") check formulaStr.contains("4 clauses") test "Variable equality and hashing": ## Test that boolean variables can be used in hash tables ## Requirements: 6.1 var variant1 = newVariantProfile() variant1.addFlag("optimization", "lto") variant1.calculateHash() var variant2 = newVariantProfile() variant2.addFlag("optimization", "lto") variant2.calculateHash() let var1 = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: variant1 ) let var2 = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: variant2 ) let var3 = BoolVar( package: "zlib", version: SemanticVersion(major: 1, minor: 2, patch: 13), variant: newVariantProfile() ) # Same package+version+variant should be equal check var1 == var2 check hash(var1) == hash(var2) # Different package should not be equal check var1 != var3 test "Clause string representation": ## Test that clauses have readable string representations ## Requirements: 6.5 let var1 = BoolVar( package: "nginx", version: SemanticVersion(major: 1, minor: 24, patch: 0), variant: newVariantProfile() ) let var2 = BoolVar( package: "zlib", version: SemanticVersion(major: 1, minor: 2, patch: 13), variant: newVariantProfile() ) let clause = makeClause( @[ makeLiteral(var1, isNegated = true), makeLiteral(var2, isNegated = false) ], reason = "nginx depends on zlib" ) let clauseStr = $clause check clauseStr.contains("nginx") check clauseStr.contains("zlib") check clauseStr.contains("¬") # Negation symbol check clauseStr.contains("∨") # OR symbol check clauseStr.contains("nginx depends on zlib")