package trace import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/metadata" ) const ( traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" ) var ( traceID = mustTraceIDFromHex(traceIDStr) spanID = mustSpanIDFromHex(spanIDStr) ) func mustTraceIDFromHex(s string) (t trace.TraceID) { var err error t, err = trace.TraceIDFromHex(s) if err != nil { panic(err) } return } func mustSpanIDFromHex(s string) (t trace.SpanID) { var err error t, err = trace.SpanIDFromHex(s) if err != nil { panic(err) } return } func TestExtractValidTraceContext(t *testing.T) { stateStr := "key1=value1,key2=value2" state, err := trace.ParseTraceState(stateStr) require.NoError(t, err) tests := []struct { name string traceparent string tracestate string sc trace.SpanContext }{ { name: "not sampled", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "sampled", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, Remote: true, }), }, { name: "valid tracestate", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", tracestate: stateStr, sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceState: state, Remote: true, }), }, { name: "invalid tracestate perserves traceparent", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", tracestate: "invalid$@#=invalid", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "future version not sampled", traceparent: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "future version sampled", traceparent: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, Remote: true, }), }, { name: "future version sample bit set", traceparent: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, Remote: true, }), }, { name: "future version sample bit not set", traceparent: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "future version additional data", traceparent: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00-XYZxsf09", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "B3 format ending in dash", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00-", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "future version B3 format ending in dash", traceparent: "03-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00-", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, } otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) propagator := otel.GetTextMapPropagator() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() md := metadata.MD{} md.Set("traceparent", tt.traceparent) md.Set("tracestate", tt.tracestate) _, spanCtx := Extract(ctx, propagator, &md) assert.Equal(t, tt.sc, spanCtx) }) } } func TestExtractInvalidTraceContext(t *testing.T) { tests := []struct { name string header string }{ { name: "wrong version length", header: "0000-00000000000000000000000000000000-0000000000000000-01", }, { name: "wrong trace ID length", header: "00-ab00000000000000000000000000000000-cd00000000000000-01", }, { name: "wrong span ID length", header: "00-ab000000000000000000000000000000-cd0000000000000000-01", }, { name: "wrong trace flag length", header: "00-ab000000000000000000000000000000-cd00000000000000-0100", }, { name: "bogus version", header: "qw-00000000000000000000000000000000-0000000000000000-01", }, { name: "bogus trace ID", header: "00-qw000000000000000000000000000000-cd00000000000000-01", }, { name: "bogus span ID", header: "00-ab000000000000000000000000000000-qw00000000000000-01", }, { name: "bogus trace flag", header: "00-ab000000000000000000000000000000-cd00000000000000-qw", }, { name: "upper case version", header: "A0-00000000000000000000000000000000-0000000000000000-01", }, { name: "upper case trace ID", header: "00-AB000000000000000000000000000000-cd00000000000000-01", }, { name: "upper case span ID", header: "00-ab000000000000000000000000000000-CD00000000000000-01", }, { name: "upper case trace flag", header: "00-ab000000000000000000000000000000-cd00000000000000-A1", }, { name: "zero trace ID and span ID", header: "00-00000000000000000000000000000000-0000000000000000-01", }, { name: "trace-flag unused bits set", header: "00-ab000000000000000000000000000000-cd00000000000000-09", }, { name: "missing options", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7", }, { name: "empty options", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-", }, } otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) propagator := otel.GetTextMapPropagator() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() md := metadata.MD{} md.Set("traceparent", tt.header) _, spanCtx := Extract(ctx, propagator, &md) assert.Equal(t, trace.SpanContext{}, spanCtx) }) } } func TestInjectValidTraceContext(t *testing.T) { stateStr := "key1=value1,key2=value2" state, err := trace.ParseTraceState(stateStr) require.NoError(t, err) tests := []struct { name string traceparent string tracestate string sc trace.SpanContext }{ { name: "not sampled", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, Remote: true, }), }, { name: "sampled", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled, Remote: true, }), }, { name: "unsupported trace flag bits dropped", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceFlags: 0xff, Remote: true, }), }, { name: "with tracestate", traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", tracestate: stateStr, sc: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, TraceState: state, Remote: true, }), }, } otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) propagator := otel.GetTextMapPropagator() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() ctx = trace.ContextWithRemoteSpanContext(ctx, tt.sc) want := metadata.MD{} want.Set("traceparent", tt.traceparent) if len(tt.tracestate) > 0 { want.Set("tracestate", tt.tracestate) } md := metadata.MD{} Inject(ctx, propagator, &md) assert.Equal(t, want, md) mm := &metadataSupplier{ metadata: &md, } assert.NotEmpty(t, mm.Keys()) }) } } func TestInvalidSpanContextDropped(t *testing.T) { invalidSC := trace.SpanContext{} require.False(t, invalidSC.IsValid()) ctx := trace.ContextWithRemoteSpanContext(context.Background(), invalidSC) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})) propagator := otel.GetTextMapPropagator() md := metadata.MD{} Inject(ctx, propagator, &md) mm := &metadataSupplier{ metadata: &md, } assert.Empty(t, mm.Keys()) assert.Equal(t, "", mm.Get("traceparent"), "injected invalid SpanContext") }