1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
// SPDX-License-Identifier: GPL-3.0-or-later
package config
import (
"strings"
"testing"
"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 (plan 13
// review F6).
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)
}
}
|