package trace import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/zeromicro/go-zero/core/logx" "go.opentelemetry.io/otel" ) func TestStartAgent(t *testing.T) { logx.Disable() const ( endpoint1 = "localhost:1234" endpoint2 = "remotehost:1234" endpoint3 = "localhost:1235" endpoint4 = "localhost:1236" endpoint5 = "udp://localhost:6831" endpoint6 = "localhost:1237" endpoint71 = "/tmp/trace.log" endpoint72 = "/not-exist-fs/trace.log" ) c1 := Config{ Name: "foo", } c2 := Config{ Name: "any", Endpoint: endpoint2, Batcher: kindZipkin, } c3 := Config{ Name: "bla", Endpoint: endpoint3, Batcher: "otlp", } c4 := Config{ Name: "otlpgrpc", Endpoint: endpoint3, Batcher: kindOtlpGrpc, OtlpHeaders: map[string]string{ "uptrace-dsn": "http://project2_secret_token@localhost:14317/2", }, } c5 := Config{ Name: "otlphttp", Endpoint: endpoint4, Batcher: kindOtlpHttp, OtlpHeaders: map[string]string{ "uptrace-dsn": "http://project2_secret_token@localhost:14318/2", }, OtlpHttpPath: "/v1/traces", } c6 := Config{ Name: "file", Endpoint: endpoint71, Batcher: kindFile, } c7 := Config{ Name: "file", Endpoint: endpoint72, Batcher: kindFile, } StartAgent(c1) StartAgent(c1) StartAgent(c2) StartAgent(c3) StartAgent(c4) StartAgent(c5) StartAgent(c6) StartAgent(c7) defer StopAgent() // With sync.Once, only the first non-disabled config (c1) takes effect. // Subsequent calls are ignored, which is the desired behavior to prevent // multiple servers (REST + RPC) from reinitializing the global tracer. assert.NotNil(t, tp) } func TestCreateExporter_InvalidFilePath(t *testing.T) { logx.Disable() c := Config{ Name: "test-invalid-file", Endpoint: "/non-existent-directory/trace.log", Batcher: kindFile, } _, err := createExporter(c) assert.Error(t, err) assert.Contains(t, err.Error(), "file exporter endpoint error") } func TestCreateExporter_UnknownBatcher(t *testing.T) { logx.Disable() c := Config{ Name: "test-unknown", Endpoint: "localhost:1234", Batcher: "unknown-batcher-type", } _, err := createExporter(c) assert.Error(t, err) assert.Contains(t, err.Error(), "unknown exporter") } func TestCreateExporter_ValidExporters(t *testing.T) { logx.Disable() tests := []struct { name string config Config wantErr bool errMsg string }{ { name: "valid file exporter", config: Config{ Name: "file-test", Endpoint: "/tmp/trace-test.log", Batcher: kindFile, }, wantErr: false, }, { name: "invalid file path", config: Config{ Name: "file-test-invalid", Endpoint: "/invalid-path/that/does/not/exist/trace.log", Batcher: kindFile, }, wantErr: true, errMsg: "file exporter endpoint error", }, { name: "unknown batcher", config: Config{ Name: "unknown-test", Endpoint: "localhost:1234", Batcher: "invalid-batcher", }, wantErr: true, errMsg: "unknown exporter", }, { name: "zipkin", config: Config{ Name: "zipkin", Endpoint: "http://localhost:9411/api/v2/spans", Batcher: kindZipkin, }, wantErr: false, }, { name: "otlpgrpc", config: Config{ Name: "otlpgrpc", Endpoint: "localhost:4317", Batcher: kindOtlpGrpc, }, wantErr: false, }, { name: "otlpgrpc with headers", config: Config{ Name: "otlpgrpc-headers", Endpoint: "localhost:4317", Batcher: kindOtlpGrpc, OtlpHeaders: map[string]string{ "authorization": "Bearer token123", "x-custom-key": "custom-value", }, }, wantErr: false, }, { name: "otlphttp", config: Config{ Name: "otlphttp", Endpoint: "localhost:4318", Batcher: kindOtlpHttp, }, wantErr: false, }, { name: "otlphttp with headers", config: Config{ Name: "otlphttp-headers", Endpoint: "localhost:4318", Batcher: kindOtlpHttp, OtlpHeaders: map[string]string{ "authorization": "Bearer token456", "x-api-key": "api-key-value", }, }, wantErr: false, }, { name: "otlphttp with headers and path", config: Config{ Name: "otlphttp-headers-path", Endpoint: "localhost:4318", Batcher: kindOtlpHttp, OtlpHttpPath: "/v1/traces", OtlpHeaders: map[string]string{ "authorization": "Bearer token789", "x-custom-trace": "trace-id", }, }, wantErr: false, }, { name: "otlphttp with secure connection", config: Config{ Name: "otlphttp-secure", Endpoint: "localhost:4318", Batcher: kindOtlpHttp, OtlpHttpSecure: true, OtlpHeaders: map[string]string{ "authorization": "Bearer secure-token", }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { exporter, err := createExporter(tt.config) if tt.wantErr { assert.Error(t, err) if tt.errMsg != "" { assert.Contains(t, err.Error(), tt.errMsg) } assert.Nil(t, exporter) } else { assert.NoError(t, err) assert.NotNil(t, exporter) // Clean up the exporter if exporter != nil { _ = exporter.Shutdown(context.Background()) } } }) } } func TestStopAgent(t *testing.T) { logx.Disable() // StopAgent should be idempotent and safe to call multiple times assert.NotPanics(t, func() { StopAgent() StopAgent() StopAgent() }) } func TestStartAgent_WithEndpoint(t *testing.T) { logx.Disable() tests := []struct { name string config Config wantErr bool }{ { name: "empty endpoint - no exporter created", config: Config{ Name: "test-no-endpoint", Sampler: 1.0, }, wantErr: false, }, { name: "valid endpoint with file exporter", config: Config{ Name: "test-with-endpoint", Endpoint: "/tmp/test-trace.log", Batcher: kindFile, Sampler: 1.0, }, wantErr: false, }, { name: "endpoint with invalid exporter type", config: Config{ Name: "test-invalid-batcher", Endpoint: "localhost:1234", Batcher: "invalid-type", Sampler: 1.0, }, wantErr: true, }, { name: "endpoint with invalid file path", config: Config{ Name: "test-invalid-path", Endpoint: "/non/existent/path/trace.log", Batcher: kindFile, Sampler: 1.0, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Reset tp for each test originalTp := tp tp = nil defer func() { if tp != nil { _ = tp.Shutdown(context.Background()) } tp = originalTp }() err := startAgent(tt.config) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.NotNil(t, tp, "TracerProvider should be created") } }) } } func TestStartAgent_ErrorHandler(t *testing.T) { // Setup a tracer provider to test error handler originalTp := tp tp = nil defer func() { if tp != nil { _ = tp.Shutdown(context.Background()) } tp = originalTp }() // Call startAgent to set up the error handler config := Config{ Name: "test-error-handler", Sampler: 1.0, } err := startAgent(config) assert.NoError(t, err) assert.NotNil(t, tp) // Verify the error handler was set and can be called without panicking // We test this by calling otel.Handle which will invoke the registered error handler testErr := errors.New("test otel error") assert.NotPanics(t, func() { otel.Handle(testErr) }, "Error handler should handle errors without panicking") }