A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
1package logging
2
3import (
4 "bytes"
5 "log/slog"
6 "strings"
7 "testing"
8)
9
10// captureLogOutput runs a function and captures slog output
11func captureLogOutput(level string, logFunc func()) string {
12 var buf bytes.Buffer
13
14 // Save original logger
15 originalLogger := slog.Default()
16 defer slog.SetDefault(originalLogger)
17
18 // Parse level
19 var logLevel slog.Level
20 switch strings.ToLower(strings.TrimSpace(level)) {
21 case "debug":
22 logLevel = slog.LevelDebug
23 case "info", "":
24 logLevel = slog.LevelInfo
25 case "warn", "warning":
26 logLevel = slog.LevelWarn
27 case "error":
28 logLevel = slog.LevelError
29 default:
30 logLevel = slog.LevelInfo
31 }
32
33 // Create logger that writes to buffer
34 opts := &slog.HandlerOptions{
35 Level: logLevel,
36 }
37 handler := slog.NewTextHandler(&buf, opts)
38 slog.SetDefault(slog.New(handler))
39
40 // Run the function that generates logs
41 logFunc()
42
43 return buf.String()
44}
45
46func TestInitLogger(t *testing.T) {
47 // Save original logger to restore after all tests
48 originalLogger := slog.Default()
49 defer slog.SetDefault(originalLogger)
50
51 tests := []struct {
52 name string
53 level string
54 shouldLogDebug bool
55 shouldLogInfo bool
56 shouldLogWarn bool
57 shouldLogError bool
58 }{
59 {
60 name: "debug level logs all",
61 level: "debug",
62 shouldLogDebug: true,
63 shouldLogInfo: true,
64 shouldLogWarn: true,
65 shouldLogError: true,
66 },
67 {
68 name: "info level logs info and above",
69 level: "info",
70 shouldLogDebug: false,
71 shouldLogInfo: true,
72 shouldLogWarn: true,
73 shouldLogError: true,
74 },
75 {
76 name: "warn level logs warn and above",
77 level: "warn",
78 shouldLogDebug: false,
79 shouldLogInfo: false,
80 shouldLogWarn: true,
81 shouldLogError: true,
82 },
83 {
84 name: "error level logs only errors",
85 level: "error",
86 shouldLogDebug: false,
87 shouldLogInfo: false,
88 shouldLogWarn: false,
89 shouldLogError: true,
90 },
91 {
92 name: "empty level defaults to info",
93 level: "",
94 shouldLogDebug: false,
95 shouldLogInfo: true,
96 shouldLogWarn: true,
97 shouldLogError: true,
98 },
99 {
100 name: "invalid level defaults to info",
101 level: "invalid",
102 shouldLogDebug: false,
103 shouldLogInfo: true,
104 shouldLogWarn: true,
105 shouldLogError: true,
106 },
107 {
108 name: "case insensitive - DEBUG",
109 level: "DEBUG",
110 shouldLogDebug: true,
111 shouldLogInfo: true,
112 shouldLogWarn: true,
113 shouldLogError: true,
114 },
115 {
116 name: "case insensitive - WaRn",
117 level: "WaRn",
118 shouldLogDebug: false,
119 shouldLogInfo: false,
120 shouldLogWarn: true,
121 shouldLogError: true,
122 },
123 {
124 name: "whitespace handling - ' info '",
125 level: " info ",
126 shouldLogDebug: false,
127 shouldLogInfo: true,
128 shouldLogWarn: true,
129 shouldLogError: true,
130 },
131 {
132 name: "warning alias for warn",
133 level: "warning",
134 shouldLogDebug: false,
135 shouldLogInfo: false,
136 shouldLogWarn: true,
137 shouldLogError: true,
138 },
139 }
140
141 for _, tt := range tests {
142 t.Run(tt.name, func(t *testing.T) {
143 output := captureLogOutput(tt.level, func() {
144 slog.Debug("debug message")
145 slog.Info("info message")
146 slog.Warn("warn message")
147 slog.Error("error message")
148 })
149
150 // Check debug
151 if tt.shouldLogDebug {
152 if !strings.Contains(output, "debug message") {
153 t.Errorf("Expected debug message to be logged")
154 }
155 } else {
156 if strings.Contains(output, "debug message") {
157 t.Errorf("Did not expect debug message to be logged")
158 }
159 }
160
161 // Check info
162 if tt.shouldLogInfo {
163 if !strings.Contains(output, "info message") {
164 t.Errorf("Expected info message to be logged")
165 }
166 } else {
167 if strings.Contains(output, "info message") {
168 t.Errorf("Did not expect info message to be logged")
169 }
170 }
171
172 // Check warn
173 if tt.shouldLogWarn {
174 if !strings.Contains(output, "warn message") {
175 t.Errorf("Expected warn message to be logged")
176 }
177 } else {
178 if strings.Contains(output, "warn message") {
179 t.Errorf("Did not expect warn message to be logged")
180 }
181 }
182
183 // Check error
184 if tt.shouldLogError {
185 if !strings.Contains(output, "error message") {
186 t.Errorf("Expected error message to be logged")
187 }
188 } else {
189 if strings.Contains(output, "error message") {
190 t.Errorf("Did not expect error message to be logged")
191 }
192 }
193 })
194 }
195}
196
197func TestInitLogger_LogLevels(t *testing.T) {
198 // Save original logger
199 originalLogger := slog.Default()
200 defer slog.SetDefault(originalLogger)
201
202 // Test that InitLogger actually calls slog.SetDefault
203 InitLogger("debug")
204
205 // Create a buffer to capture output
206 var buf bytes.Buffer
207 handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{
208 Level: slog.LevelDebug,
209 })
210 slog.SetDefault(slog.New(handler))
211
212 // Log at debug level
213 slog.Debug("test debug message")
214
215 // Verify output contains the message
216 if !strings.Contains(buf.String(), "test debug message") {
217 t.Error("Debug message not logged after InitLogger")
218 }
219}
220
221func TestSetupTestLogger(t *testing.T) {
222 // Save original logger
223 originalLogger := slog.Default()
224 defer slog.SetDefault(originalLogger)
225
226 // Test 1: SetupTestLogger suppresses INFO and DEBUG
227 cleanup := SetupTestLogger()
228
229 // Create a buffer to capture what SHOULD be discarded
230 // (but we can't really test io.Discard directly, so we'll test behavior)
231
232 // Log at different levels - since it's set to WARN, debug/info should be suppressed
233 // We can't capture io.Discard output, but we can verify the logger is configured correctly
234 logger := slog.Default()
235
236 // Verify handler is configured to discard
237 if logger == nil {
238 t.Error("Expected logger to be set")
239 }
240
241 // Test 2: Cleanup restores original logger
242 cleanup()
243
244 if slog.Default() != originalLogger {
245 t.Error("Expected cleanup to restore original logger")
246 }
247}
248
249func TestSetupTestLogger_LevelFiltering(t *testing.T) {
250 // Save original logger
251 originalLogger := slog.Default()
252 defer slog.SetDefault(originalLogger)
253
254 // Setup test logger (WARN level, io.Discard)
255 cleanup := SetupTestLogger()
256 defer cleanup()
257
258 // Replace the handler output with a buffer so we can test
259 // (This is a bit of a workaround since the real SetupTestLogger uses io.Discard)
260 var buf bytes.Buffer
261 handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{
262 Level: slog.LevelWarn,
263 })
264 slog.SetDefault(slog.New(handler))
265
266 // Log at different levels
267 slog.Debug("debug message")
268 slog.Info("info message")
269 slog.Warn("warn message")
270 slog.Error("error message")
271
272 output := buf.String()
273
274 // Debug and Info should NOT be in output (filtered by WARN level)
275 if strings.Contains(output, "debug message") {
276 t.Error("Debug message should be filtered out at WARN level")
277 }
278 if strings.Contains(output, "info message") {
279 t.Error("Info message should be filtered out at WARN level")
280 }
281
282 // Warn and Error SHOULD be in output
283 if !strings.Contains(output, "warn message") {
284 t.Error("Warn message should be logged at WARN level")
285 }
286 if !strings.Contains(output, "error message") {
287 t.Error("Error message should be logged at WARN level")
288 }
289}
290
291func TestSetupTestLogger_UsageWithTCleanup(t *testing.T) {
292 // This test demonstrates the intended usage pattern
293 originalLogger := slog.Default()
294
295 // Simulate using SetupTestLogger in a test
296 cleanup := SetupTestLogger()
297 t.Cleanup(cleanup)
298
299 // Logger should be different now
300 if slog.Default() == originalLogger {
301 t.Error("Expected logger to be changed after SetupTestLogger")
302 }
303
304 // When test ends, t.Cleanup will run and restore the logger
305 // We can't directly test this since it happens after the test function returns,
306 // but we're verifying the pattern works
307}
308
309func TestSetupTestLogger_MultipleCallsIndependent(t *testing.T) {
310 // Save original logger
311 originalLogger := slog.Default()
312 defer slog.SetDefault(originalLogger)
313
314 // First call
315 cleanup1 := SetupTestLogger()
316 logger1 := slog.Default()
317
318 // Second call
319 cleanup2 := SetupTestLogger()
320 logger2 := slog.Default()
321
322 // Loggers might be different instances
323 if logger1 == nil || logger2 == nil {
324 t.Error("Expected loggers to be set")
325 }
326
327 // Cleanup in reverse order (like defer)
328 cleanup2()
329 cleanup1()
330}
331
332func TestInitLogger_OutputFormat(t *testing.T) {
333 // Save original logger
334 originalLogger := slog.Default()
335 defer slog.SetDefault(originalLogger)
336
337 var buf bytes.Buffer
338
339 // Configure logger with buffer
340 opts := &slog.HandlerOptions{
341 Level: slog.LevelInfo,
342 }
343 handler := slog.NewTextHandler(&buf, opts)
344 slog.SetDefault(slog.New(handler))
345
346 // Log a message
347 slog.Info("test message", "key", "value")
348
349 output := buf.String()
350
351 // Verify text format (not JSON)
352 if !strings.Contains(output, "test message") {
353 t.Error("Expected message in output")
354 }
355 if !strings.Contains(output, "key=value") {
356 t.Error("Expected key=value in text format")
357 }
358 // Should NOT be JSON
359 if strings.HasPrefix(output, "{") {
360 t.Error("Expected text format, not JSON")
361 }
362}
363
364func BenchmarkInitLogger(b *testing.B) {
365 originalLogger := slog.Default()
366 defer slog.SetDefault(originalLogger)
367
368 b.ResetTimer()
369 for i := 0; i < b.N; i++ {
370 InitLogger("info")
371 }
372}
373
374func BenchmarkSetupTestLogger(b *testing.B) {
375 originalLogger := slog.Default()
376 defer slog.SetDefault(originalLogger)
377
378 b.ResetTimer()
379 for i := 0; i < b.N; i++ {
380 cleanup := SetupTestLogger()
381 cleanup()
382 }
383}
384
385// Example test showing how to use SetupTestLogger
386func ExampleSetupTestLogger() {
387 // In a test function:
388 cleanup := SetupTestLogger()
389 defer cleanup()
390
391 // Now logs at DEBUG and INFO are suppressed
392 slog.Debug("This won't show")
393 slog.Info("This won't show either")
394 slog.Warn("This WILL show")
395
396 // cleanup() will restore the original logger when defer runs
397}