## Tests for Resolution Cache with CAS Integration ## ## This test suite verifies: ## - L1 (in-memory) cache operations ## - L2 (CAS) cache operations ## - Cache invalidation on repo state changes ## - Cache metrics and hit rates ## - Disabled cache behavior import unittest import options import tables import ../src/nip/resolver/resolution_cache import ../src/nip/resolver/types import ../src/nip/cas/storage suite "Resolution Cache Construction": test "Create cache with default settings": let cas = newCASStorage("/tmp/test-cas-1") let cache = newResolutionCache(cas) check cache.isEnabled check cache.l1Capacity == 100 test "Create cache with custom capacity": let cas = newCASStorage("/tmp/test-cas-2") let cache = newResolutionCache(cas, l1Capacity = 50) check cache.l1Capacity == 50 test "Create disabled cache": let cas = newCASStorage("/tmp/test-cas-3") let cache = newResolutionCache(cas, enabled = false) check not cache.isEnabled suite "L1 Cache Operations": test "Cache miss on empty cache": let cas = newCASStorage("/tmp/test-cas-4") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let result = cache.get(key) check result.value.isNone check result.source == CacheMiss test "Put and get from L1 cache": let cas = newCASStorage("/tmp/test-cas-5") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key, graph) let result = cache.get(key) check result.value.isSome check result.source == L1Hit check result.value.get.rootPackage.name == "nginx" test "Multiple entries in L1 cache": let cas = newCASStorage("/tmp/test-cas-6") let cache = newResolutionCache(cas) let key1 = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let key2 = CacheKey( rootPackage: "apache", rootConstraint: ">=2.4.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph1 = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) let graph2 = DependencyGraph( rootPackage: PackageId(name: "apache", version: "2.4.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key1, graph1) cache.put(key2, graph2) let result1 = cache.get(key1) let result2 = cache.get(key2) check result1.value.isSome check result1.source == L1Hit check result1.value.get.rootPackage.name == "nginx" check result2.value.isSome check result2.source == L1Hit check result2.value.get.rootPackage.name == "apache" test "Different variant demands produce different cache keys": let cas = newCASStorage("/tmp/test-cas-7") let cache = newResolutionCache(cas) let key1 = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @["ssl"], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let key2 = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @["ssl", "http2"], # Different USE flags libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph1 = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "ssl"), nodes: @[], timestamp: 1700000000 ) let graph2 = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "ssl-http2"), nodes: @[], timestamp: 1700000000 ) cache.put(key1, graph1) cache.put(key2, graph2) let result1 = cache.get(key1) let result2 = cache.get(key2) check result1.value.get.rootPackage.variant == "ssl" check result2.value.get.rootPackage.variant == "ssl-http2" suite "Cache Invalidation": test "Invalidate specific entry": let cas = newCASStorage("/tmp/test-cas-8") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key, graph) check cache.get(key).value.isSome cache.invalidate(key) check cache.get(key).value.isNone test "Clear all L1 entries": let cas = newCASStorage("/tmp/test-cas-9") let cache = newResolutionCache(cas) let key1 = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let key2 = CacheKey( rootPackage: "apache", rootConstraint: ">=2.4.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph1 = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) let graph2 = DependencyGraph( rootPackage: PackageId(name: "apache", version: "2.4.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key1, graph1) cache.put(key2, graph2) cache.clear() check cache.get(key1).value.isNone check cache.get(key2).value.isNone test "Update repo hash invalidates cache": let cas = newCASStorage("/tmp/test-cas-10") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.updateRepoHash("hash123") cache.put(key, graph) check cache.get(key).value.isSome # Update repo hash (simulates metadata change) cache.updateRepoHash("hash456") # Cache should be invalidated check cache.get(key).value.isNone test "Same repo hash doesn't invalidate cache": let cas = newCASStorage("/tmp/test-cas-11") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.updateRepoHash("hash123") cache.put(key, graph) check cache.get(key).value.isSome # Update with same hash cache.updateRepoHash("hash123") # Cache should still be valid check cache.get(key).value.isSome suite "Disabled Cache Behavior": test "Disabled cache returns miss": let cas = newCASStorage("/tmp/test-cas-12") let cache = newResolutionCache(cas, enabled = false) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key, graph) let result = cache.get(key) check result.value.isNone check result.source == CacheMiss test "Enable and disable cache": let cas = newCASStorage("/tmp/test-cas-13") let cache = newResolutionCache(cas, enabled = true) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) # Enabled: should cache cache.put(key, graph) check cache.get(key).value.isSome # Disable: should return miss cache.setEnabled(false) check cache.get(key).value.isNone # Re-enable: should still have cached value cache.setEnabled(true) check cache.get(key).value.isSome suite "Cache Metrics": test "Track L1 hits": let cas = newCASStorage("/tmp/test-cas-14") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key, graph) discard cache.get(key) # Hit discard cache.get(key) # Hit let metrics = cache.getMetrics() check metrics.l1Hits == 2 check metrics.misses == 0 test "Track cache misses": let cas = newCASStorage("/tmp/test-cas-15") let cache = newResolutionCache(cas) let key1 = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let key2 = CacheKey( rootPackage: "apache", rootConstraint: ">=2.4.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) discard cache.get(key1) # Miss discard cache.get(key2) # Miss let metrics = cache.getMetrics() check metrics.l1Hits == 0 check metrics.misses == 2 test "Calculate hit rate": let cas = newCASStorage("/tmp/test-cas-16") let cache = newResolutionCache(cas) let key = CacheKey( rootPackage: "nginx", rootConstraint: ">=1.24.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.put(key, graph) discard cache.get(key) # Hit discard cache.get(key) # Hit discard cache.get(key) # Hit let key2 = CacheKey( rootPackage: "apache", rootConstraint: ">=2.4.0", repoStateHash: "hash123", variantDemand: VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) discard cache.get(key2) # Miss let metrics = cache.getMetrics() check metrics.l1HitRate == 0.75 # 3 hits / 4 total suite "Convenience Methods": test "getCached with individual parameters": let cas = newCASStorage("/tmp/test-cas-17") let cache = newResolutionCache(cas) let graph = DependencyGraph( rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"), nodes: @[], timestamp: 1700000000 ) cache.putCached( "nginx", ">=1.24.0", "hash123", VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ), graph ) let result = cache.getCached( "nginx", ">=1.24.0", "hash123", VariantDemand( useFlags: @[], libc: "musl", allocator: "jemalloc", targetArch: "x86_64", buildFlags: @[] ) ) check result.value.isSome check result.source == L1Hit