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
|
// SPDX-License-Identifier: GPL-3.0-or-later
package config
import (
"reflect"
"strings"
"testing"
"time"
"git.labunix.xyz/krino/internal/sexp"
)
func parseSettings(t *testing.T, src string) (Settings, []*Diag) {
t.Helper()
nodes, err := sexp.Parse("s.conf", []byte(src))
if err != nil {
t.Fatal(err)
}
var s Settings
d := &diags{file: "s.conf"}
seen := map[string]*sexp.Node{}
for _, n := range nodes {
s.parse(n, d, seen)
}
return s, d.list
}
func TestSettingsResolve(t *testing.T) {
s, errs := parseSettings(t, `(case strict) (fold no) (recursive yes) (max-depth 3)
(min-age 5m) (max-read 1G) (max-size 2G) (busy ".tmp") (on-conflict skip)`)
if len(errs) > 0 {
t.Fatal(errs)
}
want := Resolved{Case: CaseStrict, Fold: false, Recursive: true, MaxDepth: 3,
MinAge: 5 * time.Minute, MaxRead: 1 << 30, MaxSize: 2 << 30, Busy: []string{".tmp"}, OnConflict: ConflictSkip}
if got := s.Over(Builtin()); !reflect.DeepEqual(got, want) {
t.Fatalf("got %+v\nwant %+v", got, want)
}
}
func TestBuiltin(t *testing.T) {
want := Resolved{Case: CaseIgnore, Fold: true, MinAge: 2 * time.Minute, MaxRead: 50 << 20,
Busy: []string{".part", ".aria2", ".crdownload"}, OnConflict: ConflictSuffix}
if got := Builtin(); !reflect.DeepEqual(got, want) {
t.Fatalf("got %+v\nwant %+v", got, want)
}
}
func TestSettingsLayering(t *testing.T) {
defaults, _ := parseSettings(t, `(min-age 5m) (fold no)`)
dir, _ := parseSettings(t, `(fold yes)`)
got := dir.Over(defaults.Over(Builtin()))
if got.MinAge != 5*time.Minute || !got.Fold || got.MaxRead != 50<<20 {
t.Fatalf("got %+v", got)
}
}
func TestBusyEmptyDisables(t *testing.T) {
s, errs := parseSettings(t, `(busy)`)
if len(errs) > 0 || s.Busy == nil {
t.Fatalf("errs %v, busy %v", errs, s.Busy)
}
if got := s.Over(Builtin()).Busy; len(got) != 0 {
t.Fatalf("busy = %v, want none", got)
}
}
func TestSettingErrors(t *testing.T) {
tests := []struct{ src, want string }{
{`(case loud)`, `s.conf:1:7: case is ignore or strict, not loud`},
{`(case "ignore")`, `s.conf:1:7: case values are bare words: write (case ignore)`},
{`(fold)`, `s.conf:1:1: fold takes one value: (fold yes) or (fold no)`},
{`(max-depth 0)`, `s.conf:1:12: max-depth is a whole number from 1, not 0`},
{`(min-age soon)`, `s.conf:1:10: min-age: bad duration "soon": want a whole number followed by s, m, h, d or w, like 30d`},
{`(max-read 5m)`, `s.conf:1:11: max-read: bad size "5m": want a whole number with an optional K, M, G or T, like 50M`},
{`(busy part)`, `s.conf:1:7: busy takes strings, like ".part"; got part`},
{`(fold yes) (fold no)`, `s.conf:1:12: fold set twice (first at line 1)`},
}
for _, tt := range tests {
_, errs := parseSettings(t, tt.src)
if len(errs) != 1 || errs[0].Error() != tt.want {
t.Errorf("%s:\n got %v\n want %s", tt.src, errs, tt.want)
}
}
}
// TestMaxSizeErrors: max-size takes a size like max-read does, and is not a
// rule-level setting.
func TestMaxSizeErrors(t *testing.T) {
if _, errs := parseSettings(t, "(max-size big)"); len(errs) != 1 || !strings.Contains(errs[0].Msg, "max-size") {
t.Errorf("(max-size big): errs %v, want one max-size error", errs)
}
if _, errs := parseSettings(t, "(max-size 10M)"); len(errs) != 0 {
t.Errorf("(max-size 10M): errs %v, want none", errs)
}
}
|