1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package propagation_test
16
17 import (
18 "context"
19 "net/http"
20 "testing"
21
22 "github.com/stretchr/testify/assert"
23 "github.com/stretchr/testify/require"
24
25 "go.opentelemetry.io/otel/propagation"
26 "go.opentelemetry.io/otel/trace"
27 )
28
29 var (
30 traceparent = http.CanonicalHeaderKey("traceparent")
31 tracestate = http.CanonicalHeaderKey("tracestate")
32
33 prop = propagation.TraceContext{}
34 )
35
36 type testcase struct {
37 name string
38 header http.Header
39 sc trace.SpanContext
40 }
41
42 func TestExtractValidTraceContext(t *testing.T) {
43 stateStr := "key1=value1,key2=value2"
44 state, err := trace.ParseTraceState(stateStr)
45 require.NoError(t, err)
46
47 tests := []testcase{
48 {
49 name: "not sampled",
50 header: http.Header{
51 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"},
52 },
53 sc: trace.NewSpanContext(trace.SpanContextConfig{
54 TraceID: traceID,
55 SpanID: spanID,
56 Remote: true,
57 }),
58 },
59 {
60 name: "sampled",
61 header: http.Header{
62 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
63 },
64 sc: trace.NewSpanContext(trace.SpanContextConfig{
65 TraceID: traceID,
66 SpanID: spanID,
67 TraceFlags: trace.FlagsSampled,
68 Remote: true,
69 }),
70 },
71 {
72 name: "valid tracestate",
73 header: http.Header{
74 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"},
75 tracestate: []string{stateStr},
76 },
77 sc: trace.NewSpanContext(trace.SpanContextConfig{
78 TraceID: traceID,
79 SpanID: spanID,
80 TraceState: state,
81 Remote: true,
82 }),
83 },
84 {
85 name: "invalid tracestate preserves traceparent",
86 header: http.Header{
87 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"},
88 tracestate: []string{"invalid$@#=invalid"},
89 },
90 sc: trace.NewSpanContext(trace.SpanContextConfig{
91 TraceID: traceID,
92 SpanID: spanID,
93 Remote: true,
94 }),
95 },
96 {
97 name: "future version not sampled",
98 header: http.Header{
99 traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"},
100 },
101 sc: trace.NewSpanContext(trace.SpanContextConfig{
102 TraceID: traceID,
103 SpanID: spanID,
104 Remote: true,
105 }),
106 },
107 {
108 name: "future version sampled",
109 header: http.Header{
110 traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
111 },
112 sc: trace.NewSpanContext(trace.SpanContextConfig{
113 TraceID: traceID,
114 SpanID: spanID,
115 TraceFlags: trace.FlagsSampled,
116 Remote: true,
117 }),
118 },
119 {
120 name: "future version sample bit set",
121 header: http.Header{
122 traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09"},
123 },
124 sc: trace.NewSpanContext(trace.SpanContextConfig{
125 TraceID: traceID,
126 SpanID: spanID,
127 TraceFlags: trace.FlagsSampled,
128 Remote: true,
129 }),
130 },
131 {
132 name: "future version sample bit not set",
133 header: http.Header{
134 traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08"},
135 },
136 sc: trace.NewSpanContext(trace.SpanContextConfig{
137 TraceID: traceID,
138 SpanID: spanID,
139 Remote: true,
140 }),
141 },
142 {
143 name: "future version additional data",
144 header: http.Header{
145 traceparent: []string{"02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00-XYZxsf09"},
146 },
147 sc: trace.NewSpanContext(trace.SpanContextConfig{
148 TraceID: traceID,
149 SpanID: spanID,
150 Remote: true,
151 }),
152 },
153 {
154 name: "B3 format ending in dash",
155 header: http.Header{
156 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00-"},
157 },
158 sc: trace.NewSpanContext(trace.SpanContextConfig{
159 TraceID: traceID,
160 SpanID: spanID,
161 Remote: true,
162 }),
163 },
164 {
165 name: "future version B3 format ending in dash",
166 header: http.Header{
167 traceparent: []string{"03-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00-"},
168 },
169 sc: trace.NewSpanContext(trace.SpanContextConfig{
170 TraceID: traceID,
171 SpanID: spanID,
172 Remote: true,
173 }),
174 },
175 }
176
177 for _, tc := range tests {
178 t.Run(tc.name, func(t *testing.T) {
179 ctx := context.Background()
180 ctx = prop.Extract(ctx, propagation.HeaderCarrier(tc.header))
181 assert.Equal(t, tc.sc, trace.SpanContextFromContext(ctx))
182 })
183 }
184 }
185
186 func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) {
187 tests := []struct {
188 name string
189 header string
190 }{
191 {
192 name: "wrong version length",
193 header: "0000-00000000000000000000000000000000-0000000000000000-01",
194 },
195 {
196 name: "wrong trace ID length",
197 header: "00-ab00000000000000000000000000000000-cd00000000000000-01",
198 },
199 {
200 name: "wrong span ID length",
201 header: "00-ab000000000000000000000000000000-cd0000000000000000-01",
202 },
203 {
204 name: "wrong trace flag length",
205 header: "00-ab000000000000000000000000000000-cd00000000000000-0100",
206 },
207 {
208 name: "bogus version",
209 header: "qw-00000000000000000000000000000000-0000000000000000-01",
210 },
211 {
212 name: "bogus trace ID",
213 header: "00-qw000000000000000000000000000000-cd00000000000000-01",
214 },
215 {
216 name: "bogus span ID",
217 header: "00-ab000000000000000000000000000000-qw00000000000000-01",
218 },
219 {
220 name: "bogus trace flag",
221 header: "00-ab000000000000000000000000000000-cd00000000000000-qw",
222 },
223 {
224 name: "upper case version",
225 header: "A0-00000000000000000000000000000000-0000000000000000-01",
226 },
227 {
228 name: "upper case trace ID",
229 header: "00-AB000000000000000000000000000000-cd00000000000000-01",
230 },
231 {
232 name: "upper case span ID",
233 header: "00-ab000000000000000000000000000000-CD00000000000000-01",
234 },
235 {
236 name: "upper case trace flag",
237 header: "00-ab000000000000000000000000000000-cd00000000000000-A1",
238 },
239 {
240 name: "zero trace ID and span ID",
241 header: "00-00000000000000000000000000000000-0000000000000000-01",
242 },
243 {
244 name: "trace-flag unused bits set",
245 header: "00-ab000000000000000000000000000000-cd00000000000000-09",
246 },
247 {
248 name: "missing options",
249 header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7",
250 },
251 {
252 name: "empty options",
253 header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-",
254 },
255 }
256
257 empty := trace.SpanContext{}
258 for _, tt := range tests {
259 t.Run(tt.name, func(t *testing.T) {
260 h := http.Header{traceparent: []string{tt.header}}
261 ctx := context.Background()
262 ctx = prop.Extract(ctx, propagation.HeaderCarrier(h))
263
264
265
266
267 assert.Equal(t, empty, trace.SpanContextFromContext(ctx))
268 })
269 }
270 }
271
272 func TestInjectValidTraceContext(t *testing.T) {
273 stateStr := "key1=value1,key2=value2"
274 state, err := trace.ParseTraceState(stateStr)
275 require.NoError(t, err)
276
277 tests := []testcase{
278 {
279 name: "not sampled",
280 header: http.Header{
281 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"},
282 },
283 sc: trace.NewSpanContext(trace.SpanContextConfig{
284 TraceID: traceID,
285 SpanID: spanID,
286 Remote: true,
287 }),
288 },
289 {
290 name: "sampled",
291 header: http.Header{
292 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
293 },
294 sc: trace.NewSpanContext(trace.SpanContextConfig{
295 TraceID: traceID,
296 SpanID: spanID,
297 TraceFlags: trace.FlagsSampled,
298 Remote: true,
299 }),
300 },
301 {
302 name: "unsupported trace flag bits dropped",
303 header: http.Header{
304 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"},
305 },
306 sc: trace.NewSpanContext(trace.SpanContextConfig{
307 TraceID: traceID,
308 SpanID: spanID,
309 TraceFlags: 0xff,
310 Remote: true,
311 }),
312 },
313 {
314 name: "with tracestate",
315 header: http.Header{
316 traceparent: []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00"},
317 tracestate: []string{stateStr},
318 },
319 sc: trace.NewSpanContext(trace.SpanContextConfig{
320 TraceID: traceID,
321 SpanID: spanID,
322 TraceState: state,
323 Remote: true,
324 }),
325 },
326 }
327
328 for _, tc := range tests {
329 t.Run(tc.name, func(t *testing.T) {
330 ctx := context.Background()
331 ctx = trace.ContextWithRemoteSpanContext(ctx, tc.sc)
332
333 h := http.Header{}
334 prop.Inject(ctx, propagation.HeaderCarrier(h))
335 assert.Equal(t, tc.header, h)
336 })
337 }
338 }
339
340 func TestInvalidSpanContextDropped(t *testing.T) {
341 invalidSC := trace.SpanContext{}
342 require.False(t, invalidSC.IsValid())
343 ctx := trace.ContextWithRemoteSpanContext(context.Background(), invalidSC)
344
345 header := http.Header{}
346 propagation.TraceContext{}.Inject(ctx, propagation.HeaderCarrier(header))
347 assert.Equal(t, "", header.Get("traceparent"), "injected invalid SpanContext")
348 }
349
350 func TestTraceContextFields(t *testing.T) {
351 expected := []string{"traceparent", "tracestate"}
352 assert.Equal(t, expected, propagation.TraceContext{}.Fields())
353 }
354
View as plain text