a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
155 lines
4.2 KiB
Go
155 lines
4.2 KiB
Go
package mcp
|
|
|
|
import (
|
|
"container/list"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
func TestPPRWalkCacheLRU(t *testing.T) {
|
|
c := newPPRWalkCache()
|
|
c.cap = 2
|
|
|
|
c.put("a", map[string]float64{"x": 1})
|
|
c.put("b", map[string]float64{"x": 2})
|
|
if _, ok := c.get("a"); !ok {
|
|
t.Fatal("a should be present")
|
|
}
|
|
// Touch a (now MRU), then insert c -> b is LRU and evicted.
|
|
c.put("c", map[string]float64{"x": 3})
|
|
if _, ok := c.get("b"); ok {
|
|
t.Fatal("b should have been evicted (LRU)")
|
|
}
|
|
if _, ok := c.get("a"); !ok {
|
|
t.Fatal("a should survive (was recently used)")
|
|
}
|
|
if _, ok := c.get("c"); !ok {
|
|
t.Fatal("c should be present")
|
|
}
|
|
}
|
|
|
|
func TestPPRWalkCacheStats(t *testing.T) {
|
|
c := newPPRWalkCache()
|
|
c.put("k", map[string]float64{"x": 1})
|
|
if _, ok := c.get("k"); !ok {
|
|
t.Fatal("hit expected")
|
|
}
|
|
if _, ok := c.get("miss"); ok {
|
|
t.Fatal("miss expected")
|
|
}
|
|
hits, misses, size, capacity, enabled := c.stats()
|
|
if hits != 1 || misses != 1 {
|
|
t.Fatalf("hits=%d misses=%d, want 1/1", hits, misses)
|
|
}
|
|
if size != 1 || capacity != 512 || !enabled {
|
|
t.Fatalf("size=%d cap=%d enabled=%v", size, capacity, enabled)
|
|
}
|
|
}
|
|
|
|
func TestPPRWalkCacheDisabled(t *testing.T) {
|
|
t.Setenv("GORTEX_PPR_CACHE_DISABLE", "1")
|
|
c := newPPRWalkCache()
|
|
if c.enabled {
|
|
t.Fatal("cache should be disabled")
|
|
}
|
|
c.put("k", map[string]float64{"x": 1})
|
|
if _, ok := c.get("k"); ok {
|
|
t.Fatal("disabled cache should never hit")
|
|
}
|
|
}
|
|
|
|
func TestPPRWalkCacheEmptyKeyAndScores(t *testing.T) {
|
|
c := newPPRWalkCache()
|
|
c.put("", map[string]float64{"x": 1}) // empty key ignored
|
|
if _, ok := c.get(""); ok {
|
|
t.Fatal("empty key should never store")
|
|
}
|
|
c.put("k", nil) // empty scores ignored
|
|
if _, ok := c.get("k"); ok {
|
|
t.Fatal("empty scores should not be stored")
|
|
}
|
|
}
|
|
|
|
func TestPPRWalkCacheDefaults(t *testing.T) {
|
|
// Construction without overriding env carries the memory-bounding
|
|
// defaults that keep the cache from ballooning on a large graph.
|
|
c := newPPRWalkCache()
|
|
if c.maxBytes != pprCacheDefaultMaxBytes {
|
|
t.Errorf("default maxBytes=%d, want %d", c.maxBytes, pprCacheDefaultMaxBytes)
|
|
}
|
|
if c.topK != pprCacheDefaultTopK {
|
|
t.Errorf("default topK=%d, want %d", c.topK, pprCacheDefaultTopK)
|
|
}
|
|
}
|
|
|
|
func TestPPRWalkCacheEnvOverrides(t *testing.T) {
|
|
t.Setenv("GORTEX_PPR_CACHE_MAX_MB", "8")
|
|
t.Setenv("GORTEX_PPR_CACHE_TOPK", "0") // 0 = unbounded is a valid override
|
|
c := newPPRWalkCache()
|
|
if c.maxBytes != 8<<20 {
|
|
t.Errorf("maxBytes=%d, want %d", c.maxBytes, 8<<20)
|
|
}
|
|
if c.topK != 0 {
|
|
t.Errorf("topK=%d, want 0", c.topK)
|
|
}
|
|
}
|
|
|
|
// newTestPPRCache builds a cache with explicit bounds, bypassing the
|
|
// environment so the test is hermetic.
|
|
func newTestPPRCache(cap int, maxBytes int64) *pprWalkCache {
|
|
return &pprWalkCache{
|
|
ll: list.New(),
|
|
m: make(map[string]*list.Element),
|
|
cap: cap,
|
|
maxBytes: maxBytes,
|
|
enabled: true,
|
|
}
|
|
}
|
|
|
|
func scoresOfSize(n int) map[string]float64 {
|
|
m := make(map[string]float64, n)
|
|
for i := range n {
|
|
m["id"+strconv.Itoa(i)] = float64(i + 1)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func TestPPRCache_ByteBudgetEvicts(t *testing.T) {
|
|
// Each 10-score entry costs 10*pprCacheBytesPerScore. Budget for two;
|
|
// the entry-count ceiling is high so the byte budget is what governs.
|
|
per := int64(10) * pprCacheBytesPerScore
|
|
c := newTestPPRCache(1000, 2*per)
|
|
|
|
for i := range 5 {
|
|
c.put("k"+strconv.Itoa(i), scoresOfSize(10))
|
|
}
|
|
|
|
if _, _, size, _, _ := c.stats(); size != 2 {
|
|
t.Fatalf("byte budget should cap the cache at 2 entries, got %d", size)
|
|
}
|
|
if c.curBytes > c.maxBytes {
|
|
t.Fatalf("curBytes %d exceeds budget %d", c.curBytes, c.maxBytes)
|
|
}
|
|
if _, ok := c.get("k4"); !ok {
|
|
t.Errorf("most-recent key k4 should survive")
|
|
}
|
|
if _, ok := c.get("k0"); ok {
|
|
t.Errorf("oldest key k0 should have been evicted")
|
|
}
|
|
}
|
|
|
|
func TestPPRCache_ReputSameSizeKeepsByteAccounting(t *testing.T) {
|
|
c := newTestPPRCache(1000, 1<<30)
|
|
c.put("a", scoresOfSize(10))
|
|
before := c.curBytes
|
|
// Re-putting an existing key replaces its scores and re-accounts its
|
|
// bytes in place — no double counting.
|
|
c.put("a", scoresOfSize(10))
|
|
if c.curBytes != before {
|
|
t.Errorf("re-put of same-size entry changed curBytes: before=%d after=%d", before, c.curBytes)
|
|
}
|
|
if _, _, size, _, _ := c.stats(); size != 1 {
|
|
t.Errorf("re-put must not add a second entry, size=%d", size)
|
|
}
|
|
}
|