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", "drm")
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(), "drm", "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// Headless runs rotate in their own directory: they never prune a DRM run.
88func TestBackendsRotateSeparately(t *testing.T) {
89 t.Setenv("XDG_STATE_HOME", t.TempDir())
90 runs := filepath.Join(os.Getenv("XDG_STATE_HOME"), "neferwl", "runs")
91 open := func(backend string) {
92 time.Sleep(2 * time.Millisecond) // distinct millisecond timestamps
93 _, closeLog, err := Open(context.Background(), backend, "info", "")
94 if err != nil {
95 t.Fatal(err)
96 }
97 if err := closeLog(); err != nil {
98 t.Fatal(err)
99 }
100 }
101 open("drm")
102 for range keepRuns + 2 {
103 open("headless")
104 }
105 if drm, _ := filepath.Glob(filepath.Join(runs, "drm", "2*.log")); len(drm) != 1 {
106 t.Fatalf("drm runs %v", drm)
107 }
108 if _, err := os.Stat(filepath.Join(runs, "drm", "latest.log")); err != nil {
109 t.Fatal(err)
110 }
111 if headless, _ := filepath.Glob(filepath.Join(runs, "headless", "2*.log")); len(headless) != keepRuns {
112 t.Fatalf("kept %d headless runs", len(headless))
113 }
114}
115
116// A category alone raises its component to debug, so its lines are written.
117func TestCategoryEnablesComponentDebug(t *testing.T) {
118 t.Setenv("XDG_STATE_HOME", t.TempDir())
119 ctx, closeLog, err := Open(context.Background(), "drm", "info", "input-motion")
120 if err != nil {
121 t.Fatal(err)
122 }
123 input, core := For(ctx, "input"), For(ctx, "core")
124 input.Debug().Msg("motion")
125 core.Debug().Msg("hidden")
126 if err := closeLog(); err != nil {
127 t.Fatal(err)
128 }
129 data, err := os.ReadFile(filepath.Join(os.Getenv("XDG_STATE_HOME"), "neferwl", "runs", "drm", "latest.log"))
130 if err != nil {
131 t.Fatal(err)
132 }
133 if lines := splitLines(data); len(lines) != 1 || !bytes.Contains(lines[0], []byte(`"motion"`)) {
134 t.Fatalf("got %q", data)
135 }
136}
137
138// Narrow categories are on only when named, never through "all".
139func TestEnabledCategories(t *testing.T) {
140 for _, category := range []string{"input-motion", "input-keys", "drm-flip"} {
141 component := categories[category]
142 for _, tc := range []struct {
143 debug string
144 want bool
145 }{{"all", false}, {component, false}, {category, true}, {"all," + category, true}} {
146 sel, err := ParseDebug(tc.debug)
147 if err != nil {
148 t.Fatal(err)
149 }
150 ctx := context.WithValue(context.Background(), debugKey{}, debugSet(sel))
151 if got := Enabled(ctx, category); got != tc.want {
152 t.Errorf("%s with %q: %v", category, tc.debug, got)
153 }
154 }
155 }
156}