package telemetry import ( "bytes" "context" "errors" "fmt" "io" "os" "strings" "testing" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) func setupEnabledTelemetry(t *testing.T) { t.Helper() tp := sdktrace.NewTracerProvider(sdktrace.WithResource(resource.Default())) mp := sdkmetric.NewMeterProvider(sdkmetric.WithResource(resource.Default())) otel.SetTracerProvider(tp) otel.SetMeterProvider(mp) tracerProvider = tp meterProvider = mp initialized = true shutdownFuncs = []func(context.Context) error{ func(ctx context.Context) error { return tp.Shutdown(ctx) }, func(ctx context.Context) error { return mp.Shutdown(ctx) }, } initMetricsOnce = false t.Cleanup(func() { _ = tp.Shutdown(context.Background()) _ = mp.Shutdown(context.Background()) tracerProvider = nil meterProvider = nil initialized = false shutdownFuncs = nil initMetricsOnce = false }) } func captureStderr(t *testing.T, fn func()) string { t.Helper() r, w, err := os.Pipe() if err != nil { t.Fatal(err) } oldStderr := os.Stderr os.Stderr = w defer func() { os.Stderr = oldStderr }() fn() _ = w.Close() var buf bytes.Buffer _, _ = io.Copy(&buf, r) return buf.String() } func TestEvent_Enabled(t *testing.T) { setupEnabledTelemetry(t) ctx := context.Background() Event(ctx, "test.event", attribute.String("key", "value")) } func TestEvent_Disabled(t *testing.T) { initialized = false shutdownFuncs = nil defer func() { initialized = false }() Event(context.Background(), "test.event") } func TestEvent_NilCtx(t *testing.T) { setupEnabledTelemetry(t) Event(nil, "test.event") //nolint:staticcheck } func TestEventf_Enabled(t *testing.T) { setupEnabledTelemetry(t) ctx := context.Background() Eventf(ctx, "test.eventf", "hello world", attribute.Int("count", 5)) } func TestErrorEvent_Enabled(t *testing.T) { setupEnabledTelemetry(t) ctx := context.Background() ErrorEvent(ctx, "test.error", errors.New("something failed"), attribute.String("detail", "extra")) } func TestErrorEvent_NilErr(t *testing.T) { setupEnabledTelemetry(t) ctx := context.Background() ErrorEvent(ctx, "test.error", nil) } func TestErrorEvent_NilCtx(t *testing.T) { setupEnabledTelemetry(t) ErrorEvent(nil, "test.error", errors.New("fail")) //nolint:staticcheck } func TestErrorEvent_Disabled(t *testing.T) { initialized = false shutdownFuncs = nil defer func() { initialized = false }() ErrorEvent(context.Background(), "test.error", errors.New("fail")) } func TestPhaseEvent_Success(t *testing.T) { setupEnabledTelemetry(t) ctx := context.Background() PhaseEvent(ctx, "scan", "main.go", 500*time.Millisecond, nil) } func TestPhaseEvent_WithError(t *testing.T) { setupEnabledTelemetry(t) ctx := context.Background() PhaseEvent(ctx, "scan", "main.go", 500*time.Millisecond, errors.New("parse error")) } func TestPrintTraceSummary_WithTokenDetails(t *testing.T) { PrintTraceSummary(5, 10, 1000, 200, 1200, 0, 0, 3*time.Second) } func TestPrintTraceSummary_WithCacheTokens(t *testing.T) { PrintTraceSummary(3, 2, 500, 100, 600, 200, 50, 2*time.Second) } func TestPrintTraceSummary_NoTokenDetails(t *testing.T) { PrintTraceSummary(2, 1, 0, 0, 500, 0, 0, 1*time.Second) } func TestPrintToolCallStarted_WithArgs(t *testing.T) { PrintToolCallStarted("file_read", map[string]any{"path": "main.go"}) } func TestPrintToolCallStarted_NoArgs(t *testing.T) { PrintToolCallStarted("list_files", nil) } func TestPrintToolCallFinished(t *testing.T) { PrintToolCallFinished("file_read", 123*time.Millisecond) } func TestPrintToolCallError(t *testing.T) { out := captureStderr(t, func() { PrintToolCallError("file_read", fmt.Errorf("permission denied")) }) if !strings.Contains(out, "✘ file_read") { t.Errorf("expected tool name with X mark, got %q", out) } if !strings.Contains(out, "permission denied") { t.Errorf("expected error message, got %q", out) } } func TestFormatDuration(t *testing.T) { tests := []struct { dur time.Duration want string }{ {0, "0s"}, {1500 * time.Millisecond, "1.5s"}, {60 * time.Second, "1m0s"}, {123 * time.Millisecond, "123ms"}, {2*time.Minute + 30*time.Second, "2m30s"}, } for _, tc := range tests { got := FormatDuration(tc.dur) if got != tc.want { t.Errorf("FormatDuration(%v) = %q, want %q", tc.dur, got, tc.want) } } } func TestSummarizeArgs(t *testing.T) { tests := []struct { name string args map[string]any want string }{ {"nil map", nil, ""}, {"empty map", map[string]any{}, ""}, {"path key returns quoted", map[string]any{"path": "foo/bar.go"}, `"foo/bar.go"`}, {"search key returns quoted", map[string]any{"search": "hello"}, `"hello"`}, {"query key returns quoted", map[string]any{"query": "world"}, `"world"`}, {"pattern key returns quoted", map[string]any{"pattern": "*.go"}, `"*.go"`}, {"generic short value", map[string]any{"foo": "bar"}, "foo=bar"}, {"long value skipped", map[string]any{"data": string(make([]byte, 60))}, ""}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := summarizeArgs(tc.args) if got != tc.want { t.Errorf("summarizeArgs(%v) = %q, want %q", tc.args, got, tc.want) } }) } }