summaryrefslogtreecommitdiff
path: root/internal/logging/logging_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 03:14:46 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 03:14:46 +0200
commita71136dc8c2a51dcaa49e4af5e42b3c6bffd6fa0 (patch)
treeed05684846c4178a0003d0324645ea8dc49978d4 /internal/logging/logging_test.go
parent1fc1611fa99993cab5dc8bf0844183285296e3b2 (diff)
Fix data races in logging package using atomic operations
Replace bare package-level vars with atomic.Pointer[log.Logger] for std and atomic.Int32 for logPreviewLimit to prevent concurrent access races. Add comprehensive tests including concurrency, edge cases, and nil logger. Coverage: 100%. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/logging/logging_test.go')
-rw-r--r--internal/logging/logging_test.go70
1 files changed, 70 insertions, 0 deletions
diff --git a/internal/logging/logging_test.go b/internal/logging/logging_test.go
index 31603f0..87eb096 100644
--- a/internal/logging/logging_test.go
+++ b/internal/logging/logging_test.go
@@ -3,6 +3,7 @@ package logging
import (
"bytes"
"log"
+ "sync"
"testing"
)
@@ -21,3 +22,72 @@ func TestPreviewAndLogfAndChatLogger(t *testing.T) {
t.Fatalf("expected logged content, got %q", out)
}
}
+
+// TestPreviewForLog_ShortString verifies no truncation when string fits within the limit.
+func TestPreviewForLog_ShortString(t *testing.T) {
+ SetLogPreviewLimit(10)
+ if got := PreviewForLog("abc"); got != "abc" {
+ t.Fatalf("expected no truncation, got %q", got)
+ }
+}
+
+// TestPreviewForLog_ExactLimit verifies no truncation when string length equals the limit.
+func TestPreviewForLog_ExactLimit(t *testing.T) {
+ SetLogPreviewLimit(3)
+ if got := PreviewForLog("abc"); got != "abc" {
+ t.Fatalf("expected no truncation for exact limit, got %q", got)
+ }
+}
+
+// TestPreviewForLog_ZeroLimit verifies unlimited preview when limit is 0.
+func TestPreviewForLog_ZeroLimit(t *testing.T) {
+ SetLogPreviewLimit(0)
+ long := "this is a long string that should not be truncated"
+ if got := PreviewForLog(long); got != long {
+ t.Fatalf("expected full string with limit=0, got %q", got)
+ }
+}
+
+// TestPreviewForLog_NegativeLimit verifies negative limit behaves like unlimited.
+func TestPreviewForLog_NegativeLimit(t *testing.T) {
+ SetLogPreviewLimit(-5)
+ s := "should not truncate"
+ if got := PreviewForLog(s); got != s {
+ t.Fatalf("expected full string with negative limit, got %q", got)
+ }
+}
+
+// TestPreviewForLog_EmptyString verifies empty input returns empty output.
+func TestPreviewForLog_EmptyString(t *testing.T) {
+ SetLogPreviewLimit(10)
+ if got := PreviewForLog(""); got != "" {
+ t.Fatalf("expected empty string, got %q", got)
+ }
+}
+
+// TestLogf_NilLogger verifies Logf does not panic when no logger is bound.
+func TestLogf_NilLogger(t *testing.T) {
+ // Store a nil logger to test the nil-check path
+ std.Store(nil)
+ // Should not panic
+ Logf("test ", "should be a no-op %s", "value")
+}
+
+// TestConcurrentAccess verifies atomic operations prevent data races
+// under concurrent usage of Bind, Logf, SetLogPreviewLimit, and PreviewForLog.
+func TestConcurrentAccess(t *testing.T) {
+ var buf bytes.Buffer
+ Bind(log.New(&buf, "", 0))
+
+ var wg sync.WaitGroup
+ for i := 0; i < 20; i++ {
+ wg.Add(1)
+ go func(n int) {
+ defer wg.Done()
+ SetLogPreviewLimit(n + 1)
+ _ = PreviewForLog("concurrent access test string")
+ Logf("goroutine ", "n=%d", n)
+ }(i)
+ }
+ wg.Wait()
+}