564 lines
14 KiB
Nim
564 lines
14 KiB
Nim
## 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
|