b6d4db1a96
internal/cache/ package with per-class TTLs (Get/Set/Invalidate); wired into node/job/ns list read paths; orca cache show/invalidate CLI. Tests: hit/miss/invalidate/TTL-expiry + bench <1ms hit. ---ci--- project: orca phase: 00 milestone: v0.11 status: execute ---/ci---
228 lines
5.8 KiB
Go
228 lines
5.8 KiB
Go
package cache
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func openTestCache(t *testing.T) (*Cache, func()) {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "orca_cache.db")
|
|
c, err := Open(path)
|
|
if err != nil {
|
|
t.Fatalf("open cache: %v", err)
|
|
}
|
|
return c, func() { _ = c.Close() }
|
|
}
|
|
|
|
func TestCache_Hit(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
want := []byte("hello-orca")
|
|
if err := c.Set("nodes", "list", want, 30*time.Second); err != nil {
|
|
t.Fatalf("set: %v", err)
|
|
}
|
|
got, inserted, err := c.Get("nodes", "list")
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if string(got) != string(want) {
|
|
t.Errorf("get value = %q, want %q", got, want)
|
|
}
|
|
if inserted.IsZero() {
|
|
t.Errorf("inserted time is zero")
|
|
}
|
|
}
|
|
|
|
func TestCache_Miss(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
got, _, err := c.Get("nodes", "missing")
|
|
if !errors.Is(err, ErrCacheMiss) {
|
|
t.Fatalf("get miss: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
if got != nil {
|
|
t.Errorf("get miss value = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestCache_Invalidate(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
if err := c.Set("nodes", "list", []byte("a"), 30*time.Second); err != nil {
|
|
t.Fatalf("set a: %v", err)
|
|
}
|
|
if err := c.Set("nodes", "other", []byte("b"), 30*time.Second); err != nil {
|
|
t.Fatalf("set b: %v", err)
|
|
}
|
|
if err := c.Set("jobs", "list", []byte("c"), 30*time.Second); err != nil {
|
|
t.Fatalf("set c: %v", err)
|
|
}
|
|
if err := c.Invalidate("nodes"); err != nil {
|
|
t.Fatalf("invalidate: %v", err)
|
|
}
|
|
if _, _, err := c.Get("nodes", "list"); !errors.Is(err, ErrCacheMiss) {
|
|
t.Errorf("nodes/list after invalidate: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
if _, _, err := c.Get("nodes", "other"); !errors.Is(err, ErrCacheMiss) {
|
|
t.Errorf("nodes/other after invalidate: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
if _, _, err := c.Get("jobs", "list"); err != nil {
|
|
t.Errorf("jobs/list after nodes invalidate: err = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestCache_InvalidateKey(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
if err := c.Set("nodes", "list", []byte("a"), 30*time.Second); err != nil {
|
|
t.Fatalf("set: %v", err)
|
|
}
|
|
if err := c.InvalidateKey("nodes", "list"); err != nil {
|
|
t.Fatalf("invalidate key: %v", err)
|
|
}
|
|
if _, _, err := c.Get("nodes", "list"); !errors.Is(err, ErrCacheMiss) {
|
|
t.Errorf("get after invalidate key: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
}
|
|
|
|
func TestCache_TTLExpiry(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
if err := c.Set("jobs", "list", []byte("stale"), 1*time.Millisecond); err != nil {
|
|
t.Fatalf("set: %v", err)
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
if _, _, err := c.Get("jobs", "list"); !errors.Is(err, ErrCacheMiss) {
|
|
t.Errorf("get after ttl expiry: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
}
|
|
|
|
func TestCache_TTLZeroNeverExpires(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
if err := c.Set("namespaces", "list", []byte("forever"), 0); err != nil {
|
|
t.Fatalf("set: %v", err)
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
got, _, err := c.Get("namespaces", "list")
|
|
if err != nil {
|
|
t.Fatalf("get ttl=0: %v", err)
|
|
}
|
|
if string(got) != "forever" {
|
|
t.Errorf("get ttl=0 value = %q, want %q", got, "forever")
|
|
}
|
|
}
|
|
|
|
func TestCache_Overwrite(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
if err := c.Set("nodes", "list", []byte("v1"), 30*time.Second); err != nil {
|
|
t.Fatalf("set v1: %v", err)
|
|
}
|
|
if err := c.Set("nodes", "list", []byte("v2"), 30*time.Second); err != nil {
|
|
t.Fatalf("set v2: %v", err)
|
|
}
|
|
got, _, err := c.Get("nodes", "list")
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if string(got) != "v2" {
|
|
t.Errorf("get after overwrite = %q, want %q", got, "v2")
|
|
}
|
|
}
|
|
|
|
func TestCache_InvalidateAll(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
_ = c.Set("nodes", "list", []byte("a"), 30*time.Second)
|
|
_ = c.Set("jobs", "list", []byte("b"), 30*time.Second)
|
|
if err := c.InvalidateAll(); err != nil {
|
|
t.Fatalf("invalidate all: %v", err)
|
|
}
|
|
if _, _, err := c.Get("nodes", "list"); !errors.Is(err, ErrCacheMiss) {
|
|
t.Errorf("nodes/list after invalidate-all: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
if _, _, err := c.Get("jobs", "list"); !errors.Is(err, ErrCacheMiss) {
|
|
t.Errorf("jobs/list after invalidate-all: err = %v, want ErrCacheMiss", err)
|
|
}
|
|
}
|
|
|
|
func TestCache_Stats(t *testing.T) {
|
|
c, cleanup := openTestCache(t)
|
|
defer cleanup()
|
|
|
|
_ = c.Set("nodes", "list", []byte("aaaa"), 30*time.Second)
|
|
_ = c.Set("jobs", "list", []byte("bb"), 30*time.Second)
|
|
stats, err := c.Stats()
|
|
if err != nil {
|
|
t.Fatalf("stats: %v", err)
|
|
}
|
|
if len(stats) != 2 {
|
|
t.Fatalf("stats len = %d, want 2", len(stats))
|
|
}
|
|
var nodes, jobs *ClassStats
|
|
for i := range stats {
|
|
switch stats[i].Class {
|
|
case "nodes":
|
|
nodes = &stats[i]
|
|
case "jobs":
|
|
jobs = &stats[i]
|
|
}
|
|
}
|
|
if nodes == nil || nodes.Count != 1 || nodes.Bytes != 4 {
|
|
t.Errorf("nodes stats = %+v, want count=1 bytes=4", nodes)
|
|
}
|
|
if jobs == nil || jobs.Count != 1 || jobs.Bytes != 2 {
|
|
t.Errorf("jobs stats = %+v, want count=1 bytes=2", jobs)
|
|
}
|
|
}
|
|
|
|
func TestCache_OpenDefaultPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("ORCA_HOME", dir)
|
|
c, err := Open("")
|
|
if err != nil {
|
|
t.Fatalf("open default path: %v", err)
|
|
}
|
|
defer c.Close()
|
|
if err := c.Set("nodes", "list", []byte("ok"), 0); err != nil {
|
|
t.Fatalf("set: %v", err)
|
|
}
|
|
got, _, err := c.Get("nodes", "list")
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if string(got) != "ok" {
|
|
t.Errorf("get = %q, want %q", got, "ok")
|
|
}
|
|
}
|
|
|
|
func BenchmarkCacheHit(b *testing.B) {
|
|
path := filepath.Join(b.TempDir(), "orca_cache.db")
|
|
c, err := Open(path)
|
|
if err != nil {
|
|
b.Fatalf("open: %v", err)
|
|
}
|
|
defer c.Close()
|
|
if err := c.Set("nodes", "list", []byte("bench"), 0); err != nil {
|
|
b.Fatalf("set: %v", err)
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if _, _, err := c.Get("nodes", "list"); err != nil {
|
|
b.Fatalf("get: %v", err)
|
|
}
|
|
}
|
|
}
|