aboutsummaryrefslogtreecommitdiff
path: root/internal/engine
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-15 00:32:11 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-15 00:32:11 +0200
commitccd6faf3d10da0669631fb3e3ba17a46b9e63a09 (patch)
treeb1829034cf6c6fec206ba50deac59ff4369ce974 /internal/engine
parent2f4e6dd8dfc7e18064e087c65ff9d28b4833cce2 (diff)
downloadkrino-ccd6faf3d10da0669631fb3e3ba17a46b9e63a09.tar.gz
krino-ccd6faf3d10da0669631fb3e3ba17a46b9e63a09.zip
(matched) is unknown after an undecided rule, so a catch-all leaves an unreadable file alone
Diffstat (limited to 'internal/engine')
-rw-r--r--internal/engine/engine_test.go2
-rw-r--r--internal/engine/exclude_test.go36
-rw-r--r--internal/engine/facts.go15
-rw-r--r--internal/engine/match.go6
4 files changed, 52 insertions, 7 deletions
diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go
index c38c1fc..9de3ea4 100644
--- a/internal/engine/engine_test.go
+++ b/internal/engine/engine_test.go
@@ -53,7 +53,7 @@ func (f fakeFacts) ModTime() time.Time { return
func (f fakeFacts) Now() time.Time { return time.Time{} }
func (f fakeFacts) ContentContains(cond.Options, []string) (int, error) { return -1, nil }
func (f fakeFacts) Duplicate([]string) (string, bool, error) { return "", false, nil }
-func (f fakeFacts) Matched() bool { return false }
+func (f fakeFacts) Matched() (bool, bool) { return false, false }
var _ cond.Facts = fakeFacts{}
diff --git a/internal/engine/exclude_test.go b/internal/engine/exclude_test.go
index 9aafd7f..3398b07 100644
--- a/internal/engine/exclude_test.go
+++ b/internal/engine/exclude_test.go
@@ -512,3 +512,39 @@ func TestExplainAgreesWithMatchOnADuplicateExclude(t *testing.T) {
}
}
}
+
+// TestNotMatchedAfterAnUnknownRuleDoesNotAct: a partly read document an
+// earlier content rule could not decide is not caught by a later
+// (not (matched)) catch-all (plan 11 review L6).
+func TestNotMatchedAfterAnUnknownRuleDoesNotAct(t *testing.T) {
+ h, dl := excludeTree(t, map[string]string{})
+ os.MkdirAll(dl, 0o755)
+ partDocx(t, filepath.Join(dl, "part.docx"), "good body text")
+ main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": `
+(path "~/dl")
+(rule "acme" (when (content "acme ltd")) (move "Acme"))
+(rule "rest" (when (not (matched))) (move "Unsorted"))
+`})
+ e, errs := Load(main)
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+ r, err := e.Match(context.Background(), e.Dirs[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, fm := range r.Matched {
+ if fm.File.Rel == "part.docx" {
+ t.Errorf("part.docx matched %d rules; the catch-all must not take a file an earlier rule could not decide", len(fm.Rules))
+ }
+ }
+ x, err := e.Explain(context.Background(), filepath.Join(dl, "part.docx"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, rt := range x.Rules {
+ if rt.Match {
+ t.Errorf("explain: rule %s matches", rt.Rule.Name)
+ }
+ }
+}
diff --git a/internal/engine/facts.go b/internal/engine/facts.go
index f8fde89..89bd71b 100644
--- a/internal/engine/facts.go
+++ b/internal/engine/facts.go
@@ -132,6 +132,9 @@ type facts struct {
file scan.File
matched bool
+ // undecided: an earlier rule's condition was unknown (content krino
+ // could not read), so (matched) is unknown while none has matched.
+ undecided bool
contentDone bool // extraction was attempted
contentErr error // why it failed
@@ -146,12 +149,12 @@ func newFacts(run *matchRun, file scan.File) *facts {
return &facts{run: run, file: file}
}
-func (f *facts) Name() string { return f.file.Name }
-func (f *facts) Rel() string { return f.file.Rel }
-func (f *facts) Size() int64 { return f.file.Size }
-func (f *facts) ModTime() time.Time { return f.file.ModTime }
-func (f *facts) Now() time.Time { return f.run.now }
-func (f *facts) Matched() bool { return f.matched }
+func (f *facts) Name() string { return f.file.Name }
+func (f *facts) Rel() string { return f.file.Rel }
+func (f *facts) Size() int64 { return f.file.Size }
+func (f *facts) ModTime() time.Time { return f.file.ModTime }
+func (f *facts) Now() time.Time { return f.run.now }
+func (f *facts) Matched() (bool, bool) { return f.matched, f.undecided }
// ContentContains answers a content test (spec ยง6.1). A file above
// max-read is never read, cached or not. Before the file has been
diff --git a/internal/engine/match.go b/internal/engine/match.go
index c252596..2ef3ae1 100644
--- a/internal/engine/match.go
+++ b/internal/engine/match.go
@@ -169,6 +169,9 @@ func evalFile(run *matchRun, file scan.File) FileMatch {
for _, w := range res.Warnings {
fm.Warnings = append(fm.Warnings, r.Name+": "+w)
}
+ if res.Unreadable {
+ f.undecided = true
+ }
if !res.Match {
continue
}
@@ -316,6 +319,9 @@ func (e *Engine) Explain(ctx context.Context, path string) (*Explanation, error)
}
trace := r.Cond.Explain(f)
match := trace.Value
+ if trace.Unknown {
+ f.undecided = true
+ }
if match {
f.matched = true
matched = append(matched, RuleMatch{Rule: r})