// SPDX-License-Identifier: GPL-3.0-or-later package config import ( "fmt" "os" "path/filepath" "strings" "testing" "time" ) // writeFiles creates each file under root, making directories as needed. func writeFiles(t *testing.T, root string, files map[string]string) { t.Helper() for name, body := range files { p := filepath.Join(root, name) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte(body), 0o644); err != nil { t.Fatal(err) } } } func TestLoad(t *testing.T) { root := t.TempDir() writeFiles(t, root, map[string]string{ "krino.conf": `(include "a" "b") (defaults (min-age 5m))`, "dirs/a.conf": `(path "/tmp/a") (min-age 1m) (rule "r" (stop))`, "dirs/b.conf": `(path "/tmp/b") (rule "r" (stop))`, }) main := filepath.Join(root, "krino.conf") cfg, errs := Load(main) if len(errs) > 0 { t.Fatal(errs) } if len(cfg.Dirs) != 2 || cfg.Dirs[0].Name != "a" || cfg.Dirs[1].Path != "/tmp/b" { t.Fatalf("dirs = %+v", cfg.Dirs) } if got := cfg.Resolved(cfg.Dirs[0]).MinAge; got != time.Minute { t.Errorf("a min-age = %v, want the directory's 1m", got) } if got := cfg.Resolved(cfg.Dirs[1]).MinAge; got != 5*time.Minute { t.Errorf("b min-age = %v, want the default 5m", got) } cfg, errs = Load(main, "b") if len(errs) > 0 || len(cfg.Dirs) != 1 || cfg.Dirs[0].Name != "b" { t.Fatalf("Load(b) = %+v, %v", cfg, errs) } } func TestLoadErrors(t *testing.T) { root := t.TempDir() main := filepath.Join(root, "krino.conf") cfg, errs := Load(main) if cfg != nil || len(errs) != 1 || errs[0].Error() != main+": not found; create it with: krino init" { t.Fatalf("missing main file: %v", errs) } writeFiles(t, root, map[string]string{"krino.conf": "(include \"gone\")\n"}) _, errs = Load(main) want := fmt.Sprintf(`%s:1:10: included "gone", but %s does not exist; create it with: krino new gone PATH`, main, filepath.Join(root, "dirs", "gone.conf")) if len(errs) != 1 || errs[0].Error() != want { t.Fatalf("missing dir file:\n got %v\n want %s", errs, want) } _, errs = Load(main, "other") if len(errs) != 1 || !strings.HasSuffix(errs[0].Error(), `: "other" is not in include`) { t.Fatalf("unknown name: %v", errs) } } // TestLoadSyntaxErrorStopsAtOneDiag is item C: a krino.conf that fails to // parse must report only the syntax error, not also "not in include" for // names the caller asked for. func TestLoadSyntaxErrorStopsAtOneDiag(t *testing.T) { root := t.TempDir() writeFiles(t, root, map[string]string{"krino.conf": `(include "dl"`}) main := filepath.Join(root, "krino.conf") _, errs := Load(main, "dl") if len(errs) != 1 { t.Fatalf("errs = %v, want exactly one diag", errs) } want := fmt.Sprintf(`%s:1:1: "(" never closed: (include "dl")`, main) if errs[0].Error() != want { t.Fatalf("got %s\nwant %s", errs[0], want) } } func TestDefaultFileAndLogFile(t *testing.T) { t.Setenv("XDG_CONFIG_HOME", "/conf") t.Setenv("XDG_STATE_HOME", "/state") if got := DefaultFile(); got != "/conf/krino/krino.conf" { t.Errorf("DefaultFile() = %q", got) } c := &Config{Main: &Main{}} if got := c.LogFile(); got != "/state/krino/krino.log" { t.Errorf("LogFile() = %q", got) } c.Main.Log = "/x.log" if got := c.LogFile(); got != "/x.log" { t.Errorf("LogFile() = %q", got) } } func TestLockFile(t *testing.T) { h := t.TempDir() t.Setenv("HOME", h) t.Setenv("XDG_STATE_HOME", "") c := &Config{} want := filepath.Join(h, ".local", "state", "krino", "dl.lock") if got := c.LockFile("dl"); got != want { t.Errorf("LockFile = %q, want %q", got, want) } } // TestLoadWithOverriddenText: LoadWith reads the text the caller supplies // instead of a file's own, so an editor can check what it has not saved yet // (GUI design ยง1.3); the files on disk are neither read differently nor // changed. func TestLoadWithOverriddenText(t *testing.T) { h := t.TempDir() t.Setenv("HOME", h) main := filepath.Join(h, "krino.conf") if err := os.MkdirAll(filepath.Join(h, "dirs"), 0o755); err != nil { t.Fatal(err) } os.WriteFile(main, []byte("(include \"dl\")\n"), 0o644) os.WriteFile(filepath.Join(h, "dirs", "dl.conf"), []byte("(path \"/tmp\")\n"), 0o644) over := map[string][]byte{filepath.Join(h, "dirs", "dl.conf"): []byte("(path \"/tmp\")\n(rule \"r\" (move \"Out\"))\n")} cfg, errs := LoadWith(main, over) if len(errs) > 0 || len(cfg.Dirs) != 1 || len(cfg.Dirs[0].Rules) != 1 { t.Fatalf("overridden text not used: %v %+v", errs, cfg.Dirs) } if _, errs := LoadWith(main, map[string][]byte{main: []byte("(include \"dl\"")}); len(errs) == 0 { t.Error("a mistake in the overridden main file was not reported") } if cfg, errs := Load(main); len(errs) > 0 || len(cfg.Dirs[0].Rules) != 0 { t.Errorf("the files on disk were read differently: %v %+v", errs, cfg.Dirs) } }