// SPDX-License-Identifier: GPL-3.0-or-later package config import ( "reflect" "strings" "testing" "time" "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) } }