| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package otel | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "io" | |
| "net/http" | |
| "net/http/httptest" | |
| "sync" | |
| "testing" | |
| "golang.org/x/tools/internal/event" | |
| "golang.org/x/tools/internal/event/core" | |
| "golang.org/x/tools/internal/event/export" | |
| "golang.org/x/tools/internal/event/label" | |
| ) | |
| func TestConvertSpan(t *testing.T) { | |
| span := &export.Span{ | |
| Name: "test-operation", | |
| ID: export.SpanContext{ | |
| TraceID: export.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, | |
| SpanID: export.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, | |
| }, | |
| ParentID: export.SpanID{8, 7, 6, 5, 4, 3, 2, 1}, | |
| } | |
| result := convertSpan(span) | |
| if result.Name != "test-operation" { | |
| t.Errorf("expected name 'test-operation', got %q", result.Name) | |
| } | |
| if result.TraceID != "0102030405060708090a0b0c0d0e0f10" { | |
| t.Errorf("unexpected traceId: %s", result.TraceID) | |
| } | |
| if result.SpanID != "0102030405060708" { | |
| t.Errorf("unexpected spanId: %s", result.SpanID) | |
| } | |
| if result.ParentSpanID != "0807060504030201" { | |
| t.Errorf("unexpected parentSpanId: %s", result.ParentSpanID) | |
| } | |
| if result.Kind != 1 { | |
| t.Errorf("expected kind 1, got %d", result.Kind) | |
| } | |
| if result.Status.Code != 0 { | |
| t.Errorf("expected status code 0, got %d", result.Status.Code) | |
| } | |
| } | |
| // TestE2E_SpanExport is a minimal end-to-end test verifying the full pipeline. | |
| func TestE2E_SpanExport(t *testing.T) { | |
| var received []byte | |
| var mu sync.Mutex | |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| mu.Lock() | |
| received, _ = io.ReadAll(r.Body) | |
| mu.Unlock() | |
| w.WriteHeader(http.StatusOK) | |
| })) | |
| defer server.Close() | |
| baseExporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context { | |
| return ctx | |
| } | |
| spansExporter := export.Spans(baseExporter) | |
| otelExporter := NewExporter(context.Background(), WithEndpoint(server.URL)) | |
| chainedExporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context { | |
| ctx = spansExporter(ctx, ev, lm) | |
| return otelExporter.ProcessEvent(ctx, ev, lm) | |
| } | |
| event.SetExporter(chainedExporter) | |
| ctx := context.Background() | |
| ctx, end := event.Start(ctx, "e2e-span") | |
| end() | |
| otelExporter.Flush() | |
| mu.Lock() | |
| data := received | |
| mu.Unlock() | |
| if len(data) == 0 { | |
| t.Fatal("no data received") | |
| } | |
| // Just verify it's valid JSON with a span | |
| var req otlpTraceExportRequest | |
| if err := json.Unmarshal(data, &req); err != nil { | |
| t.Fatalf("invalid JSON: %v", err) | |
| } | |
| if len(req.ResourceSpans) == 0 || | |
| len(req.ResourceSpans[0].ScopeSpans) == 0 || | |
| len(req.ResourceSpans[0].ScopeSpans[0].Spans) == 0 { | |
| t.Fatal("expected at least one span") | |
| } | |
| } |