package telemetry import ( "context" "net" "testing" "time" "go.opentelemetry.io/otel" sdktrace "go.opentelemetry.io/otel/sdk/trace" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" ) // fakeCollector starts a TCP listener that accepts connections but speaks no protocol. // Returns the address and a cleanup function. func fakeCollector(t *testing.T) string { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("fakeCollector: %v", err) } t.Cleanup(func() { ln.Close() }) // Accept in background so gRPC dial doesn't get refused. go func() { for { conn, err := ln.Accept() if err != nil { return } conn.Close() } }() return ln.Addr().String() } func cfgWith(endpoint string) Config { return Config{ ServiceName: "test-service", ServiceVersion: "0.0.1", Environment: "test", OTLPEndpoint: endpoint, OTLPInsecure: true, } } func TestNew_ShutdownCallable(t *testing.T) { endpoint := fakeCollector(t) ctx := context.Background() shutdown, err := New(ctx, cfgWith(endpoint)) if err != nil { t.Fatalf("New: %v", err) } if shutdown == nil { t.Fatal("shutdown func is nil") } // Use a short timeout: the fake server doesn't speak gRPC so flush will fail, // but we only care that shutdown is callable and returns (not that export succeeds). shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() _ = shutdown(shutCtx) // export errors with fake server are expected } func TestNew_SetsGlobalTracerProvider(t *testing.T) { endpoint := fakeCollector(t) ctx := context.Background() shutdown, err := New(ctx, cfgWith(endpoint)) if err != nil { t.Fatalf("New: %v", err) } t.Cleanup(func() { shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() _ = shutdown(shutCtx) }) tp := otel.GetTracerProvider() if _, ok := tp.(*sdktrace.TracerProvider); !ok { t.Errorf("expected *sdktrace.TracerProvider, got %T", tp) } } func TestNew_SetsGlobalMeterProvider(t *testing.T) { endpoint := fakeCollector(t) ctx := context.Background() shutdown, err := New(ctx, cfgWith(endpoint)) if err != nil { t.Fatalf("New: %v", err) } t.Cleanup(func() { shutCtx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) defer cancel() _ = shutdown(shutCtx) }) mp := otel.GetMeterProvider() if _, ok := mp.(*sdkmetric.MeterProvider); !ok { t.Errorf("expected *sdkmetric.MeterProvider, got %T", mp) } } func TestNewResource_Fields(t *testing.T) { cfg := Config{ ServiceName: "my-service", ServiceVersion: "2.0.0", Environment: "staging", } res := newResource(cfg) check := func(key, want string) { t.Helper() for _, kv := range res.Attributes() { if string(kv.Key) == key { if got := kv.Value.AsString(); got != want { t.Errorf("resource[%s]: want %q, got %q", key, want, got) } return } } t.Errorf("resource attribute %q not found", key) } check(string(semconv.ServiceNameKey), "my-service") check(string(semconv.ServiceVersionKey), "2.0.0") check("deployment.environment", "staging") } func TestNewResource_MergesWithDefault(t *testing.T) { cfg := Config{ServiceName: "svc"} res := newResource(cfg) if res == nil { t.Fatal("newResource returned nil") } // Default resource contributes service.instance.id and telemetry.sdk.* fields. if len(res.Attributes()) == 0 { t.Error("resource has no attributes") } } func TestNewResource_IsNotDefault(t *testing.T) { cfg := Config{ServiceName: "unique-service-name"} res := newResource(cfg) def := resource.Default() // Custom resource must differ from the bare default: it has extra attributes. if len(res.Attributes()) <= len(def.Attributes()) { t.Error("newResource added no attributes beyond the default") } }