// 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) } }