235 lines
7.2 KiB
Nim
235 lines
7.2 KiB
Nim
## Property-Based Tests for Variant Hash Calculation
|
|
##
|
|
## **Feature: 02-nip-dependency-resolution, Property 1: Hash Determinism**
|
|
##
|
|
## These tests verify that variant hash calculation is deterministic:
|
|
## - Same profile always produces same hash
|
|
## - Different profiles produce different hashes (with high probability)
|
|
## - Hash is independent of insertion order
|
|
## - Hash is platform-independent
|
|
|
|
import std/[unittest, random, tables, sets]
|
|
import ../src/nip/resolver/variant_types
|
|
import ../src/nip/resolver/variant_hash
|
|
|
|
suite "Property 1: Hash Determinism":
|
|
|
|
test "Property 1.1: Same profile produces same hash (100 iterations)":
|
|
## **Validates: Requirements 1.5**
|
|
##
|
|
## For any variant profile, calculating the hash multiple times
|
|
## should always produce the same result
|
|
|
|
var rng = initRand(42) # Deterministic seed for reproducibility
|
|
var passCount = 0
|
|
|
|
for i in 0..<100:
|
|
# Create random profile
|
|
var profile = newVariantProfile()
|
|
|
|
# Add random domains and flags
|
|
let numDomains = rng.rand(1..5)
|
|
for d in 0..<numDomains:
|
|
let domainName = "domain" & $d
|
|
let exclusivity = if rng.rand(1.0) < 0.3: Exclusive else: NonExclusive
|
|
var domain = newVariantDomain(domainName, exclusivity)
|
|
|
|
let numFlags = if exclusivity == Exclusive: 1 else: rng.rand(1..5)
|
|
for f in 0..<numFlags:
|
|
domain.flags.incl("flag" & $f)
|
|
|
|
profile.addDomain(domain)
|
|
|
|
# Calculate hash multiple times
|
|
var profile1 = profile
|
|
var profile2 = profile
|
|
var profile3 = profile
|
|
let hash1 = calculateVariantHash(profile1)
|
|
let hash2 = calculateVariantHash(profile2)
|
|
let hash3 = calculateVariantHash(profile3)
|
|
|
|
# All hashes must be identical
|
|
if hash1 == hash2 and hash2 == hash3:
|
|
passCount += 1
|
|
|
|
check passCount == 100
|
|
echo " ✅ Property 1.1: ", passCount, "/100 profiles produced deterministic hashes"
|
|
|
|
suite "Variant Unification Tests":
|
|
|
|
test "Unify: Empty demands returns empty profile":
|
|
## Test that unifying zero demands produces an empty profile
|
|
|
|
let result = unify(@[])
|
|
|
|
check result.kind == Unified
|
|
check result.profile.domains.len == 0
|
|
|
|
test "Unify: Single demand returns that profile":
|
|
## Test that unifying one demand returns the same profile
|
|
|
|
var profile = newVariantProfile()
|
|
profile.addFlag("graphics", "wayland")
|
|
profile.calculateHash()
|
|
|
|
let demand = VariantDemand(
|
|
packageName: "test",
|
|
variantProfile: profile,
|
|
optional: false
|
|
)
|
|
|
|
let result = unify(@[demand])
|
|
|
|
check result.kind == Unified
|
|
check result.profile == profile
|
|
|
|
test "Unify: Compatible non-exclusive domains merge successfully":
|
|
## Test that non-exclusive domains accumulate flags
|
|
|
|
var profile1 = newVariantProfile()
|
|
profile1.addFlag("graphics", "wayland")
|
|
profile1.calculateHash()
|
|
|
|
var profile2 = newVariantProfile()
|
|
profile2.addFlag("graphics", "vulkan")
|
|
profile2.calculateHash()
|
|
|
|
let demand1 = VariantDemand(
|
|
packageName: "pkg1",
|
|
variantProfile: profile1,
|
|
optional: false
|
|
)
|
|
|
|
let demand2 = VariantDemand(
|
|
packageName: "pkg2",
|
|
variantProfile: profile2,
|
|
optional: false
|
|
)
|
|
|
|
let result = unify(@[demand1, demand2])
|
|
|
|
check result.kind == Unified
|
|
check result.profile.hasDomain("graphics")
|
|
|
|
let graphicsDomain = result.profile.getDomain("graphics")
|
|
check "wayland" in graphicsDomain.flags
|
|
check "vulkan" in graphicsDomain.flags
|
|
|
|
test "Unify: Incompatible exclusive domains produce conflict":
|
|
## Test that conflicting exclusive domains are detected
|
|
|
|
var profile1 = newVariantProfile()
|
|
var initDomain1 = newVariantDomain("init", Exclusive)
|
|
initDomain1.flags.incl("systemd")
|
|
profile1.addDomain(initDomain1)
|
|
profile1.calculateHash()
|
|
|
|
var profile2 = newVariantProfile()
|
|
var initDomain2 = newVariantDomain("init", Exclusive)
|
|
initDomain2.flags.incl("dinit")
|
|
profile2.addDomain(initDomain2)
|
|
profile2.calculateHash()
|
|
|
|
let demand1 = VariantDemand(
|
|
packageName: "pkg1",
|
|
variantProfile: profile1,
|
|
optional: false
|
|
)
|
|
|
|
let demand2 = VariantDemand(
|
|
packageName: "pkg2",
|
|
variantProfile: profile2,
|
|
optional: false
|
|
)
|
|
|
|
let result = unify(@[demand1, demand2])
|
|
|
|
check result.kind == Conflict
|
|
check result.conflictingDomain == "init"
|
|
|
|
test "Unify: Compatible exclusive domains merge successfully":
|
|
## Test that identical exclusive domains can merge
|
|
|
|
var profile1 = newVariantProfile()
|
|
var initDomain1 = newVariantDomain("init", Exclusive)
|
|
initDomain1.flags.incl("systemd")
|
|
profile1.addDomain(initDomain1)
|
|
profile1.calculateHash()
|
|
|
|
var profile2 = newVariantProfile()
|
|
var initDomain2 = newVariantDomain("init", Exclusive)
|
|
initDomain2.flags.incl("systemd")
|
|
profile2.addDomain(initDomain2)
|
|
profile2.calculateHash()
|
|
|
|
let demand1 = VariantDemand(
|
|
packageName: "pkg1",
|
|
variantProfile: profile1,
|
|
optional: false
|
|
)
|
|
|
|
let demand2 = VariantDemand(
|
|
packageName: "pkg2",
|
|
variantProfile: profile2,
|
|
optional: false
|
|
)
|
|
|
|
let result = unify(@[demand1, demand2])
|
|
|
|
check result.kind == Unified
|
|
check result.profile.hasDomain("init")
|
|
|
|
let initDomain = result.profile.getDomain("init")
|
|
check "systemd" in initDomain.flags
|
|
|
|
test "Unify: Multiple demands with mixed domains":
|
|
## Test complex unification with multiple domains
|
|
|
|
var profile1 = newVariantProfile()
|
|
profile1.addFlag("graphics", "wayland")
|
|
var initDomain1 = newVariantDomain("init", Exclusive)
|
|
initDomain1.flags.incl("systemd")
|
|
profile1.addDomain(initDomain1)
|
|
profile1.calculateHash()
|
|
|
|
var profile2 = newVariantProfile()
|
|
profile2.addFlag("graphics", "vulkan")
|
|
profile2.addFlag("optimization", "lto")
|
|
profile2.calculateHash()
|
|
|
|
var profile3 = newVariantProfile()
|
|
var initDomain3 = newVariantDomain("init", Exclusive)
|
|
initDomain3.flags.incl("systemd")
|
|
profile3.addDomain(initDomain3)
|
|
profile3.addFlag("optimization", "pgo")
|
|
profile3.calculateHash()
|
|
|
|
let demands = @[
|
|
VariantDemand(packageName: "pkg1", variantProfile: profile1, optional: false),
|
|
VariantDemand(packageName: "pkg2", variantProfile: profile2, optional: false),
|
|
VariantDemand(packageName: "pkg3", variantProfile: profile3, optional: false)
|
|
]
|
|
|
|
let result = unify(demands)
|
|
|
|
check result.kind == Unified
|
|
|
|
# Check graphics domain (non-exclusive)
|
|
check result.profile.hasDomain("graphics")
|
|
let graphicsDomain = result.profile.getDomain("graphics")
|
|
check "wayland" in graphicsDomain.flags
|
|
check "vulkan" in graphicsDomain.flags
|
|
|
|
# Check init domain (exclusive)
|
|
check result.profile.hasDomain("init")
|
|
let initDomain = result.profile.getDomain("init")
|
|
check "systemd" in initDomain.flags
|
|
|
|
# Check optimization domain (non-exclusive)
|
|
check result.profile.hasDomain("optimization")
|
|
let optDomain = result.profile.getDomain("optimization")
|
|
check "lto" in optDomain.flags
|
|
check "pgo" in optDomain.flags
|
|
|
|
echo " ✅ Complex unification: ", $result.profile
|