// SPDX-License-Identifier: GPL-3.0-or-later package config import ( "strings" "testing" "git.labunix.xyz/krino/internal/sexp" ) func parseOne(t *testing.T, body string) *Dir { t.Helper() dir, errs := ParseDir("dl", "dl.conf", []byte("(path \"/tmp\")\n"+body)) if len(errs) > 0 { t.Fatalf("%s: %v", body, errs) } return dir } // TestPrintRule: a rule prints as krino.conf(5) writes one, and printing // what that text parses to gives the same text again (GUI design §5.1). func TestPrintRule(t *testing.T) { src := `(rule "acme" (when (type pdf) (or (content "acme ltd") (name "^acme"))) (fold no) (move "Work/{mtime:%Y}") (stop))` want := `(rule "acme" (when (type pdf) (or (content "acme ltd") (name "^acme"))) (fold no) (move "Work/{mtime:%Y}") (stop)) ` got := PrintRule(parseOne(t, src).Rules[0]) if got != want { t.Fatalf("got\n%s\nwant\n%s", got, want) } if again := PrintRule(parseOne(t, got).Rules[0]); again != got { t.Errorf("printing is not stable:\n%s", again) } } // TestPrintRuleWithoutWhen: a rule that matches every file prints without a // (when ...), and a delete prints as its two forms. func TestPrintRuleWithoutWhen(t *testing.T) { for src, want := range map[string]string{ `(rule "all" (move "Out"))`: "(rule \"all\"\n (move \"Out\"))\n", `(rule "old" (when (age > 90d)) (delete))`: "(rule \"old\"\n (when (age > 90d))\n (delete))\n", `(rule "gone" (delete permanent))`: "(rule \"gone\"\n (delete permanent))\n", } { if got := PrintRule(parseOne(t, src).Rules[0]); got != want { t.Errorf("%s printed as\n%q\nwant\n%q", src, got, want) } } } // TestPrintExclude: an exclude prints as one form with its conditions. func TestPrintExclude(t *testing.T) { want := "(exclude (type pdf) (content \"confidential\"))\n" if got := PrintExclude(parseOne(t, `(exclude (type pdf) (content "confidential"))`).Excludes[0]); got != want { t.Errorf("got %q, want %q", got, want) } } // TestSpliceLeavesTheRestAlone: replacing one form's text changes nothing // else in the file, comments included (GUI design §5.1). func TestSpliceLeavesTheRestAlone(t *testing.T) { src := []byte("; keep me\n(path \"/tmp\")\n(rule \"a\" (move \"Out\"))\n; and me\n") dir, errs := ParseDir("dl", "dl.conf", src) if len(errs) > 0 { t.Fatal(errs) } spliced, err := Splice(src, dir.Rules[0].Pos, dir.Rules[0].End, "(rule \"a\"\n (move \"In\"))") if err != nil { t.Fatal(err) } got := string(spliced) want := "; keep me\n(path \"/tmp\")\n(rule \"a\"\n (move \"In\"))\n; and me\n" if got != want { t.Errorf("got\n%q\nwant\n%q", got, want) } if !strings.HasPrefix(string(src), "; keep me\n(path \"/tmp\")\n(rule \"a\" (move \"Out\"))") { t.Error("Splice changed the source it was given") } } // TestSpliceRefusesOffsetsOutsideTheSource: a stale end offset - the file // changed since the form was parsed - is an error, not a panic. func TestSpliceRefusesOffsetsOutsideTheSource(t *testing.T) { src := []byte("(path \"/tmp\")") for _, c := range []struct{ start, end int }{{0, len(src) + 2}, {8, 4}, {-1, 3}} { if _, err := Splice(src, sexp.Pos{Offset: c.start}, sexp.Pos{Offset: c.end}, "x"); err == nil { t.Errorf("Splice(%d, %d) did not refuse", c.start, c.end) } } out, err := Splice(src, sexp.Pos{Offset: 0}, sexp.Pos{Offset: len(src)}, "(path \"/x\")") if err != nil || string(out) != "(path \"/x\")" { t.Errorf("Splice over the whole source = %q, %v", out, err) } }