diff options
Diffstat (limited to 'internal/engine')
| -rw-r--r-- | internal/engine/cache_test.go | 57 | ||||
| -rw-r--r-- | internal/engine/facts.go | 2 | ||||
| -rw-r--r-- | internal/engine/match.go | 29 |
3 files changed, 79 insertions, 9 deletions
diff --git a/internal/engine/cache_test.go b/internal/engine/cache_test.go index eee2860..8c52d48 100644 --- a/internal/engine/cache_test.go +++ b/internal/engine/cache_test.go @@ -6,9 +6,14 @@ import ( "context" "os" "path/filepath" + "runtime" "strings" "testing" "time" + "unicode" + + "krino/internal/config" + "krino/internal/extract" ) // countingPDFTree makes ~/dl holding the given .pdf files, and a fake @@ -222,3 +227,55 @@ func TestExplainUsesCacheWithoutWriting(t *testing.T) { t.Errorf("explain extracted although the run cached a.pdf") } } + +// TestCacheRereadsARenamedExtension: the extension picks the extractor, so +// a file renamed from .html to .txt - same inode, size and mtime - is read +// again, not answered from what the markup reader found (review M6). +func TestCacheRereadsARenamedExtension(t *testing.T) { + home, dl := excludeTree(t, map[string]string{"page.html": "<p>ACME</p> Ltd"}) + main := writeConfig(t, home, `(include "dl")`, map[string]string{"dl": "(path \"~/dl\")\n(rule \"acme\" (when (content \"acme ltd\")) (move \"Acme\"))\n"}) + if r := cachedMatch(t, home, main); matchedNames(r) != "page.html" { + t.Fatalf("as html: matched %q", matchedNames(r)) + } + if err := os.Rename(filepath.Join(dl, "page.html"), filepath.Join(dl, "page.txt")); err != nil { + t.Fatal(err) + } + if r := cachedMatch(t, home, main); matchedNames(r) != "" { + t.Errorf("as txt, answered from the html read: matched %q", matchedNames(r)) + } +} + +// TestCacheFingerprintCoversMaxReadAndTables: answers depend on max-read +// (it caps what is read), on the Go release and on the Unicode tables, so +// all are in the fingerprint (review cache F2, F4). +func TestCacheFingerprintCoversMaxReadAndTables(t *testing.T) { + e := &Engine{Extract: extract.New()} + small := e.cacheFingerprint(&Dir{Settings: config.Resolved{MaxRead: 1 << 10}}) + large := e.cacheFingerprint(&Dir{Settings: config.Resolved{MaxRead: 1 << 20}}) + if small == large { + t.Error("max-read does not change the fingerprint") + } + for _, want := range []string{runtime.Version(), unicode.Version} { + if !strings.Contains(small, want) { + t.Errorf("fingerprint %q lacks %q", small, want) + } + } +} + +// TestCacheRemovedWhenNoContentTestsRemain: once a directory has no content +// test, its cache - holding keywords it no longer uses - is removed (review +// cache F6). +func TestCacheRemovedWhenNoContentTestsRemain(t *testing.T) { + home, _, _ := countingPDFTree(t, map[string]string{"a.pdf": "Invoice ACME"}) + main := writeConfig(t, home, `(include "dl")`, map[string]string{"dl": acmeRules}) + cachedMatch(t, home, main) + file := filepath.Join(home, ".cache", "krino", "dl.cache") + if _, err := os.Stat(file); err != nil { + t.Fatal(err) + } + writeConfig(t, home, `(include "dl")`, map[string]string{"dl": "(path \"~/dl\")\n(rule \"all\" (move \"Out\"))\n"}) + cachedMatch(t, home, main) + if _, err := os.Stat(file); !os.IsNotExist(err) { + t.Errorf("the cache of a directory without content tests is still there: %v", err) + } +} diff --git a/internal/engine/facts.go b/internal/engine/facts.go index 9035245..487604b 100644 --- a/internal/engine/facts.go +++ b/internal/engine/facts.go @@ -212,7 +212,7 @@ func (f *facts) cacheID() (kwcache.ID, bool) { // fileCacheID is file's kwcache.ID. func fileCacheID(file scan.File) kwcache.ID { - return kwcache.ID{Dev: file.Dev, Ino: file.Ino, Size: file.Size, MTime: file.ModTime.UnixNano()} + return kwcache.ID{Dev: file.Dev, Ino: file.Ino, Size: file.Size, MTime: file.ModTime.UnixNano(), Ext: strings.ToLower(filepath.Ext(file.Name))} } // Duplicate resolves dirs against the directory's root, builds (or reuses) diff --git a/internal/engine/match.go b/internal/engine/match.go index b922829..08d75af 100644 --- a/internal/engine/match.go +++ b/internal/engine/match.go @@ -114,7 +114,11 @@ func (e *Engine) Match(ctx context.Context, d *Dir) (*Result, error) { ids = append(ids, fileCacheID(f)) } } - if err := run.cache.Save(e.cacheFile(d), ids); err != nil { + keys := make([]string, len(d.ContentKeywords)) + for i, k := range d.ContentKeywords { + keys[i] = k.Key() + } + if err := run.cache.Save(e.cacheFile(d), ids, keys); err != nil { warnings = append(warnings, "cache: "+err.Error()) } } @@ -297,11 +301,12 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error) return &Explanation{Dir: d, File: sf, Skip: skip, Excludes: excludes, Excluded: excluded, Rules: rules}, nil } -// cacheFingerprint identifies what a cached keyword answer depends on -// besides the file: the extractor (its version and tools) and the -// normalisation version. A cache written under any other is discarded. -func (e *Engine) cacheFingerprint() string { - return fmt.Sprintf("%s norm%d", e.Extract.Fingerprint(), norm.Version) +// cacheFingerprint identifies what a cached keyword answer of d depends on +// besides the file: the extractor (its version and tools), normalisation +// and its Unicode tables, the Go release, and d's max-read, which caps what +// is read. A cache written under any other is discarded. +func (e *Engine) cacheFingerprint(d *Dir) string { + return fmt.Sprintf("%s %s %s max-read=%d", e.Extract.Fingerprint(), norm.Fingerprint(), runtime.Version(), d.Settings.MaxRead) } // cacheFile is d's keyword cache file. @@ -313,10 +318,18 @@ func (e *Engine) cacheFile(d *Dir) string { // engine has a CacheDir and the directory has content tests at all. A cache // that cannot be read is replaced by an empty one, reported as a warning. func (e *Engine) openCache(run *matchRun) []string { - if e.CacheDir == "" || len(run.d.ContentKeywords) == 0 { + if e.CacheDir == "" { + return nil + } + if len(run.d.ContentKeywords) == 0 { + // No content test left: a cache from an earlier configuration only + // holds keywords this directory no longer uses (review cache F6). + if err := os.Remove(e.cacheFile(run.d)); err != nil && !os.IsNotExist(err) { + return []string{"cache: " + err.Error()} + } return nil } - c, err := kwcache.Load(e.cacheFile(run.d), e.cacheFingerprint()) + c, err := kwcache.Load(e.cacheFile(run.d), e.cacheFingerprint(run.d)) run.cache = c if err != nil { return []string{"cache: " + err.Error() + " (starting a new one)"} |
