1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package trace
16
17 import (
18 "bytes"
19 "context"
20 "testing"
21
22 "github.com/google/go-cmp/cmp"
23 "github.com/stretchr/testify/assert"
24
25 "go.opentelemetry.io/otel/attribute"
26 )
27
28 func TestSpanContextIsValid(t *testing.T) {
29 for _, testcase := range []struct {
30 name string
31 tid TraceID
32 sid SpanID
33 want bool
34 }{
35 {
36 name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
37 tid: [16]byte{1},
38 sid: [8]byte{42},
39 want: true,
40 }, {
41 name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
42 tid: TraceID([16]byte{}),
43 sid: [8]byte{},
44 want: false,
45 }, {
46 name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
47 tid: TraceID([16]byte{}),
48 sid: [8]byte{42},
49 want: false,
50 }, {
51 name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
52 tid: TraceID([16]byte{1}),
53 sid: [8]byte{},
54 want: false,
55 },
56 } {
57 t.Run(testcase.name, func(t *testing.T) {
58 sc := SpanContext{
59 traceID: testcase.tid,
60 spanID: testcase.sid,
61 }
62 have := sc.IsValid()
63 if have != testcase.want {
64 t.Errorf("Want: %v, but have: %v", testcase.want, have)
65 }
66 })
67 }
68 }
69
70 func TestSpanContextEqual(t *testing.T) {
71 a := SpanContext{
72 traceID: [16]byte{1},
73 spanID: [8]byte{42},
74 }
75
76 b := SpanContext{
77 traceID: [16]byte{1},
78 spanID: [8]byte{42},
79 }
80
81 c := SpanContext{
82 traceID: [16]byte{2},
83 spanID: [8]byte{42},
84 }
85
86 if !a.Equal(b) {
87 t.Error("Want: true, but have: false")
88 }
89
90 if a.Equal(c) {
91 t.Error("Want: false, but have: true")
92 }
93 }
94
95 func TestSpanContextIsSampled(t *testing.T) {
96 for _, testcase := range []struct {
97 name string
98 tf TraceFlags
99 want bool
100 }{
101 {
102 name: "SpanContext.IsSampled() returns false if sc is not sampled",
103 want: false,
104 }, {
105 name: "SpanContext.IsSampled() returns true if sc is sampled",
106 tf: FlagsSampled,
107 want: true,
108 },
109 } {
110 t.Run(testcase.name, func(t *testing.T) {
111 sc := SpanContext{
112 traceFlags: testcase.tf,
113 }
114
115 have := sc.IsSampled()
116
117 if have != testcase.want {
118 t.Errorf("Want: %v, but have: %v", testcase.want, have)
119 }
120 })
121 }
122 }
123
124 func TestSpanContextIsRemote(t *testing.T) {
125 for _, testcase := range []struct {
126 name string
127 remote bool
128 want bool
129 }{
130 {
131 name: "SpanContext.IsRemote() returns false if sc is not remote",
132 want: false,
133 }, {
134 name: "SpanContext.IsRemote() returns true if sc is remote",
135 remote: true,
136 want: true,
137 },
138 } {
139 t.Run(testcase.name, func(t *testing.T) {
140 sc := SpanContext{
141 remote: testcase.remote,
142 }
143
144 have := sc.IsRemote()
145
146 if have != testcase.want {
147 t.Errorf("Want: %v, but have: %v", testcase.want, have)
148 }
149 })
150 }
151 }
152
153 func TestSpanContextMarshalJSON(t *testing.T) {
154 for _, testcase := range []struct {
155 name string
156 tid TraceID
157 sid SpanID
158 tstate TraceState
159 tflags TraceFlags
160 isRemote bool
161 want []byte
162 }{
163 {
164 name: "SpanContext.MarshalJSON() returns json with partial data",
165 tid: [16]byte{1},
166 sid: [8]byte{42},
167 want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"00","TraceState":"","Remote":false}`),
168 },
169 {
170 name: "SpanContext.MarshalJSON() returns json with full data",
171 tid: [16]byte{1},
172 sid: [8]byte{42},
173 tflags: FlagsSampled,
174 isRemote: true,
175 tstate: TraceState{list: []member{
176 {Key: "foo", Value: "1"},
177 }},
178 want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"01","TraceState":"foo=1","Remote":true}`),
179 },
180 } {
181 t.Run(testcase.name, func(t *testing.T) {
182 sc := SpanContext{
183 traceID: testcase.tid,
184 spanID: testcase.sid,
185 traceFlags: testcase.tflags,
186 traceState: testcase.tstate,
187 remote: testcase.isRemote,
188 }
189 have, err := sc.MarshalJSON()
190 if err != nil {
191 t.Errorf("Marshaling failed: %v", err)
192 }
193
194 if !bytes.Equal(have, testcase.want) {
195 t.Errorf("Want: %v, but have: %v", string(testcase.want), string(have))
196 }
197 })
198 }
199 }
200
201 func TestSpanIDFromHex(t *testing.T) {
202 for _, testcase := range []struct {
203 name string
204 hex string
205 sid SpanID
206 valid bool
207 }{
208 {
209 name: "Valid SpanID",
210 sid: SpanID([8]byte{42}),
211 hex: "2a00000000000000",
212 valid: true,
213 }, {
214 name: "Invalid SpanID with invalid length",
215 hex: "80f198ee56343ba",
216 valid: false,
217 }, {
218 name: "Invalid SpanID with invalid char",
219 hex: "80f198ee563433g7",
220 valid: false,
221 }, {
222 name: "Invalid SpanID with uppercase",
223 hex: "80f198ee53ba86F7",
224 valid: false,
225 }, {
226 name: "Invalid SpanID with zero value",
227 hex: "0000000000000000",
228 valid: false,
229 },
230 } {
231 t.Run(testcase.name, func(t *testing.T) {
232 sid, err := SpanIDFromHex(testcase.hex)
233
234 if testcase.valid && err != nil {
235 t.Errorf("Expected SpanID %s to be valid but end with error %s", testcase.hex, err.Error())
236 } else if !testcase.valid && err == nil {
237 t.Errorf("Expected SpanID %s to be invalid but end no error", testcase.hex)
238 }
239
240 if sid != testcase.sid {
241 t.Errorf("Want: %v, but have: %v", testcase.sid, sid)
242 }
243 })
244 }
245 }
246
247 func TestIsValidFromHex(t *testing.T) {
248 for _, testcase := range []struct {
249 name string
250 hex string
251 tid TraceID
252 valid bool
253 }{
254 {
255 name: "Valid TraceID",
256 tid: TraceID([16]byte{128, 241, 152, 238, 86, 52, 59, 168, 100, 254, 139, 42, 87, 211, 239, 247}),
257 hex: "80f198ee56343ba864fe8b2a57d3eff7",
258 valid: true,
259 }, {
260 name: "Invalid TraceID with invalid length",
261 hex: "80f198ee56343ba864fe8b2a57d3eff",
262 valid: false,
263 }, {
264 name: "Invalid TraceID with invalid char",
265 hex: "80f198ee56343ba864fe8b2a57d3efg7",
266 valid: false,
267 }, {
268 name: "Invalid TraceID with uppercase",
269 hex: "80f198ee56343ba864fe8b2a57d3efF7",
270 valid: false,
271 }, {
272 name: "Invalid TraceID with zero value",
273 hex: "00000000000000000000000000000000",
274 valid: false,
275 },
276 } {
277 t.Run(testcase.name, func(t *testing.T) {
278 tid, err := TraceIDFromHex(testcase.hex)
279
280 if testcase.valid && err != nil {
281 t.Errorf("Expected TraceID %s to be valid but end with error %s", testcase.hex, err.Error())
282 }
283
284 if !testcase.valid && err == nil {
285 t.Errorf("Expected TraceID %s to be invalid but end no error", testcase.hex)
286 }
287
288 if tid != testcase.tid {
289 t.Errorf("Want: %v, but have: %v", testcase.tid, tid)
290 }
291 })
292 }
293 }
294
295 func TestSpanContextHasTraceID(t *testing.T) {
296 for _, testcase := range []struct {
297 name string
298 tid TraceID
299 want bool
300 }{
301 {
302 name: "SpanContext.HasTraceID() returns true if both Low and High are nonzero",
303 tid: TraceID([16]byte{1}),
304 want: true,
305 }, {
306 name: "SpanContext.HasTraceID() returns false if neither Low nor High are nonzero",
307 tid: TraceID{},
308 want: false,
309 },
310 } {
311 t.Run(testcase.name, func(t *testing.T) {
312
313 sc := SpanContext{traceID: testcase.tid}
314 have := sc.HasTraceID()
315 if have != testcase.want {
316 t.Errorf("Want: %v, but have: %v", testcase.want, have)
317 }
318 })
319 }
320 }
321
322 func TestSpanContextHasSpanID(t *testing.T) {
323 for _, testcase := range []struct {
324 name string
325 sc SpanContext
326 want bool
327 }{
328 {
329 name: "SpanContext.HasSpanID() returns true if self.SpanID != 0",
330 sc: SpanContext{spanID: [8]byte{42}},
331 want: true,
332 }, {
333 name: "SpanContext.HasSpanID() returns false if self.SpanID == 0",
334 sc: SpanContext{},
335 want: false,
336 },
337 } {
338 t.Run(testcase.name, func(t *testing.T) {
339
340 have := testcase.sc.HasSpanID()
341 if have != testcase.want {
342 t.Errorf("Want: %v, but have: %v", testcase.want, have)
343 }
344 })
345 }
346 }
347
348 func TestTraceFlagsIsSampled(t *testing.T) {
349 for _, testcase := range []struct {
350 name string
351 tf TraceFlags
352 want bool
353 }{
354 {
355 name: "sampled",
356 tf: FlagsSampled,
357 want: true,
358 }, {
359 name: "unused bits are ignored, still not sampled",
360 tf: ^FlagsSampled,
361 want: false,
362 }, {
363 name: "unused bits are ignored, still sampled",
364 tf: FlagsSampled | ^FlagsSampled,
365 want: true,
366 }, {
367 name: "not sampled/default",
368 want: false,
369 },
370 } {
371 t.Run(testcase.name, func(t *testing.T) {
372 have := testcase.tf.IsSampled()
373 if have != testcase.want {
374 t.Errorf("Want: %v, but have: %v", testcase.want, have)
375 }
376 })
377 }
378 }
379
380 func TestTraceFlagsWithSampled(t *testing.T) {
381 for _, testcase := range []struct {
382 name string
383 start TraceFlags
384 sample bool
385 want TraceFlags
386 }{
387 {
388 name: "sampled unchanged",
389 start: FlagsSampled,
390 want: FlagsSampled,
391 sample: true,
392 }, {
393 name: "become sampled",
394 want: FlagsSampled,
395 sample: true,
396 }, {
397 name: "unused bits are ignored, still not sampled",
398 start: ^FlagsSampled,
399 want: ^FlagsSampled,
400 sample: false,
401 }, {
402 name: "unused bits are ignored, becomes sampled",
403 start: ^FlagsSampled,
404 want: FlagsSampled | ^FlagsSampled,
405 sample: true,
406 }, {
407 name: "not sampled/default",
408 sample: false,
409 },
410 } {
411 t.Run(testcase.name, func(t *testing.T) {
412 have := testcase.start.WithSampled(testcase.sample)
413 if have != testcase.want {
414 t.Errorf("Want: %v, but have: %v", testcase.want, have)
415 }
416 })
417 }
418 }
419
420 func TestStringTraceID(t *testing.T) {
421 for _, testcase := range []struct {
422 name string
423 tid TraceID
424 want string
425 }{
426 {
427 name: "TraceID.String returns string representation of self.TraceID values > 0",
428 tid: TraceID([16]byte{255}),
429 want: "ff000000000000000000000000000000",
430 },
431 {
432 name: "TraceID.String returns string representation of self.TraceID values == 0",
433 tid: TraceID([16]byte{}),
434 want: "00000000000000000000000000000000",
435 },
436 } {
437 t.Run(testcase.name, func(t *testing.T) {
438
439 have := testcase.tid.String()
440 if have != testcase.want {
441 t.Errorf("Want: %s, but have: %s", testcase.want, have)
442 }
443 })
444 }
445 }
446
447 func TestStringSpanID(t *testing.T) {
448 for _, testcase := range []struct {
449 name string
450 sid SpanID
451 want string
452 }{
453 {
454 name: "SpanID.String returns string representation of self.SpanID values > 0",
455 sid: SpanID([8]byte{255}),
456 want: "ff00000000000000",
457 },
458 {
459 name: "SpanID.String returns string representation of self.SpanID values == 0",
460 sid: SpanID([8]byte{}),
461 want: "0000000000000000",
462 },
463 } {
464 t.Run(testcase.name, func(t *testing.T) {
465
466 have := testcase.sid.String()
467 if have != testcase.want {
468 t.Errorf("Want: %s, but have: %s", testcase.want, have)
469 }
470 })
471 }
472 }
473
474 func TestValidateSpanKind(t *testing.T) {
475 tests := []struct {
476 in SpanKind
477 want SpanKind
478 }{
479 {
480 SpanKindUnspecified,
481 SpanKindInternal,
482 },
483 {
484 SpanKindInternal,
485 SpanKindInternal,
486 },
487 {
488 SpanKindServer,
489 SpanKindServer,
490 },
491 {
492 SpanKindClient,
493 SpanKindClient,
494 },
495 {
496 SpanKindProducer,
497 SpanKindProducer,
498 },
499 {
500 SpanKindConsumer,
501 SpanKindConsumer,
502 },
503 }
504 for _, test := range tests {
505 if got := ValidateSpanKind(test.in); got != test.want {
506 t.Errorf("ValidateSpanKind(%#v) = %#v, want %#v", test.in, got, test.want)
507 }
508 }
509 }
510
511 func TestSpanKindString(t *testing.T) {
512 tests := []struct {
513 in SpanKind
514 want string
515 }{
516 {
517 SpanKindUnspecified,
518 "unspecified",
519 },
520 {
521 SpanKindInternal,
522 "internal",
523 },
524 {
525 SpanKindServer,
526 "server",
527 },
528 {
529 SpanKindClient,
530 "client",
531 },
532 {
533 SpanKindProducer,
534 "producer",
535 },
536 {
537 SpanKindConsumer,
538 "consumer",
539 },
540 }
541 for _, test := range tests {
542 if got := test.in.String(); got != test.want {
543 t.Errorf("%#v.String() = %#v, want %#v", test.in, got, test.want)
544 }
545 }
546 }
547
548 func assertSpanContextEqual(got SpanContext, want SpanContext) bool {
549 return got.spanID == want.spanID &&
550 got.traceID == want.traceID &&
551 got.traceFlags == want.traceFlags &&
552 got.remote == want.remote &&
553 got.traceState.String() == want.traceState.String()
554 }
555
556 func TestNewSpanContext(t *testing.T) {
557 testCases := []struct {
558 name string
559 config SpanContextConfig
560 expectedSpanContext SpanContext
561 }{
562 {
563 name: "Complete SpanContext",
564 config: SpanContextConfig{
565 TraceID: TraceID([16]byte{1}),
566 SpanID: SpanID([8]byte{42}),
567 TraceFlags: 0x1,
568 TraceState: TraceState{list: []member{
569 {"foo", "bar"},
570 }},
571 },
572 expectedSpanContext: SpanContext{
573 traceID: TraceID([16]byte{1}),
574 spanID: SpanID([8]byte{42}),
575 traceFlags: 0x1,
576 traceState: TraceState{list: []member{
577 {"foo", "bar"},
578 }},
579 },
580 },
581 {
582 name: "Empty SpanContext",
583 config: SpanContextConfig{},
584 expectedSpanContext: SpanContext{},
585 },
586 {
587 name: "Partial SpanContext",
588 config: SpanContextConfig{
589 TraceID: TraceID([16]byte{1}),
590 SpanID: SpanID([8]byte{42}),
591 },
592 expectedSpanContext: SpanContext{
593 traceID: TraceID([16]byte{1}),
594 spanID: SpanID([8]byte{42}),
595 traceFlags: 0x0,
596 traceState: TraceState{},
597 },
598 },
599 }
600
601 for _, tc := range testCases {
602 t.Run(tc.name, func(t *testing.T) {
603 sctx := NewSpanContext(tc.config)
604 if !assertSpanContextEqual(sctx, tc.expectedSpanContext) {
605 t.Fatalf("%s: Unexpected context created: %s", tc.name, cmp.Diff(sctx, tc.expectedSpanContext))
606 }
607 })
608 }
609 }
610
611 func TestSpanContextDerivation(t *testing.T) {
612 from := SpanContext{}
613 to := SpanContext{traceID: TraceID([16]byte{1})}
614
615 modified := from.WithTraceID(to.TraceID())
616 if !assertSpanContextEqual(modified, to) {
617 t.Fatalf("WithTraceID: Unexpected context created: %s", cmp.Diff(modified, to))
618 }
619
620 from = to
621 to.spanID = SpanID([8]byte{42})
622
623 modified = from.WithSpanID(to.SpanID())
624 if !assertSpanContextEqual(modified, to) {
625 t.Fatalf("WithSpanID: Unexpected context created: %s", cmp.Diff(modified, to))
626 }
627
628 from = to
629 to.traceFlags = 0x13
630
631 modified = from.WithTraceFlags(to.TraceFlags())
632 if !assertSpanContextEqual(modified, to) {
633 t.Fatalf("WithTraceFlags: Unexpected context created: %s", cmp.Diff(modified, to))
634 }
635
636 from = to
637 to.traceState = TraceState{list: []member{{"foo", "bar"}}}
638
639 modified = from.WithTraceState(to.TraceState())
640 if !assertSpanContextEqual(modified, to) {
641 t.Fatalf("WithTraceState: Unexpected context created: %s", cmp.Diff(modified, to))
642 }
643 }
644
645 func TestLinkFromContext(t *testing.T) {
646 k1v1 := attribute.String("key1", "value1")
647 spanCtx := SpanContext{traceID: TraceID([16]byte{1}), remote: true}
648
649 receiverCtx := ContextWithRemoteSpanContext(context.Background(), spanCtx)
650 link := LinkFromContext(receiverCtx, k1v1)
651
652 if !assertSpanContextEqual(link.SpanContext, spanCtx) {
653 t.Fatalf("LinkFromContext: Unexpected context created: %s", cmp.Diff(link.SpanContext, spanCtx))
654 }
655 assert.Equal(t, link.Attributes[0], k1v1)
656 }
657
View as plain text