repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/cache/cache_test.go
Rawpackage cache
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
"bugabinga.net/luci/internal/ciconfig"
)
func TestKeyRestoreAndSave(t *testing.T) {
root := t.TempDir()
workspace := filepath.Join(root, "work")
if err := os.MkdirAll(filepath.Join(workspace, ".deps"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "lock"), []byte("v1"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, ".deps", "module"), []byte("cached"), 0o644); err != nil {
t.Fatal(err)
}
entry := ciconfig.Cache{Name: "deps", Path: ".deps", KeyFiles: []string{"lock"}}
key, err := Key(workspace, entry)
if err != nil || len(key) != 64 {
t.Fatalf("key=%q err=%v", key, err)
}
store := Store{DataDir: filepath.Join(root, "data")}
if err := store.Save("repo", "job", entry, key, workspace); err != nil {
t.Fatal(err)
}
if err := os.RemoveAll(filepath.Join(workspace, ".deps")); err != nil {
t.Fatal(err)
}
if _, err := store.Restore("repo", "job", entry, key, workspace); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(filepath.Join(workspace, ".deps", "module"))
if err != nil || string(data) != "cached" {
t.Fatalf("data=%q err=%v", data, err)
}
if _, err := store.Restore("repo", "job", entry, "missing", workspace); err != nil {
t.Fatal(err)
}
}
func TestKeyChangesAndMarksMissingFiles(t *testing.T) {
workspace := t.TempDir()
entry := ciconfig.Cache{Name: "deps", Path: ".deps", KeyFiles: []string{"missing"}}
missing, err := Key(workspace, entry)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "missing"), []byte("present"), 0o644); err != nil {
t.Fatal(err)
}
present, err := Key(workspace, entry)
if err != nil || present == missing {
t.Fatalf("missing=%q present=%q err=%v", missing, present, err)
}
}
func TestRejectsUnsafePathsSymlinksAndSpecialFiles(t *testing.T) {
root := t.TempDir()
workspace := filepath.Join(root, "work")
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
if _, err := Key(workspace, ciconfig.Cache{KeyFiles: []string{"../outside"}}); err == nil {
t.Fatal("unsafe key accepted")
}
if err := os.Symlink(root, filepath.Join(workspace, "link")); err != nil {
t.Fatal(err)
}
if _, err := Key(workspace, ciconfig.Cache{KeyFiles: []string{"link/file"}}); err == nil {
t.Fatal("symlink key accepted")
}
store := Store{DataDir: filepath.Join(root, "data")}
entry := ciconfig.Cache{Name: "deps", Path: "link/cache"}
if err := store.Save("repo", "job", entry, "key", workspace); err == nil {
t.Fatal("symlink cache accepted")
}
entry.Path = "missing"
if err := store.Save("repo", "job", entry, "key", workspace); err != nil {
t.Fatalf("missing cache failed: %v", err)
}
if _, err := store.Restore("../repo", "job", entry, "key", workspace); err == nil {
t.Fatal("unsafe repo accepted")
}
}
func TestSaveEvictsOldVersions(t *testing.T) {
root := t.TempDir()
store := Store{DataDir: root, MaxVersions: 2}
for i, key := range []string{"aa", "bb", "cc"} {
workspace := filepath.Join(root, "w", key)
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "value"), []byte(key), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Save("repo", "job", ciconfig.Cache{Name: "deps", Path: "value"}, key, workspace); err != nil {
t.Fatal(err)
}
mtime := time.Date(2026, 1, 1, 0, 0, i, 0, time.UTC)
if err := os.Chtimes(filepath.Join(root, "cache", "repo", "job", "deps", key), mtime, mtime); err != nil {
t.Fatal(err)
}
}
entries, err := os.ReadDir(filepath.Join(root, "cache", "repo", "job", "deps"))
if err != nil {
t.Fatal(err)
}
var names []string
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".lock") && !strings.HasPrefix(entry.Name(), ".") {
names = append(names, entry.Name())
}
}
if len(names) != 2 {
t.Fatalf("remaining=%v", names)
}
for _, name := range names {
if name == "aa" {
t.Fatalf("oldest survived: %v", names)
}
}
}
func TestSaveEvictsToByteBudget(t *testing.T) {
root := t.TempDir()
store := Store{DataDir: root, MaxVersions: 5, MaxBytes: 3}
for i, key := range []string{"aa", "bb", "cc"} {
workspace := filepath.Join(root, "w", key)
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "value"), []byte(key), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Save("repo", "job", ciconfig.Cache{Name: "deps", Path: "value"}, key, workspace); err != nil {
t.Fatal(err)
}
mtime := time.Date(2026, 1, 1, 0, 0, i, 0, time.UTC)
if err := os.Chtimes(filepath.Join(root, "cache", "repo", "job", "deps", key), mtime, mtime); err != nil {
t.Fatal(err)
}
}
entries, err := os.ReadDir(filepath.Join(root, "cache", "repo", "job", "deps"))
if err != nil {
t.Fatal(err)
}
remaining := 0
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".lock") && !strings.HasPrefix(entry.Name(), ".") {
remaining++
}
}
if remaining != 1 {
t.Fatalf("remaining=%d want 1 (newest only under 3-byte budget)", remaining)
}
}
func TestSaveReservesAggregateBudgetBeforeStaging(t *testing.T) {
root := t.TempDir()
store := Store{DataDir: root, MaxVersions: 3, MaxBytes: 6}
save := func(name, data string) error {
workspace := filepath.Join(root, "work", name)
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "value"), []byte(data), 0o644); err != nil {
t.Fatal(err)
}
return store.Save("repo", "job", ciconfig.Cache{Name: name, Path: "value"}, "key", workspace)
}
if err := save("first", "1234"); err != nil {
t.Fatal(err)
}
if err := save("second", "5678"); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(root, "cache", "repo", "job", "first", "key")); !os.IsNotExist(err) {
t.Fatalf("old aggregate cache remains: %v", err)
}
if _, err := os.Stat(filepath.Join(root, "cache", "repo", "job", "second", "key")); err != nil {
t.Fatalf("new aggregate cache missing: %v", err)
}
workspace := filepath.Join(root, "oversized")
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(workspace, "value"), []byte("1234567"), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Save("repo", "job", ciconfig.Cache{Name: "large", Path: "value"}, "key", workspace); err == nil {
t.Fatal("oversized cache staged")
}
if _, err := os.Stat(filepath.Join(root, "cache", "repo", "job", "large", "key")); !os.IsNotExist(err) {
t.Fatalf("oversized cache was stored: %v", err)
}
}
func TestSaveReplacesProtectedVersionWithinAggregateBudget(t *testing.T) {
root := t.TempDir()
workspace := filepath.Join(root, "work")
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
entry := ciconfig.Cache{Name: "deps", Path: "value"}
store := Store{DataDir: root, MaxBytes: 3}
for _, value := range []string{"old", "new"} {
if err := os.WriteFile(filepath.Join(workspace, "value"), []byte(value), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Save("repo", "job", entry, "key", workspace); err != nil {
t.Fatal(err)
}
}
}
func TestRestoreFallsBackToNewestVersion(t *testing.T) {
root := t.TempDir()
workspace := filepath.Join(root, "work")
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
store := Store{DataDir: root, MaxVersions: 3}
for i, key := range []string{"old", "new"} {
source := filepath.Join(root, "src", key)
if err := os.MkdirAll(filepath.Join(source, "deps"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(source, "deps", "marker"), []byte(key), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Save("repo", "job", ciconfig.Cache{Name: "deps", Path: "deps"}, key, source); err != nil {
t.Fatal(err)
}
mtime := time.Date(2026, 1, 1, 0, i, 0, 0, time.UTC)
if err := os.Chtimes(filepath.Join(root, "cache", "repo", "job", "deps", key), mtime, mtime); err != nil {
t.Fatal(err)
}
}
if _, err := store.Restore("repo", "job", ciconfig.Cache{Name: "deps", Path: ".deps"}, "missing", workspace); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(filepath.Join(workspace, ".deps", "marker"))
if err != nil || string(data) != "new" {
t.Fatalf("restored=%q err=%v; want newest", data, err)
}
}
func TestRestoreWithoutAnyVersionSucceeds(t *testing.T) {
root := t.TempDir()
workspace := filepath.Join(root, "work")
if err := os.MkdirAll(workspace, 0o755); err != nil {
t.Fatal(err)
}
store := Store{DataDir: root}
if _, err := store.Restore("repo", "job", ciconfig.Cache{Name: "deps", Path: ".deps"}, "missing", workspace); err != nil {
t.Fatalf("empty restore err=%v", err)
}
}
func TestSaveReplacesPreviousCache(t *testing.T) {
root := t.TempDir()
workspace := filepath.Join(root, "work")
path := filepath.Join(workspace, "deps")
if err := os.MkdirAll(path, 0o755); err != nil {
t.Fatal(err)
}
entry := ciconfig.Cache{Name: "deps", Path: "deps"}
store := Store{DataDir: filepath.Join(root, "data")}
for _, value := range []string{"old", "new"} {
if err := os.WriteFile(filepath.Join(path, "value"), []byte(value), 0o644); err != nil {
t.Fatal(err)
}
if err := store.Save("repo", "job", entry, "key", workspace); err != nil {
t.Fatal(err)
}
}
if err := os.RemoveAll(path); err != nil {
t.Fatal(err)
}
if _, err := store.Restore("repo", "job", entry, "key", workspace); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(filepath.Join(path, "value"))
if err != nil || string(data) != "new" {
t.Fatalf("data=%q err=%v", data, err)
}
}