204 lines
7.7 KiB
Nim
204 lines
7.7 KiB
Nim
## Property-Based Tests for Build Synthesis
|
|
##
|
|
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
|
|
## **Validates: Requirements 8.1, 8.2, 8.3**
|
|
##
|
|
## Property: For any build configuration, calculating the build hash multiple times
|
|
## produces the same result. This ensures reproducibility - same inputs always
|
|
## produce same outputs.
|
|
|
|
import std/[unittest, random, strutils, sequtils, times]
|
|
import ../src/nip/resolver/build_synthesis
|
|
import ../src/nip/resolver/variant_types
|
|
|
|
# Generator for random variant profiles
|
|
proc generateRandomVariantProfile(): VariantProfile =
|
|
var profile = newVariantProfile()
|
|
|
|
# Add random domains
|
|
let domains = @["optimization", "security", "features", "network"]
|
|
let flags = @["lto", "hardened", "wayland", "vulkan", "ipv6", "ssl", "zstd"]
|
|
|
|
for domain in domains:
|
|
if rand(0..1) == 0: # 50% chance to include domain
|
|
let numFlags = rand(1..3)
|
|
for _ in 0..<numFlags:
|
|
let flag = flags[rand(0..flags.len-1)]
|
|
profile.addFlag(domain, flag)
|
|
|
|
profile.calculateHash()
|
|
return profile
|
|
|
|
# Generator for random build configs
|
|
proc generateRandomBuildConfig(): BuildConfig =
|
|
let packageNames = @["nginx", "postgresql", "redis", "openssl", "zlib"]
|
|
let versions = @["1.0.0", "2.1.0", "3.0.0", "1.24.0", "15.3"]
|
|
let compilers = @["gcc-13.2.0", "clang-17.0.0", "gcc-12.0.0"]
|
|
let archs = @["x86_64", "aarch64", "riscv64"]
|
|
let libcs = @["musl", "glibc"]
|
|
let allocators = @["jemalloc", "tcmalloc", "default"]
|
|
|
|
let profile = generateRandomVariantProfile()
|
|
|
|
let config = newBuildConfig(
|
|
packageName = packageNames[rand(0..packageNames.len-1)],
|
|
packageVersion = versions[rand(0..versions.len-1)],
|
|
variantProfile = profile,
|
|
sourceHash = "blake3-" & toHex(rand(0..0xFFFFFF)),
|
|
compilerVersion = compilers[rand(0..compilers.len-1)],
|
|
compilerFlags = @["-O2", "-march=native"],
|
|
configureFlags = @["--with-ssl"],
|
|
targetArchitecture = archs[rand(0..archs.len-1)],
|
|
libc = libcs[rand(0..libcs.len-1)],
|
|
allocator = allocators[rand(0..allocators.len-1)]
|
|
)
|
|
|
|
return config
|
|
|
|
suite "Build Synthesis Property Tests":
|
|
|
|
test "Property 7: Build Determinism - Hash calculation is deterministic":
|
|
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
|
|
## **Validates: Requirements 8.1, 8.2, 8.3**
|
|
##
|
|
## For any build configuration, calculating the build hash multiple times
|
|
## produces the same result. This ensures reproducibility.
|
|
##
|
|
## Property: ∀ config ∈ BuildConfig: calculateBuildHash(config) = calculateBuildHash(config)
|
|
|
|
# Run property test with 100 iterations
|
|
for iteration in 0..<100:
|
|
# Generate random build config
|
|
let config = generateRandomBuildConfig()
|
|
|
|
# Calculate hash multiple times
|
|
let hash1 = calculateBuildHash(config)
|
|
let hash2 = calculateBuildHash(config)
|
|
let hash3 = calculateBuildHash(config)
|
|
|
|
# All hashes should be identical
|
|
check hash1 == hash2
|
|
check hash2 == hash3
|
|
check hash1 == hash3
|
|
|
|
# Hash should have correct format
|
|
check hash1.startsWith("xxh3-")
|
|
|
|
test "Property 7: Build Determinism - Synthesis produces deterministic hashes":
|
|
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
|
|
## **Validates: Requirements 8.1, 8.2, 8.3**
|
|
##
|
|
## For any variant profile, synthesizing a build multiple times produces
|
|
## the same build hash. This ensures reproducibility across builds.
|
|
##
|
|
## Property: ∀ profile ∈ VariantProfile: synthesizeBuild(..., profile).buildHash = synthesizeBuild(..., profile).buildHash
|
|
|
|
# Run property test with 100 iterations
|
|
for iteration in 0..<100:
|
|
# Generate random variant profile
|
|
let profile = generateRandomVariantProfile()
|
|
|
|
# Synthesize builds multiple times
|
|
let result1 = synthesizeBuild(
|
|
packageName = "test-package",
|
|
packageVersion = "1.0.0",
|
|
variantProfile = profile,
|
|
sourceHash = "blake3-abc123"
|
|
)
|
|
|
|
let result2 = synthesizeBuild(
|
|
packageName = "test-package",
|
|
packageVersion = "1.0.0",
|
|
variantProfile = profile,
|
|
sourceHash = "blake3-abc123"
|
|
)
|
|
|
|
let result3 = synthesizeBuild(
|
|
packageName = "test-package",
|
|
packageVersion = "1.0.0",
|
|
variantProfile = profile,
|
|
sourceHash = "blake3-abc123"
|
|
)
|
|
|
|
# All build hashes should be identical
|
|
check result1.buildHash == result2.buildHash
|
|
check result2.buildHash == result3.buildHash
|
|
check result1.buildHash == result3.buildHash
|
|
|
|
test "Property 7: Build Determinism - Canonical representation is deterministic":
|
|
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
|
|
## **Validates: Requirements 8.1, 8.2, 8.3**
|
|
##
|
|
## For any build configuration, the canonical representation is deterministic.
|
|
## This ensures that the same configuration always produces the same hash.
|
|
##
|
|
## Property: ∀ config ∈ BuildConfig: toCanonical(config) = toCanonical(config)
|
|
|
|
# Run property test with 100 iterations
|
|
for iteration in 0..<100:
|
|
# Generate random build config
|
|
let config = generateRandomBuildConfig()
|
|
|
|
# Get canonical representation multiple times
|
|
let canonical1 = config.toCanonical()
|
|
let canonical2 = config.toCanonical()
|
|
let canonical3 = config.toCanonical()
|
|
|
|
# All canonical representations should be identical
|
|
check canonical1 == canonical2
|
|
check canonical2 == canonical3
|
|
check canonical1 == canonical3
|
|
|
|
test "Property 7: Build Determinism - Hash verification is consistent":
|
|
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
|
|
## **Validates: Requirements 8.1, 8.2, 8.3**
|
|
##
|
|
## For any build configuration and its hash, verification always produces
|
|
## the same result. This ensures consistent verification.
|
|
##
|
|
## Property: ∀ config ∈ BuildConfig, hash = calculateBuildHash(config):
|
|
## verifyBuildHash(hash, config) = verifyBuildHash(hash, config)
|
|
|
|
# Run property test with 100 iterations
|
|
for iteration in 0..<100:
|
|
# Generate random build config
|
|
let config = generateRandomBuildConfig()
|
|
|
|
# Calculate hash
|
|
let hash = calculateBuildHash(config)
|
|
|
|
# Verify hash multiple times
|
|
let verify1 = verifyBuildHash(hash, config)
|
|
let verify2 = verifyBuildHash(hash, config)
|
|
let verify3 = verifyBuildHash(hash, config)
|
|
|
|
# All verifications should be identical and true
|
|
check verify1 == verify2
|
|
check verify2 == verify3
|
|
check verify1 == true
|
|
|
|
test "Property 7: Build Determinism - Different configs produce different hashes":
|
|
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
|
|
## **Validates: Requirements 8.1, 8.2, 8.3**
|
|
##
|
|
## For any two different build configurations, they should produce different
|
|
## hashes (with very high probability). This ensures uniqueness.
|
|
##
|
|
## Property: ∀ config1, config2 ∈ BuildConfig where config1 ≠ config2:
|
|
## calculateBuildHash(config1) ≠ calculateBuildHash(config2)
|
|
|
|
# Run property test with 50 iterations (fewer because we need pairs)
|
|
for iteration in 0..<50:
|
|
# Generate two random build configs
|
|
let config1 = generateRandomBuildConfig()
|
|
let config2 = generateRandomBuildConfig()
|
|
|
|
# Calculate hashes
|
|
let hash1 = calculateBuildHash(config1)
|
|
let hash2 = calculateBuildHash(config2)
|
|
|
|
# If configs are different, hashes should be different
|
|
# (with very high probability - hash collisions are extremely rare)
|
|
if config1.toCanonical() != config2.toCanonical():
|
|
check hash1 != hash2
|