summaryrefslogtreecommitdiff
path: root/internal/kwcache/kwcache_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 15:16:55 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 15:16:55 +0200
commit0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe (patch)
tree358e331945b8206ed4a72703e3aedfe8ea7cdcdb /internal/kwcache/kwcache_test.go
parent1d3f2d1e4c59867024470d3444e12698b7ebb22e (diff)
downloadkrino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.tar.gz
krino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.zip
krino: 0.0.5 — keyword cache, t and d in reviewv0.0.5
Diffstat (limited to 'internal/kwcache/kwcache_test.go')
-rw-r--r--internal/kwcache/kwcache_test.go180
1 files changed, 180 insertions, 0 deletions
diff --git a/internal/kwcache/kwcache_test.go b/internal/kwcache/kwcache_test.go
new file mode 100644
index 0000000..8395b2d
--- /dev/null
+++ b/internal/kwcache/kwcache_test.go
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package kwcache
+
+import (
+ "os"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+var (
+ a = ID{Dev: 1, Ino: 10, Size: 100, MTime: 1000}
+ b = ID{Dev: 1, Ino: 11, Size: 200, MTime: 2000}
+)
+
+func saved(t *testing.T, c *Cache, present ...ID) string {
+ t.Helper()
+ path := filepath.Join(t.TempDir(), "sub", "dl.cache")
+ if err := c.Save(path, present); err != nil {
+ t.Fatal(err)
+ }
+ return path
+}
+
+// TestLookupAfterReload: answers stored and saved come back after a Load
+// with the same fingerprint, for any keywords the entry covers.
+func TestLookupAfterReload(t *testing.T) {
+ c := New("fp")
+ c.Store(a, map[string]bool{"k:acme": true, "k:faktura": false, "k:nip": true})
+ path := saved(t, c, a)
+
+ got, err := Load(path, "fp")
+ if err != nil {
+ t.Fatal(err)
+ }
+ hits, ok := got.Lookup(a, []string{"k:faktura", "k:nip"})
+ if !ok || !reflect.DeepEqual(hits, []bool{false, true}) {
+ t.Errorf("Lookup = %v, %v; want [false true], true", hits, ok)
+ }
+ if _, ok := got.Lookup(a, []string{"k:acme", "k:new"}); ok {
+ t.Error("a keyword the entry was never checked against must be a miss")
+ }
+ if _, ok := got.Lookup(b, []string{"k:acme"}); ok {
+ t.Error("a file never stored must be a miss")
+ }
+}
+
+// TestLookupBeforeSave: what was stored in this run answers at once.
+func TestLookupBeforeSave(t *testing.T) {
+ c := New("fp")
+ c.Store(a, map[string]bool{"k:acme": true})
+ if hits, ok := c.Lookup(a, []string{"k:acme"}); !ok || !hits[0] {
+ t.Errorf("Lookup = %v, %v", hits, ok)
+ }
+}
+
+// TestChangedFileMisses: a different size or modification time is a
+// different ID, so nothing stored for the old one answers.
+func TestChangedFileMisses(t *testing.T) {
+ c := New("fp")
+ c.Store(a, map[string]bool{"k:acme": true})
+ for _, id := range []ID{{Dev: 1, Ino: 10, Size: 101, MTime: 1000}, {Dev: 1, Ino: 10, Size: 100, MTime: 1001}} {
+ if _, ok := c.Lookup(id, []string{"k:acme"}); ok {
+ t.Errorf("%+v answered from %+v's entry", id, a)
+ }
+ }
+}
+
+// TestFingerprintMismatchDiscardsAll: a cache written under a different
+// extractor fingerprint loads empty, without error.
+func TestFingerprintMismatchDiscardsAll(t *testing.T) {
+ c := New("old")
+ c.Store(a, map[string]bool{"k:acme": true})
+ path := saved(t, c, a)
+ got, err := Load(path, "new")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := got.Lookup(a, []string{"k:acme"}); ok {
+ t.Error("entry survived a fingerprint change")
+ }
+}
+
+// TestSaveKeepsOnlyPresentFiles: entries for files not passed to Save, from
+// this run or an earlier one, are dropped.
+func TestSaveKeepsOnlyPresentFiles(t *testing.T) {
+ c := New("fp")
+ c.Store(a, map[string]bool{"k:acme": true})
+ c.Store(b, map[string]bool{"k:acme": false})
+ path := saved(t, c, a, b)
+
+ next, err := Load(path, "fp")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := next.Save(path, []ID{b}); err != nil {
+ t.Fatal(err)
+ }
+ last, err := Load(path, "fp")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := last.Lookup(a, []string{"k:acme"}); ok {
+ t.Error("a is gone from the directory but its entry was kept")
+ }
+ if hits, ok := last.Lookup(b, []string{"k:acme"}); !ok || hits[0] {
+ t.Errorf("b's entry lost across a reload: %v, %v", hits, ok)
+ }
+}
+
+// TestStoreReplacesLoadedEntry: a file read again this run replaces what
+// an earlier run stored for it.
+func TestStoreReplacesLoadedEntry(t *testing.T) {
+ c := New("fp")
+ c.Store(a, map[string]bool{"k:acme": true})
+ path := saved(t, c, a)
+ next, _ := Load(path, "fp")
+ next.Store(a, map[string]bool{"k:acme": false, "k:new": true})
+ if hits, ok := next.Lookup(a, []string{"k:acme", "k:new"}); !ok || hits[0] || !hits[1] {
+ t.Errorf("Lookup = %v, %v; want the new entry", hits, ok)
+ }
+}
+
+// TestSavePermissions: the directory is private and the file readable by
+// its owner only, with no temporary file left behind.
+func TestSavePermissions(t *testing.T) {
+ c := New("fp")
+ c.Store(a, map[string]bool{"k:acme": true})
+ path := saved(t, c, a)
+ fi, err := os.Stat(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if perm := fi.Mode().Perm(); perm != 0o600 {
+ t.Errorf("file mode %o, want 600", perm)
+ }
+ di, err := os.Stat(filepath.Dir(path))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if perm := di.Mode().Perm(); perm != 0o700 {
+ t.Errorf("directory mode %o, want 700", perm)
+ }
+ entries, _ := os.ReadDir(filepath.Dir(path))
+ if len(entries) != 1 {
+ t.Errorf("directory holds %d entries, want only the cache", len(entries))
+ }
+}
+
+// TestLoadMissingAndCorrupt: no file is an empty cache and no error; an
+// unreadable one is an empty cache and an error saying so.
+func TestLoadMissingAndCorrupt(t *testing.T) {
+ dir := t.TempDir()
+ c, err := Load(filepath.Join(dir, "none.cache"), "fp")
+ if err != nil || c == nil {
+ t.Fatalf("missing: %v, %v", c, err)
+ }
+ bad := filepath.Join(dir, "bad.cache")
+ os.WriteFile(bad, []byte("{not json"), 0o600)
+ c, err = Load(bad, "fp")
+ if err == nil || c == nil {
+ t.Fatalf("corrupt: want an empty cache and an error, got %v, %v", c, err)
+ }
+ if _, ok := c.Lookup(a, []string{"k:acme"}); ok {
+ t.Error("corrupt cache answered")
+ }
+}
+
+// TestSaveWithNothingWritesNothing: a cache that never held an entry, and
+// has no file yet, creates none.
+func TestSaveWithNothingWritesNothing(t *testing.T) {
+ path := filepath.Join(t.TempDir(), "sub", "dl.cache")
+ if err := New("fp").Save(path, []ID{a}); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := os.Stat(filepath.Dir(path)); !os.IsNotExist(err) {
+ t.Errorf("Save created %s for an empty cache", filepath.Dir(path))
+ }
+}