1package logging
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "os"
8 "path/filepath"
9 "testing"
10 "time"
11)
12
13func TestParseDebug(t *testing.T) {
14 for _, tc := range []struct {
15 input string
16 wantError bool
17 selected []string
18 }{
19 {"", false, nil}, {"core,app", false, []string{"core", "app"}},
20 {"all", false, []string{"all"}}, {"unknown", true, nil}, {"core,", true, nil},
21 {"all,input-motion", false, []string{"all", "input-motion"}},
22 } {
23 got, err := ParseDebug(tc.input)
24 if (err != nil) != tc.wantError {
25 t.Fatalf("ParseDebug(%q): %v", tc.input, err)
26 }
27 for _, name := range tc.selected {
28 if !got[name] {
29 t.Errorf("ParseDebug(%q) missing %s", tc.input, name)
30 }
31 }
32 }
33}
34
35func TestComponentLevelsAndRotation(t *testing.T) {
36 t.Setenv("XDG_STATE_HOME", t.TempDir())
37 dir := filepath.Join(os.Getenv("XDG_STATE_HOME"), "neferwl", "runs")
38 path := filepath.Join(dir, "latest.log")
39 for run := 0; run < keepRuns+2; run++ {
40 time.Sleep(2 * time.Millisecond) // distinct millisecond timestamps
41 ctx, closeLog, err := Open(context.Background(), "info", "core")
42 if err != nil {
43 t.Fatal(err)
44 }
45 core := For(ctx, "core")
46 app := For(ctx, "app")
47 core.Debug().Msg("visible")
48 app.Debug().Msg("hidden")
49 app.Info().Msg("startup")
50 if err := closeLog(); err != nil {
51 t.Fatal(err)
52 }
53 data, err := os.ReadFile(path)
54 if err != nil {
55 t.Fatal(err)
56 }
57 lines := splitLines(data)
58 if len(lines) != 2 {
59 t.Fatalf("expected 2 entries, got %q", data)
60 }
61 for _, line := range lines {
62 var entry map[string]any
63 if err := json.Unmarshal(line, &entry); err != nil {
64 t.Fatal(err)
65 }
66 if entry["component"] != "core" && entry["component"] != "app" {
67 t.Fatalf("missing component: %v", entry)
68 }
69 }
70 }
71 runs, _ := filepath.Glob(filepath.Join(dir, "2*.log"))
72 if len(runs) != keepRuns {
73 t.Fatalf("kept %d runs, want %d", len(runs), keepRuns)
74 }
75}
76
77func splitLines(data []byte) [][]byte {
78 var lines [][]byte
79 for _, line := range bytes.Split(data, []byte("\n")) {
80 if len(line) > 0 {
81 lines = append(lines, line)
82 }
83 }
84 return lines
85}
86
87// A category alone raises its component to debug, so its lines are written.
88func TestCategoryEnablesComponentDebug(t *testing.T) {
89 t.Setenv("XDG_STATE_HOME", t.TempDir())
90 ctx, closeLog, err := Open(context.Background(), "info", "input-motion")
91 if err != nil {
92 t.Fatal(err)
93 }
94 input, core := For(ctx, "input"), For(ctx, "core")
95 input.Debug().Msg("motion")
96 core.Debug().Msg("hidden")
97 if err := closeLog(); err != nil {
98 t.Fatal(err)
99 }
100 data, err := os.ReadFile(filepath.Join(os.Getenv("XDG_STATE_HOME"), "neferwl", "runs", "latest.log"))
101 if err != nil {
102 t.Fatal(err)
103 }
104 if lines := splitLines(data); len(lines) != 1 || !bytes.Contains(lines[0], []byte(`"motion"`)) {
105 t.Fatalf("got %q", data)
106 }
107}
108
109// Narrow categories are on only when named, never through "all".
110func TestEnabledCategories(t *testing.T) {
111 for _, tc := range []struct {
112 debug string
113 want bool
114 }{{"all", false}, {"input", false}, {"input-motion", true}, {"all,input-motion", true}} {
115 sel, err := ParseDebug(tc.debug)
116 if err != nil {
117 t.Fatal(err)
118 }
119 ctx := context.WithValue(context.Background(), debugKey{}, debugSet(sel))
120 if got := Enabled(ctx, "input-motion"); got != tc.want {
121 t.Errorf("%q: %v", tc.debug, got)
122 }
123 }
124}