## test_variant_fingerprint.nim ## Tests for variant fingerprint calculation ## Ensures determinism, uniqueness, and correctness import std/[unittest, tables, strutils] import ../src/nimpak/variant_fingerprint import ../src/nimpak/variant_types import ../src/nimpak/config suite "Variant Fingerprint Tests": # Test data let testToolchain = newToolchainInfo("gcc", "13.2.0") let testTarget = newTargetInfo("x86_64", "linux") let testCompilerFlags = CompilerFlags( cflags: "-O2 -pipe", cxxflags: "-O2 -pipe", ldflags: "-Wl,-O1", makeflags: "-j4" ) test "Same inputs produce identical fingerprints": # Create two identical configurations var domains1 = initTable[string, seq[string]]() domains1["init"] = @["dinit"] domains1["security"] = @["pie", "relro"] domains1["optimization"] = @["lto"] var domains2 = initTable[string, seq[string]]() domains2["init"] = @["dinit"] domains2["security"] = @["pie", "relro"] domains2["optimization"] = @["lto"] let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget ) check fp1 == fp2 check fp1.len == 20 # "blake2b-" + 12 chars check fp1.startsWith("blake2b-") test "Different package names produce different fingerprints": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "apache", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) check fp1 != fp2 test "Different versions produce different fingerprints": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.29.0", domains, testCompilerFlags, testToolchain, testTarget ) check fp1 != fp2 test "Different domain flags produce different fingerprints": var domains1 = initTable[string, seq[string]]() domains1["init"] = @["dinit"] domains1["security"] = @["pie", "relro"] var domains2 = initTable[string, seq[string]]() domains2["init"] = @["systemd"] domains2["security"] = @["pie", "relro"] let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget ) check fp1 != fp2 test "Flag order independence - same flags different order produce same fingerprint": # Test that domain flags are sorted before hashing var domains1 = initTable[string, seq[string]]() domains1["security"] = @["pie", "relro", "hardened"] domains1["optimization"] = @["lto", "pgo"] domains1["init"] = @["dinit"] var domains2 = initTable[string, seq[string]]() domains2["init"] = @["dinit"] domains2["optimization"] = @["lto", "pgo"] domains2["security"] = @["pie", "relro", "hardened"] let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget ) check fp1 == fp2 test "Value order within domain affects fingerprint": # Different order of values within a domain should produce same result # because values are sorted var domains1 = initTable[string, seq[string]]() domains1["security"] = @["relro", "pie", "hardened"] var domains2 = initTable[string, seq[string]]() domains2["security"] = @["pie", "relro", "hardened"] let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget ) check fp1 == fp2 test "Different compiler flags produce different fingerprints": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] let flags1 = CompilerFlags( cflags: "-O2 -pipe", cxxflags: "-O2 -pipe", ldflags: "-Wl,-O1", makeflags: "-j4" ) let flags2 = CompilerFlags( cflags: "-O3 -march=native", cxxflags: "-O3 -march=native", ldflags: "-Wl,-O1", makeflags: "-j4" ) let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains, flags1, testToolchain, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains, flags2, testToolchain, testTarget ) check fp1 != fp2 test "Different toolchains produce different fingerprints": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] let toolchain1 = newToolchainInfo("gcc", "13.2.0") let toolchain2 = newToolchainInfo("clang", "17.0.0") let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, toolchain1, testTarget ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, toolchain2, testTarget ) check fp1 != fp2 test "Different targets produce different fingerprints": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] let target1 = newTargetInfo("x86_64", "linux") let target2 = newTargetInfo("aarch64", "linux") let fp1 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, target1 ) let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, target2 ) check fp1 != fp2 test "buildVariantFingerprint creates complete object": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] domains["security"] = @["pie", "relro"] let fp = buildVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) check fp.packageName == "nginx" check fp.version == "1.28.0" check fp.domainFlags == domains check fp.compilerFlags.cflags == testCompilerFlags.cflags check fp.toolchain == testToolchain check fp.target == testTarget check fp.hash.len == 20 check fp.hash.startsWith("blake2b-") test "isValidFingerprint validates format correctly": check isValidFingerprint("blake2b-abc123def456") == true check isValidFingerprint("blake2b-ABC123DEF456") == true check isValidFingerprint("blake2b-0123456789ab") == true # Invalid cases check isValidFingerprint("blake2b-") == false check isValidFingerprint("blake2b-abc") == false # Too short check isValidFingerprint("blake2b-abc123def456xyz") == false # Too long check isValidFingerprint("blake3-abc123def456") == false # Wrong prefix check isValidFingerprint("abc123def456") == false # No prefix check isValidFingerprint("blake2b-xyz123def456") == false # Invalid hex test "extractFingerprintPrefix extracts 12-char prefix": let fullHash = "blake2b-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" let prefix = extractFingerprintPrefix(fullHash) check prefix == "blake2b-0123456789ab" check prefix.len == 20 test "getFingerprintInputString returns deterministic input": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] domains["security"] = @["pie", "relro"] let input = getFingerprintInputString( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) check input.contains("nginx") check input.contains("1.28.0") check input.contains("init:dinit") check input.contains("security:") check input.contains("pie") check input.contains("relro") check input.contains("cflags:-O2 -pipe") check input.contains("toolchain:gcc-13.2.0") check input.contains("target:x86_64-linux") test "Empty domains produce valid fingerprint": var domains = initTable[string, seq[string]]() let fp = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) check fp.len == 20 check fp.startsWith("blake2b-") test "Multiple domains with multiple values": var domains = initTable[string, seq[string]]() domains["init"] = @["dinit"] domains["runtime"] = @["ssl", "http3", "zstd", "ipv6"] domains["security"] = @["pie", "relro", "hardened"] domains["optimization"] = @["lto", "march-native"] domains["network"] = @["ipv6", "wireguard"] let fp = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) check fp.len == 20 check fp.startsWith("blake2b-") # Verify it's deterministic let fp2 = calculateVariantFingerprint( "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget ) check fp == fp2 when isMainModule: echo "Running variant fingerprint tests..."