1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package trace
16
17 import (
18 "context"
19 "errors"
20 "fmt"
21 "math"
22 "strconv"
23 "strings"
24 "sync"
25 "sync/atomic"
26 "testing"
27 "time"
28
29 "github.com/google/go-cmp/cmp"
30 "github.com/stretchr/testify/assert"
31 "github.com/stretchr/testify/require"
32
33 "go.opentelemetry.io/otel"
34 "go.opentelemetry.io/otel/attribute"
35 "go.opentelemetry.io/otel/codes"
36 "go.opentelemetry.io/otel/sdk/instrumentation"
37 ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
38 "go.opentelemetry.io/otel/sdk/resource"
39 semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
40 "go.opentelemetry.io/otel/trace"
41 )
42
43 const envVar = "OTEL_RESOURCE_ATTRIBUTES"
44
45 type storingHandler struct {
46 errs []error
47 }
48
49 func (s *storingHandler) Handle(err error) {
50 s.errs = append(s.errs, err)
51 }
52
53 func (s *storingHandler) Reset() {
54 s.errs = nil
55 }
56
57 var (
58 tid trace.TraceID
59 sid trace.SpanID
60 sc trace.SpanContext
61
62 handler = &storingHandler{}
63 )
64
65 func init() {
66 tid, _ = trace.TraceIDFromHex("01020304050607080102040810203040")
67 sid, _ = trace.SpanIDFromHex("0102040810203040")
68 sc = trace.NewSpanContext(trace.SpanContextConfig{
69 TraceID: tid,
70 SpanID: sid,
71 TraceFlags: 0x1,
72 })
73
74 otel.SetErrorHandler(handler)
75 }
76
77 func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) {
78 harness := ottest.NewHarness(t)
79
80 harness.TestTracerProvider(func() trace.TracerProvider {
81 return NewTracerProvider(WithSampler(TraceIDRatioBased(0)))
82 })
83
84 tp := NewTracerProvider(WithSampler(TraceIDRatioBased(0)))
85 harness.TestTracer(func() trace.Tracer {
86 return tp.Tracer("")
87 })
88 }
89
90 type testExporter struct {
91 mu sync.RWMutex
92 idx map[string]int
93 spans []*snapshot
94 }
95
96 func NewTestExporter() *testExporter {
97 return &testExporter{idx: make(map[string]int)}
98 }
99
100 func (te *testExporter) ExportSpans(_ context.Context, spans []ReadOnlySpan) error {
101 te.mu.Lock()
102 defer te.mu.Unlock()
103
104 i := len(te.spans)
105 for _, s := range spans {
106 te.idx[s.Name()] = i
107 te.spans = append(te.spans, s.(*snapshot))
108 i++
109 }
110 return nil
111 }
112
113 func (te *testExporter) Spans() []*snapshot {
114 te.mu.RLock()
115 defer te.mu.RUnlock()
116
117 cp := make([]*snapshot, len(te.spans))
118 copy(cp, te.spans)
119 return cp
120 }
121
122 func (te *testExporter) GetSpan(name string) (*snapshot, bool) {
123 te.mu.RLock()
124 defer te.mu.RUnlock()
125 i, ok := te.idx[name]
126 if !ok {
127 return nil, false
128 }
129 return te.spans[i], true
130 }
131
132 func (te *testExporter) Len() int {
133 te.mu.RLock()
134 defer te.mu.RUnlock()
135 return len(te.spans)
136 }
137
138 func (te *testExporter) Shutdown(context.Context) error {
139 te.Reset()
140 return nil
141 }
142
143 func (te *testExporter) Reset() {
144 te.mu.Lock()
145 defer te.mu.Unlock()
146 te.idx = make(map[string]int)
147 te.spans = te.spans[:0]
148 }
149
150 type testSampler struct {
151 callCount int
152 prefix string
153 t *testing.T
154 }
155
156 func (ts *testSampler) ShouldSample(p SamplingParameters) SamplingResult {
157 ts.callCount++
158 ts.t.Logf("called sampler for name %q", p.Name)
159 decision := Drop
160 if strings.HasPrefix(p.Name, ts.prefix) {
161 decision = RecordAndSample
162 }
163 return SamplingResult{Decision: decision, Attributes: []attribute.KeyValue{attribute.Int("callCount", ts.callCount)}}
164 }
165
166 func (ts testSampler) Description() string {
167 return "testSampler"
168 }
169
170 func TestSetName(t *testing.T) {
171 tp := NewTracerProvider()
172
173 type testCase struct {
174 name string
175 newName string
176 }
177 for idx, tt := range []testCase{
178 {
179 name: "foobar",
180 newName: "foobaz",
181 },
182 {
183 name: "foobar",
184 newName: "barbaz",
185 },
186 {
187 name: "barbar",
188 newName: "barbaz",
189 },
190 {
191 name: "barbar",
192 newName: "foobar",
193 },
194 } {
195 sp := startNamedSpan(tp, "SetName", tt.name)
196 if sdkspan, ok := sp.(*recordingSpan); ok {
197 if sdkspan.Name() != tt.name {
198 t.Errorf("%d: invalid name at span creation, expected %v, got %v", idx, tt.name, sdkspan.Name())
199 }
200 } else {
201 t.Errorf("%d: unable to coerce span to SDK span, is type %T", idx, sp)
202 }
203 sp.SetName(tt.newName)
204 if sdkspan, ok := sp.(*recordingSpan); ok {
205 if sdkspan.Name() != tt.newName {
206 t.Errorf("%d: span name not changed, expected %v, got %v", idx, tt.newName, sdkspan.Name())
207 }
208 } else {
209 t.Errorf("%d: unable to coerce span to SDK span, is type %T", idx, sp)
210 }
211 sp.End()
212 }
213 }
214
215 func TestSpanIsRecording(t *testing.T) {
216 t.Run("while Span active", func(t *testing.T) {
217 for name, tc := range map[string]struct {
218 sampler Sampler
219 want bool
220 }{
221 "Always sample, recording on": {sampler: AlwaysSample(), want: true},
222 "Never sample recording off": {sampler: NeverSample(), want: false},
223 } {
224 tp := NewTracerProvider(WithSampler(tc.sampler))
225 _, span := tp.Tracer(name).Start(context.Background(), "StartSpan")
226 got := span.IsRecording()
227 span.End()
228 assert.Equal(t, got, tc.want, name)
229 }
230 })
231
232 t.Run("after Span end", func(t *testing.T) {
233 for name, tc := range map[string]Sampler{
234 "Always Sample": AlwaysSample(),
235 "Never Sample": NeverSample(),
236 } {
237 tp := NewTracerProvider(WithSampler(tc))
238 _, span := tp.Tracer(name).Start(context.Background(), "StartSpan")
239 span.End()
240 got := span.IsRecording()
241 assert.False(t, got, name)
242 }
243 })
244 }
245
246 func TestSampling(t *testing.T) {
247 idg := defaultIDGenerator()
248 const total = 10000
249 for name, tc := range map[string]struct {
250 sampler Sampler
251 expect float64
252 parent bool
253 sampledParent bool
254 }{
255
256 "NeverSample": {sampler: NeverSample(), expect: 0},
257 "AlwaysSample": {sampler: AlwaysSample(), expect: 1.0},
258 "TraceIdRatioBased_-1": {sampler: TraceIDRatioBased(-1.0), expect: 0},
259 "TraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25},
260 "TraceIdRatioBased_.50": {sampler: TraceIDRatioBased(0.50), expect: .5},
261 "TraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75},
262 "TraceIdRatioBased_2.0": {sampler: TraceIDRatioBased(2.0), expect: 1},
263
264
265 "ParentNeverSample": {sampler: ParentBased(NeverSample()), expect: 0},
266 "ParentAlwaysSample": {sampler: ParentBased(AlwaysSample()), expect: 1},
267 "ParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: .5},
268
269
270 "UnsampledParentSpanWithTraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25, parent: true},
271 "SampledParentSpanWithTraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25, parent: true, sampledParent: true},
272 "UnsampledParentSpanWithTraceIdRatioBased_.50": {sampler: TraceIDRatioBased(0.50), expect: .5, parent: true},
273 "SampledParentSpanWithTraceIdRatioBased_.50": {sampler: TraceIDRatioBased(0.50), expect: .5, parent: true, sampledParent: true},
274 "UnsampledParentSpanWithTraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75, parent: true},
275 "SampledParentSpanWithTraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75, parent: true, sampledParent: true},
276
277
278 "SampledParentSpanWithNeverSample": {sampler: NeverSample(), expect: 0, parent: true, sampledParent: true},
279
280
281 "SampledParentSpanWithParentNeverSample": {sampler: ParentBased(NeverSample()), expect: 1, parent: true, sampledParent: true},
282 "UnsampledParentSpanWithParentNeverSampler": {sampler: ParentBased(NeverSample()), expect: 0, parent: true, sampledParent: false},
283 "SampledParentSpanWithParentAlwaysSampler": {sampler: ParentBased(AlwaysSample()), expect: 1, parent: true, sampledParent: true},
284 "UnsampledParentSpanWithParentAlwaysSampler": {sampler: ParentBased(AlwaysSample()), expect: 0, parent: true, sampledParent: false},
285 "SampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: 1, parent: true, sampledParent: true},
286 "UnsampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: 0, parent: true, sampledParent: false},
287 } {
288 tc := tc
289 t.Run(name, func(t *testing.T) {
290 t.Parallel()
291 p := NewTracerProvider(WithSampler(tc.sampler))
292 tr := p.Tracer("test")
293 var sampled int
294 for i := 0; i < total; i++ {
295 ctx := context.Background()
296 if tc.parent {
297 tid, sid := idg.NewIDs(ctx)
298 psc := trace.NewSpanContext(trace.SpanContextConfig{
299 TraceID: tid,
300 SpanID: sid,
301 })
302 if tc.sampledParent {
303 psc = psc.WithTraceFlags(trace.FlagsSampled)
304 }
305 ctx = trace.ContextWithRemoteSpanContext(ctx, psc)
306 }
307 _, span := tr.Start(ctx, "test")
308 if span.SpanContext().IsSampled() {
309 sampled++
310 }
311 }
312 tolerance := 0.0
313 got := float64(sampled) / float64(total)
314
315 if tc.expect > 0 && tc.expect < 1 {
316
317 const z = 4.75342
318 tolerance = z * math.Sqrt(got*(1-got)/total)
319 }
320
321 diff := math.Abs(got - tc.expect)
322 if diff > tolerance {
323 t.Errorf("got %f (diff: %f), expected %f (w/tolerance: %f)", got, diff, tc.expect, tolerance)
324 }
325 })
326 }
327 }
328
329 func TestStartSpanWithParent(t *testing.T) {
330 tp := NewTracerProvider()
331 tr := tp.Tracer("SpanWithParent")
332 ctx := context.Background()
333
334 _, s1 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "span1-unsampled-parent1")
335 if err := checkChild(t, sc, s1); err != nil {
336 t.Error(err)
337 }
338
339 _, s2 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "span2-unsampled-parent1")
340 if err := checkChild(t, sc, s2); err != nil {
341 t.Error(err)
342 }
343
344 ts, err := trace.ParseTraceState("k=v")
345 if err != nil {
346 t.Error(err)
347 }
348 sc2 := sc.WithTraceState(ts)
349 _, s3 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc2), "span3-sampled-parent2")
350 if err := checkChild(t, sc2, s3); err != nil {
351 t.Error(err)
352 }
353
354 ctx2, s4 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc2), "span4-sampled-parent2")
355 if err := checkChild(t, sc2, s4); err != nil {
356 t.Error(err)
357 }
358
359 s4Sc := s4.SpanContext()
360 _, s5 := tr.Start(ctx2, "span5-implicit-childof-span4")
361 if err := checkChild(t, s4Sc, s5); err != nil {
362 t.Error(err)
363 }
364 }
365
366
367
368 func TestStartSpanWithNilContext(t *testing.T) {
369 tp := NewTracerProvider()
370 tr := tp.Tracer("NoPanic")
371
372
373 assert.NotPanics(t, func() { tr.Start(nil, "should-not-panic") })
374 }
375
376 func TestStartSpanNewRootNotSampled(t *testing.T) {
377 alwaysSampleTp := NewTracerProvider()
378 sampledTr := alwaysSampleTp.Tracer("AlwaysSampled")
379 neverSampleTp := NewTracerProvider(WithSampler(ParentBased(NeverSample())))
380 neverSampledTr := neverSampleTp.Tracer("ParentBasedNeverSample")
381 ctx := context.Background()
382
383 ctx, s1 := sampledTr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "span1-sampled")
384 if err := checkChild(t, sc, s1); err != nil {
385 t.Error(err)
386 }
387
388 _, s2 := neverSampledTr.Start(ctx, "span2-no-newroot")
389 if !s2.SpanContext().IsSampled() {
390 t.Error(fmt.Errorf("got child span is not sampled, want child span with sampler: ParentBased(NeverSample()) to be sampled"))
391 }
392
393
394 _, s3 := neverSampledTr.Start(ctx, "span3-newroot", trace.WithNewRoot())
395 if s3.SpanContext().IsSampled() {
396 t.Error(fmt.Errorf("got child span is sampled, want child span WithNewRoot() and with sampler: ParentBased(NeverSample()) to not be sampled"))
397 }
398 }
399
400 func TestSetSpanAttributesOnStart(t *testing.T) {
401 te := NewTestExporter()
402 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
403 span := startSpan(tp,
404 "StartSpanAttribute",
405 trace.WithAttributes(attribute.String("key1", "value1")),
406 trace.WithAttributes(attribute.String("key2", "value2")),
407 )
408 got, err := endSpan(te, span)
409 if err != nil {
410 t.Fatal(err)
411 }
412
413 want := &snapshot{
414 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
415 TraceID: tid,
416 TraceFlags: 0x1,
417 }),
418 parent: sc.WithRemote(true),
419 name: "span0",
420 attributes: []attribute.KeyValue{
421 attribute.String("key1", "value1"),
422 attribute.String("key2", "value2"),
423 },
424 spanKind: trace.SpanKindInternal,
425 instrumentationScope: instrumentation.Scope{Name: "StartSpanAttribute"},
426 }
427 if diff := cmpDiff(got, want); diff != "" {
428 t.Errorf("SetSpanAttributesOnStart: -got +want %s", diff)
429 }
430 }
431
432 func TestSamplerAttributesLocalChildSpan(t *testing.T) {
433 sampler := &testSampler{prefix: "span", t: t}
434 te := NewTestExporter()
435 tp := NewTracerProvider(WithSampler(sampler), WithSyncer(te), WithResource(resource.Empty()))
436
437 ctx := context.Background()
438 ctx, span := startLocalSpan(ctx, tp, "SpanOne", "span0")
439 _, spanTwo := startLocalSpan(ctx, tp, "SpanTwo", "span1")
440
441 spanTwo.End()
442 span.End()
443
444 got := te.Spans()
445 require.Len(t, got, 2)
446
447 gotSpan0, gotSpan1 := got[0], got[1]
448
449
450 assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 2)}, gotSpan0.Attributes())
451 assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 1)}, gotSpan1.Attributes())
452 }
453
454 func TestSpanSetAttributes(t *testing.T) {
455 attrs := [...]attribute.KeyValue{
456 attribute.String("key1", "value1"),
457 attribute.String("key2", "value2"),
458 attribute.String("key3", "value3"),
459 attribute.String("key4", "value4"),
460 attribute.String("key1", "value5"),
461 attribute.String("key2", "value6"),
462 attribute.String("key3", "value7"),
463 }
464 invalid := attribute.KeyValue{}
465
466 tests := []struct {
467 name string
468 input [][]attribute.KeyValue
469 wantAttrs []attribute.KeyValue
470 wantDropped int
471 }{
472 {
473 name: "array",
474 input: [][]attribute.KeyValue{attrs[:3]},
475 wantAttrs: attrs[:3],
476 },
477 {
478 name: "single_value:array",
479 input: [][]attribute.KeyValue{attrs[:1], attrs[1:3]},
480 wantAttrs: attrs[:3],
481 },
482 {
483 name: "array:single_value",
484 input: [][]attribute.KeyValue{attrs[:2], attrs[2:3]},
485 wantAttrs: attrs[:3],
486 },
487 {
488 name: "single_values",
489 input: [][]attribute.KeyValue{attrs[:1], attrs[1:2], attrs[2:3]},
490 wantAttrs: attrs[:3],
491 },
492
493
494
495
496
497
498
499
500
501 {
502 name: "drop_last_added",
503 input: [][]attribute.KeyValue{attrs[:3], attrs[3:4], attrs[3:4]},
504 wantAttrs: attrs[:3],
505 wantDropped: 2,
506 },
507
508
509
510
511
512
513
514
515 {
516 name: "single_value_update",
517 input: [][]attribute.KeyValue{attrs[:1], attrs[:3]},
518 wantAttrs: attrs[:3],
519 },
520 {
521 name: "all_update",
522 input: [][]attribute.KeyValue{attrs[:3], attrs[4:7]},
523 wantAttrs: attrs[4:7],
524 },
525 {
526 name: "all_update/multi",
527 input: [][]attribute.KeyValue{attrs[:3], attrs[4:7], attrs[:3]},
528 wantAttrs: attrs[:3],
529 },
530 {
531 name: "deduplicate/under_capacity",
532 input: [][]attribute.KeyValue{attrs[:1], attrs[:1], attrs[:1]},
533 wantAttrs: attrs[:1],
534 },
535 {
536 name: "deduplicate/over_capacity",
537 input: [][]attribute.KeyValue{attrs[:1], attrs[:1], attrs[:1], attrs[:3]},
538 wantAttrs: attrs[:3],
539 },
540 {
541 name: "deduplicate/added",
542 input: [][]attribute.KeyValue{
543 attrs[:2],
544 {attrs[2], attrs[2], attrs[2]},
545 },
546 wantAttrs: attrs[:3],
547 },
548 {
549 name: "deduplicate/added_at_cappacity",
550 input: [][]attribute.KeyValue{
551 attrs[:3],
552 {attrs[2], attrs[2], attrs[2]},
553 },
554 wantAttrs: attrs[:3],
555 },
556 {
557 name: "invalid",
558 input: [][]attribute.KeyValue{
559 {invalid},
560 },
561 wantDropped: 1,
562 },
563 {
564 name: "invalid_with_valid",
565 input: [][]attribute.KeyValue{
566 {invalid, attrs[0]},
567 },
568 wantAttrs: attrs[:1],
569 wantDropped: 1,
570 },
571 {
572 name: "invalid_over_capacity",
573 input: [][]attribute.KeyValue{
574 {invalid, invalid, invalid, invalid, attrs[0]},
575 },
576 wantAttrs: attrs[:1],
577 wantDropped: 4,
578 },
579 {
580 name: "valid:invalid/under_capacity",
581 input: [][]attribute.KeyValue{
582 attrs[:1],
583 {invalid},
584 },
585 wantAttrs: attrs[:1],
586 wantDropped: 1,
587 },
588 {
589 name: "valid:invalid/over_capacity",
590 input: [][]attribute.KeyValue{
591 attrs[:1],
592 {invalid, invalid, invalid, invalid},
593 },
594 wantAttrs: attrs[:1],
595 wantDropped: 4,
596 },
597 {
598 name: "valid_at_capacity:invalid",
599 input: [][]attribute.KeyValue{
600 attrs[:3],
601 {invalid, invalid, invalid, invalid},
602 },
603 wantAttrs: attrs[:3],
604 wantDropped: 4,
605 },
606 }
607
608 const (
609 capacity = 3
610 instName = "TestSpanAttributeCapacity"
611 spanName = "test span"
612 )
613
614 for _, test := range tests {
615 t.Run(test.name, func(t *testing.T) {
616 te := NewTestExporter()
617 sl := NewSpanLimits()
618 sl.AttributeCountLimit = capacity
619 tp := NewTracerProvider(WithSyncer(te), WithSpanLimits(sl))
620 _, span := tp.Tracer(instName).Start(context.Background(), spanName)
621 for _, a := range test.input {
622 span.SetAttributes(a...)
623 }
624 span.End()
625
626 require.Implements(t, (*ReadOnlySpan)(nil), span)
627 roSpan := span.(ReadOnlySpan)
628
629
630 assert.ElementsMatch(t, test.wantAttrs, roSpan.Attributes(), "expected attributes")
631 assert.Equal(t, test.wantDropped, roSpan.DroppedAttributes(), "dropped attributes")
632
633 snap, ok := te.GetSpan(spanName)
634 require.Truef(t, ok, "span %s not exported", spanName)
635
636
637 assert.ElementsMatch(t, test.wantAttrs, snap.Attributes(), "expected attributes")
638 assert.Equal(t, test.wantDropped, snap.DroppedAttributes(), "dropped attributes")
639 })
640 }
641 }
642
643 func TestEvents(t *testing.T) {
644 te := NewTestExporter()
645 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
646
647 span := startSpan(tp, "Events")
648 k1v1 := attribute.String("key1", "value1")
649 k2v2 := attribute.Bool("key2", true)
650 k3v3 := attribute.Int64("key3", 3)
651
652 span.AddEvent("foo", trace.WithAttributes(attribute.String("key1", "value1")))
653 span.AddEvent("bar", trace.WithAttributes(
654 attribute.Bool("key2", true),
655 attribute.Int64("key3", 3),
656 ))
657 got, err := endSpan(te, span)
658 if err != nil {
659 t.Fatal(err)
660 }
661
662 for i := range got.Events() {
663 if !checkTime(&got.Events()[i].Time) {
664 t.Error("exporting span: expected nonzero Event Time")
665 }
666 }
667
668 want := &snapshot{
669 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
670 TraceID: tid,
671 TraceFlags: 0x1,
672 }),
673 parent: sc.WithRemote(true),
674 name: "span0",
675 events: []Event{
676 {Name: "foo", Attributes: []attribute.KeyValue{k1v1}},
677 {Name: "bar", Attributes: []attribute.KeyValue{k2v2, k3v3}},
678 },
679 spanKind: trace.SpanKindInternal,
680 instrumentationScope: instrumentation.Scope{Name: "Events"},
681 }
682 if diff := cmpDiff(got, want); diff != "" {
683 t.Errorf("Message Events: -got +want %s", diff)
684 }
685 }
686
687 func TestEventsOverLimit(t *testing.T) {
688 te := NewTestExporter()
689 sl := NewSpanLimits()
690 sl.EventCountLimit = 2
691 tp := NewTracerProvider(WithSpanLimits(sl), WithSyncer(te), WithResource(resource.Empty()))
692
693 span := startSpan(tp, "EventsOverLimit")
694 k1v1 := attribute.String("key1", "value1")
695 k2v2 := attribute.Bool("key2", false)
696 k3v3 := attribute.String("key3", "value3")
697
698 span.AddEvent("fooDrop", trace.WithAttributes(attribute.String("key1", "value1")))
699 span.AddEvent("barDrop", trace.WithAttributes(
700 attribute.Bool("key2", true),
701 attribute.String("key3", "value3"),
702 ))
703 span.AddEvent("foo", trace.WithAttributes(attribute.String("key1", "value1")))
704 span.AddEvent("bar", trace.WithAttributes(
705 attribute.Bool("key2", false),
706 attribute.String("key3", "value3"),
707 ))
708 got, err := endSpan(te, span)
709 if err != nil {
710 t.Fatal(err)
711 }
712
713 for i := range got.Events() {
714 if !checkTime(&got.Events()[i].Time) {
715 t.Error("exporting span: expected nonzero Event Time")
716 }
717 }
718
719 want := &snapshot{
720 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
721 TraceID: tid,
722 TraceFlags: 0x1,
723 }),
724 parent: sc.WithRemote(true),
725 name: "span0",
726 events: []Event{
727 {Name: "foo", Attributes: []attribute.KeyValue{k1v1}},
728 {Name: "bar", Attributes: []attribute.KeyValue{k2v2, k3v3}},
729 },
730 droppedEventCount: 2,
731 spanKind: trace.SpanKindInternal,
732 instrumentationScope: instrumentation.Scope{Name: "EventsOverLimit"},
733 }
734 if diff := cmpDiff(got, want); diff != "" {
735 t.Errorf("Message Event over limit: -got +want %s", diff)
736 }
737 }
738
739 func TestLinks(t *testing.T) {
740 te := NewTestExporter()
741 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
742
743 k1v1 := attribute.String("key1", "value1")
744 k2v2 := attribute.String("key2", "value2")
745 k3v3 := attribute.String("key3", "value3")
746
747 sc1 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
748 sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
749
750 l1 := trace.Link{SpanContext: sc1, Attributes: []attribute.KeyValue{k1v1}}
751 l2 := trace.Link{SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2, k3v3}}
752
753 links := []trace.Link{l1, l2}
754 span := startSpan(tp, "Links", trace.WithLinks(links...))
755
756 got, err := endSpan(te, span)
757 if err != nil {
758 t.Fatal(err)
759 }
760
761 want := &snapshot{
762 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
763 TraceID: tid,
764 TraceFlags: 0x1,
765 }),
766 parent: sc.WithRemote(true),
767 name: "span0",
768 links: []Link{{l1.SpanContext, l1.Attributes, 0}, {l2.SpanContext, l2.Attributes, 0}},
769 spanKind: trace.SpanKindInternal,
770 instrumentationScope: instrumentation.Scope{Name: "Links"},
771 }
772 if diff := cmpDiff(got, want); diff != "" {
773 t.Errorf("Link: -got +want %s", diff)
774 }
775 sc1 = trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
776
777 span1 := startSpan(tp, "name", trace.WithLinks([]trace.Link{
778 {SpanContext: trace.SpanContext{}},
779 {SpanContext: sc1},
780 }...))
781
782 sdkspan, _ := span1.(*recordingSpan)
783 require.Len(t, sdkspan.Links(), 1)
784 }
785
786 func TestLinksOverLimit(t *testing.T) {
787 te := NewTestExporter()
788
789 sc1 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
790 sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
791 sc3 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
792
793 sl := NewSpanLimits()
794 sl.LinkCountLimit = 2
795 tp := NewTracerProvider(WithSpanLimits(sl), WithSyncer(te), WithResource(resource.Empty()))
796
797 span := startSpan(tp, "LinksOverLimit",
798 trace.WithLinks(
799 trace.Link{SpanContext: sc1, Attributes: []attribute.KeyValue{attribute.String("key1", "value1")}},
800 trace.Link{SpanContext: sc2, Attributes: []attribute.KeyValue{attribute.String("key2", "value2")}},
801 trace.Link{SpanContext: sc3, Attributes: []attribute.KeyValue{attribute.String("key3", "value3")}},
802 ),
803 )
804
805 k2v2 := attribute.String("key2", "value2")
806 k3v3 := attribute.String("key3", "value3")
807
808 got, err := endSpan(te, span)
809 if err != nil {
810 t.Fatal(err)
811 }
812
813 want := &snapshot{
814 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
815 TraceID: tid,
816 TraceFlags: 0x1,
817 }),
818 parent: sc.WithRemote(true),
819 name: "span0",
820 links: []Link{
821 {SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2}, DroppedAttributeCount: 0},
822 {SpanContext: sc3, Attributes: []attribute.KeyValue{k3v3}, DroppedAttributeCount: 0},
823 },
824 droppedLinkCount: 1,
825 spanKind: trace.SpanKindInternal,
826 instrumentationScope: instrumentation.Scope{Name: "LinksOverLimit"},
827 }
828 if diff := cmpDiff(got, want); diff != "" {
829 t.Errorf("Link over limit: -got +want %s", diff)
830 }
831 }
832
833 func TestSetSpanName(t *testing.T) {
834 te := NewTestExporter()
835 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
836 ctx := context.Background()
837
838 want := "SpanName-1"
839 ctx = trace.ContextWithRemoteSpanContext(ctx, sc)
840 _, span := tp.Tracer("SetSpanName").Start(ctx, "SpanName-1")
841 got, err := endSpan(te, span)
842 if err != nil {
843 t.Fatal(err)
844 }
845
846 if got.Name() != want {
847 t.Errorf("span.Name: got %q; want %q", got.Name(), want)
848 }
849 }
850
851 func TestSetSpanStatus(t *testing.T) {
852 te := NewTestExporter()
853 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
854
855 span := startSpan(tp, "SpanStatus")
856 span.SetStatus(codes.Error, "Error")
857 got, err := endSpan(te, span)
858 if err != nil {
859 t.Fatal(err)
860 }
861
862 want := &snapshot{
863 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
864 TraceID: tid,
865 TraceFlags: 0x1,
866 }),
867 parent: sc.WithRemote(true),
868 name: "span0",
869 spanKind: trace.SpanKindInternal,
870 status: Status{
871 Code: codes.Error,
872 Description: "Error",
873 },
874 instrumentationScope: instrumentation.Scope{Name: "SpanStatus"},
875 }
876 if diff := cmpDiff(got, want); diff != "" {
877 t.Errorf("SetSpanStatus: -got +want %s", diff)
878 }
879 }
880
881 func TestSetSpanStatusWithoutMessageWhenStatusIsNotError(t *testing.T) {
882 te := NewTestExporter()
883 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
884
885 span := startSpan(tp, "SpanStatus")
886 span.SetStatus(codes.Ok, "This message will be ignored")
887 got, err := endSpan(te, span)
888 if err != nil {
889 t.Fatal(err)
890 }
891
892 want := &snapshot{
893 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
894 TraceID: tid,
895 TraceFlags: 0x1,
896 }),
897 parent: sc.WithRemote(true),
898 name: "span0",
899 spanKind: trace.SpanKindInternal,
900 status: Status{
901 Code: codes.Ok,
902 Description: "",
903 },
904 instrumentationScope: instrumentation.Scope{Name: "SpanStatus"},
905 }
906 if diff := cmpDiff(got, want); diff != "" {
907 t.Errorf("SetSpanStatus: -got +want %s", diff)
908 }
909 }
910
911 func cmpDiff(x, y interface{}) string {
912 return cmp.Diff(x, y,
913 cmp.AllowUnexported(snapshot{}),
914 cmp.AllowUnexported(attribute.Value{}),
915 cmp.AllowUnexported(Event{}),
916 cmp.AllowUnexported(trace.TraceState{}))
917 }
918
919
920
921 func checkChild(t *testing.T, p trace.SpanContext, apiSpan trace.Span) error {
922 s := apiSpan.(*recordingSpan)
923 if s == nil {
924 return fmt.Errorf("got nil child span, want non-nil")
925 }
926 if got, want := s.spanContext.TraceID().String(), p.TraceID().String(); got != want {
927 return fmt.Errorf("got child trace ID %s, want %s", got, want)
928 }
929 if childID, parentID := s.spanContext.SpanID().String(), p.SpanID().String(); childID == parentID {
930 return fmt.Errorf("got child span ID %s, parent span ID %s; want unequal IDs", childID, parentID)
931 }
932 if got, want := s.spanContext.TraceFlags(), p.TraceFlags(); got != want {
933 return fmt.Errorf("got child trace options %d, want %d", got, want)
934 }
935 got, want := s.spanContext.TraceState(), p.TraceState()
936 assert.Equal(t, want, got)
937 return nil
938 }
939
940
941
942 func startSpan(tp *TracerProvider, trName string, args ...trace.SpanStartOption) trace.Span {
943 return startNamedSpan(tp, trName, "span0", args...)
944 }
945
946
947
948
949
950 func startNamedSpan(tp *TracerProvider, trName, name string, args ...trace.SpanStartOption) trace.Span {
951 _, span := tp.Tracer(trName).Start(
952 trace.ContextWithRemoteSpanContext(context.Background(), sc),
953 name,
954 args...,
955 )
956 return span
957 }
958
959
960
961
962
963 func startLocalSpan(ctx context.Context, tp *TracerProvider, trName, name string, args ...trace.SpanStartOption) (context.Context, trace.Span) {
964 ctx, span := tp.Tracer(trName).Start(
965 ctx,
966 name,
967 args...,
968 )
969 return ctx, span
970 }
971
972
973
974
975
976
977
978
979
980
981 func endSpan(te *testExporter, span trace.Span) (*snapshot, error) {
982 if !span.IsRecording() {
983 return nil, fmt.Errorf("method IsRecording: got false, want true")
984 }
985 if !span.SpanContext().IsSampled() {
986 return nil, fmt.Errorf("method IsSampled: got false, want true")
987 }
988 span.End()
989 if te.Len() != 1 {
990 return nil, fmt.Errorf("got %d exported spans, want one span", te.Len())
991 }
992 got := te.Spans()[0]
993 if !got.SpanContext().SpanID().IsValid() {
994 return nil, fmt.Errorf("exporting span: expected nonzero SpanID")
995 }
996 got.spanContext = got.SpanContext().WithSpanID(trace.SpanID{})
997 if !checkTime(&got.startTime) {
998 return nil, fmt.Errorf("exporting span: expected nonzero StartTime")
999 }
1000 if !checkTime(&got.endTime) {
1001 return nil, fmt.Errorf("exporting span: expected nonzero EndTime")
1002 }
1003 return got, nil
1004 }
1005
1006
1007 func checkTime(x *time.Time) bool {
1008 if x.IsZero() {
1009 return false
1010 }
1011 *x = time.Time{}
1012 return true
1013 }
1014
1015 func TestEndSpanTwice(t *testing.T) {
1016 te := NewTestExporter()
1017 tp := NewTracerProvider(WithSyncer(te))
1018
1019 st := time.Now()
1020 et1 := st.Add(100 * time.Millisecond)
1021 et2 := st.Add(200 * time.Millisecond)
1022
1023 span := startSpan(tp, "EndSpanTwice", trace.WithTimestamp(st))
1024 span.End(trace.WithTimestamp(et1))
1025 span.End(trace.WithTimestamp(et2))
1026
1027 if te.Len() != 1 {
1028 t.Fatalf("expected only a single span, got %#v", te.Spans())
1029 }
1030
1031 ro := span.(ReadOnlySpan)
1032 if ro.EndTime() != et1 {
1033 t.Fatalf("2nd call to End() should not modify end time")
1034 }
1035 }
1036
1037 func TestStartSpanAfterEnd(t *testing.T) {
1038 te := NewTestExporter()
1039 tp := NewTracerProvider(WithSampler(AlwaysSample()), WithSyncer(te))
1040 ctx := context.Background()
1041
1042 tr := tp.Tracer("SpanAfterEnd")
1043 ctx, span0 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "parent")
1044 ctx1, span1 := tr.Start(ctx, "span-1")
1045 span1.End()
1046
1047
1048 _, span2 := tr.Start(ctx1, "span-2")
1049 span2.End()
1050 span0.End()
1051 if got, want := te.Len(), 3; got != want {
1052 t.Fatalf("len(%#v) = %d; want %d", te.Spans(), got, want)
1053 }
1054
1055 gotParent, ok := te.GetSpan("parent")
1056 if !ok {
1057 t.Fatal("parent not recorded")
1058 }
1059 gotSpan1, ok := te.GetSpan("span-1")
1060 if !ok {
1061 t.Fatal("span-1 not recorded")
1062 }
1063 gotSpan2, ok := te.GetSpan("span-2")
1064 if !ok {
1065 t.Fatal("span-2 not recorded")
1066 }
1067
1068 if got, want := gotSpan1.SpanContext().TraceID(), gotParent.SpanContext().TraceID(); got != want {
1069 t.Errorf("span-1.TraceID=%q; want %q", got, want)
1070 }
1071 if got, want := gotSpan2.SpanContext().TraceID(), gotParent.SpanContext().TraceID(); got != want {
1072 t.Errorf("span-2.TraceID=%q; want %q", got, want)
1073 }
1074 if got, want := gotSpan1.Parent().SpanID(), gotParent.SpanContext().SpanID(); got != want {
1075 t.Errorf("span-1.ParentSpanID=%q; want %q (parent.SpanID)", got, want)
1076 }
1077 if got, want := gotSpan2.Parent().SpanID(), gotSpan1.SpanContext().SpanID(); got != want {
1078 t.Errorf("span-2.ParentSpanID=%q; want %q (span1.SpanID)", got, want)
1079 }
1080 }
1081
1082 func TestChildSpanCount(t *testing.T) {
1083 te := NewTestExporter()
1084 tp := NewTracerProvider(WithSampler(AlwaysSample()), WithSyncer(te))
1085
1086 tr := tp.Tracer("ChidSpanCount")
1087 ctx, span0 := tr.Start(context.Background(), "parent")
1088 ctx1, span1 := tr.Start(ctx, "span-1")
1089 _, span2 := tr.Start(ctx1, "span-2")
1090 span2.End()
1091 span1.End()
1092
1093 _, span3 := tr.Start(ctx, "span-3")
1094 span3.End()
1095 span0.End()
1096 if got, want := te.Len(), 4; got != want {
1097 t.Fatalf("len(%#v) = %d; want %d", te.Spans(), got, want)
1098 }
1099
1100 gotParent, ok := te.GetSpan("parent")
1101 if !ok {
1102 t.Fatal("parent not recorded")
1103 }
1104 gotSpan1, ok := te.GetSpan("span-1")
1105 if !ok {
1106 t.Fatal("span-1 not recorded")
1107 }
1108 gotSpan2, ok := te.GetSpan("span-2")
1109 if !ok {
1110 t.Fatal("span-2 not recorded")
1111 }
1112 gotSpan3, ok := te.GetSpan("span-3")
1113 if !ok {
1114 t.Fatal("span-3 not recorded")
1115 }
1116
1117 if got, want := gotSpan3.ChildSpanCount(), 0; got != want {
1118 t.Errorf("span-3.ChildSpanCount=%d; want %d", got, want)
1119 }
1120 if got, want := gotSpan2.ChildSpanCount(), 0; got != want {
1121 t.Errorf("span-2.ChildSpanCount=%d; want %d", got, want)
1122 }
1123 if got, want := gotSpan1.ChildSpanCount(), 1; got != want {
1124 t.Errorf("span-1.ChildSpanCount=%d; want %d", got, want)
1125 }
1126 if got, want := gotParent.ChildSpanCount(), 2; got != want {
1127 t.Errorf("parent.ChildSpanCount=%d; want %d", got, want)
1128 }
1129 }
1130
1131 func TestNilSpanEnd(t *testing.T) {
1132 var span *recordingSpan
1133 span.End()
1134 }
1135
1136 func TestNonRecordingSpanDoesNotTrackRuntimeTracerTask(t *testing.T) {
1137 tp := NewTracerProvider(WithSampler(NeverSample()))
1138 tr := tp.Tracer("TestNonRecordingSpanDoesNotTrackRuntimeTracerTask")
1139
1140 _, apiSpan := tr.Start(context.Background(), "foo")
1141 if _, ok := apiSpan.(runtimeTracer); ok {
1142 t.Fatalf("non recording span implements runtime trace task tracking")
1143 }
1144 }
1145
1146 func TestRecordingSpanRuntimeTracerTaskEnd(t *testing.T) {
1147 tp := NewTracerProvider(WithSampler(AlwaysSample()))
1148 tr := tp.Tracer("TestRecordingSpanRuntimeTracerTaskEnd")
1149
1150 var n uint64
1151 executionTracerTaskEnd := func() {
1152 atomic.AddUint64(&n, 1)
1153 }
1154 _, apiSpan := tr.Start(context.Background(), "foo")
1155 s, ok := apiSpan.(*recordingSpan)
1156 if !ok {
1157 t.Fatal("recording span not returned from always sampled Tracer")
1158 }
1159
1160 s.executionTracerTaskEnd = executionTracerTaskEnd
1161 s.End()
1162
1163 if n != 1 {
1164 t.Error("recording span did not end runtime trace task")
1165 }
1166 }
1167
1168 func TestCustomStartEndTime(t *testing.T) {
1169 te := NewTestExporter()
1170 tp := NewTracerProvider(WithSyncer(te), WithSampler(AlwaysSample()))
1171
1172 startTime := time.Date(2019, time.August, 27, 14, 42, 0, 0, time.UTC)
1173 endTime := startTime.Add(time.Second * 20)
1174 _, span := tp.Tracer("Custom Start and End time").Start(
1175 context.Background(),
1176 "testspan",
1177 trace.WithTimestamp(startTime),
1178 )
1179 span.End(trace.WithTimestamp(endTime))
1180
1181 if te.Len() != 1 {
1182 t.Fatalf("got %d exported spans, want one span", te.Len())
1183 }
1184 got := te.Spans()[0]
1185 if !got.StartTime().Equal(startTime) {
1186 t.Errorf("expected start time to be %s, got %s", startTime, got.StartTime())
1187 }
1188 if !got.EndTime().Equal(endTime) {
1189 t.Errorf("expected end time to be %s, got %s", endTime, got.EndTime())
1190 }
1191 }
1192
1193 func TestRecordError(t *testing.T) {
1194 scenarios := []struct {
1195 err error
1196 typ string
1197 msg string
1198 }{
1199 {
1200 err: ottest.NewTestError("test error"),
1201 typ: "go.opentelemetry.io/otel/sdk/internal/internaltest.TestError",
1202 msg: "test error",
1203 },
1204 {
1205 err: errors.New("test error 2"),
1206 typ: "*errors.errorString",
1207 msg: "test error 2",
1208 },
1209 }
1210
1211 for _, s := range scenarios {
1212 te := NewTestExporter()
1213 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
1214 span := startSpan(tp, "RecordError")
1215
1216 errTime := time.Now()
1217 span.RecordError(s.err, trace.WithTimestamp(errTime))
1218
1219 got, err := endSpan(te, span)
1220 if err != nil {
1221 t.Fatal(err)
1222 }
1223
1224 want := &snapshot{
1225 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1226 TraceID: tid,
1227 TraceFlags: 0x1,
1228 }),
1229 parent: sc.WithRemote(true),
1230 name: "span0",
1231 status: Status{Code: codes.Unset},
1232 spanKind: trace.SpanKindInternal,
1233 events: []Event{
1234 {
1235 Name: semconv.ExceptionEventName,
1236 Time: errTime,
1237 Attributes: []attribute.KeyValue{
1238 semconv.ExceptionType(s.typ),
1239 semconv.ExceptionMessage(s.msg),
1240 },
1241 },
1242 },
1243 instrumentationScope: instrumentation.Scope{Name: "RecordError"},
1244 }
1245 if diff := cmpDiff(got, want); diff != "" {
1246 t.Errorf("SpanErrorOptions: -got +want %s", diff)
1247 }
1248 }
1249 }
1250
1251 func TestRecordErrorWithStackTrace(t *testing.T) {
1252 err := ottest.NewTestError("test error")
1253 typ := "go.opentelemetry.io/otel/sdk/internal/internaltest.TestError"
1254 msg := "test error"
1255
1256 te := NewTestExporter()
1257 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
1258 span := startSpan(tp, "RecordError")
1259
1260 errTime := time.Now()
1261 span.RecordError(err, trace.WithTimestamp(errTime), trace.WithStackTrace(true))
1262
1263 got, err := endSpan(te, span)
1264 if err != nil {
1265 t.Fatal(err)
1266 }
1267
1268 want := &snapshot{
1269 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1270 TraceID: tid,
1271 TraceFlags: 0x1,
1272 }),
1273 parent: sc.WithRemote(true),
1274 name: "span0",
1275 status: Status{Code: codes.Unset},
1276 spanKind: trace.SpanKindInternal,
1277 events: []Event{
1278 {
1279 Name: semconv.ExceptionEventName,
1280 Time: errTime,
1281 Attributes: []attribute.KeyValue{
1282 semconv.ExceptionType(typ),
1283 semconv.ExceptionMessage(msg),
1284 },
1285 },
1286 },
1287 instrumentationScope: instrumentation.Scope{Name: "RecordError"},
1288 }
1289
1290 assert.Equal(t, got.spanContext, want.spanContext)
1291 assert.Equal(t, got.parent, want.parent)
1292 assert.Equal(t, got.name, want.name)
1293 assert.Equal(t, got.status, want.status)
1294 assert.Equal(t, got.spanKind, want.spanKind)
1295 assert.Equal(t, got.events[0].Attributes[0].Value.AsString(), want.events[0].Attributes[0].Value.AsString())
1296 assert.Equal(t, got.events[0].Attributes[1].Value.AsString(), want.events[0].Attributes[1].Value.AsString())
1297 gotStackTraceFunctionName := strings.Split(got.events[0].Attributes[2].Value.AsString(), "\n")
1298
1299 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[1], "go.opentelemetry.io/otel/sdk/trace.recordStackTrace"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.recordStackTrace", gotStackTraceFunctionName[1])
1300 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[3], "go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).RecordError"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).RecordError", gotStackTraceFunctionName[3])
1301 }
1302
1303 func TestRecordErrorNil(t *testing.T) {
1304 te := NewTestExporter()
1305 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
1306 span := startSpan(tp, "RecordErrorNil")
1307
1308 span.RecordError(nil)
1309
1310 got, err := endSpan(te, span)
1311 if err != nil {
1312 t.Fatal(err)
1313 }
1314
1315 want := &snapshot{
1316 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1317 TraceID: tid,
1318 TraceFlags: 0x1,
1319 }),
1320 parent: sc.WithRemote(true),
1321 name: "span0",
1322 spanKind: trace.SpanKindInternal,
1323 status: Status{
1324 Code: codes.Unset,
1325 Description: "",
1326 },
1327 instrumentationScope: instrumentation.Scope{Name: "RecordErrorNil"},
1328 }
1329 if diff := cmpDiff(got, want); diff != "" {
1330 t.Errorf("SpanErrorOptions: -got +want %s", diff)
1331 }
1332 }
1333
1334 func TestWithSpanKind(t *testing.T) {
1335 te := NewTestExporter()
1336 tp := NewTracerProvider(WithSyncer(te), WithSampler(AlwaysSample()), WithResource(resource.Empty()))
1337 tr := tp.Tracer("withSpanKind")
1338
1339 _, span := tr.Start(context.Background(), "WithoutSpanKind")
1340 spanData, err := endSpan(te, span)
1341 if err != nil {
1342 t.Error(err.Error())
1343 }
1344
1345 if spanData.SpanKind() != trace.SpanKindInternal {
1346 t.Errorf("Default value of Spankind should be Internal: got %+v, want %+v\n", spanData.SpanKind(), trace.SpanKindInternal)
1347 }
1348
1349 sks := []trace.SpanKind{
1350 trace.SpanKindInternal,
1351 trace.SpanKindServer,
1352 trace.SpanKindClient,
1353 trace.SpanKindProducer,
1354 trace.SpanKindConsumer,
1355 }
1356
1357 for _, sk := range sks {
1358 te.Reset()
1359
1360 _, span := tr.Start(context.Background(), fmt.Sprintf("SpanKind-%v", sk), trace.WithSpanKind(sk))
1361 spanData, err := endSpan(te, span)
1362 if err != nil {
1363 t.Error(err.Error())
1364 }
1365
1366 if spanData.SpanKind() != sk {
1367 t.Errorf("WithSpanKind check: got %+v, want %+v\n", spanData.SpanKind(), sks)
1368 }
1369 }
1370 }
1371
1372 func mergeResource(t *testing.T, r1, r2 *resource.Resource) *resource.Resource {
1373 r, err := resource.Merge(r1, r2)
1374 assert.NoError(t, err)
1375 return r
1376 }
1377
1378 func TestWithResource(t *testing.T) {
1379 store, err := ottest.SetEnvVariables(map[string]string{
1380 envVar: "key=value,rk5=7",
1381 })
1382 require.NoError(t, err)
1383 defer func() { require.NoError(t, store.Restore()) }()
1384
1385 cases := []struct {
1386 name string
1387 options []TracerProviderOption
1388 want *resource.Resource
1389 msg string
1390 }{
1391 {
1392 name: "explicitly empty resource",
1393 options: []TracerProviderOption{WithResource(resource.Empty())},
1394 want: resource.Environment(),
1395 },
1396 {
1397 name: "uses default if no resource option",
1398 options: []TracerProviderOption{},
1399 want: resource.Default(),
1400 },
1401 {
1402 name: "explicit resource",
1403 options: []TracerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))},
1404 want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))),
1405 },
1406 {
1407 name: "last resource wins",
1408 options: []TracerProviderOption{
1409 WithResource(resource.NewSchemaless(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))),
1410 WithResource(resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
1411 },
1412 want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
1413 },
1414 {
1415 name: "overlapping attributes with environment resource",
1416 options: []TracerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))},
1417 want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))),
1418 },
1419 }
1420 for _, tc := range cases {
1421 tc := tc
1422 t.Run(tc.name, func(t *testing.T) {
1423 te := NewTestExporter()
1424 defaultOptions := []TracerProviderOption{WithSyncer(te), WithSampler(AlwaysSample())}
1425 tp := NewTracerProvider(append(defaultOptions, tc.options...)...)
1426 span := startSpan(tp, "WithResource")
1427 span.SetAttributes(attribute.String("key1", "value1"))
1428 got, err := endSpan(te, span)
1429 if err != nil {
1430 t.Error(err.Error())
1431 }
1432 want := &snapshot{
1433 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1434 TraceID: tid,
1435 TraceFlags: 0x1,
1436 }),
1437 parent: sc.WithRemote(true),
1438 name: "span0",
1439 attributes: []attribute.KeyValue{
1440 attribute.String("key1", "value1"),
1441 },
1442 spanKind: trace.SpanKindInternal,
1443 resource: tc.want,
1444 instrumentationScope: instrumentation.Scope{Name: "WithResource"},
1445 }
1446 if diff := cmpDiff(got, want); diff != "" {
1447 t.Errorf("WithResource:\n -got +want %s", diff)
1448 }
1449 })
1450 }
1451 }
1452
1453 func TestWithInstrumentationVersionAndSchema(t *testing.T) {
1454 te := NewTestExporter()
1455 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
1456
1457 ctx := context.Background()
1458 ctx = trace.ContextWithRemoteSpanContext(ctx, sc)
1459 _, span := tp.Tracer(
1460 "WithInstrumentationVersion",
1461 trace.WithInstrumentationVersion("v0.1.0"),
1462 trace.WithSchemaURL("https://opentelemetry.io/schemas/1.2.0"),
1463 ).Start(ctx, "span0")
1464 got, err := endSpan(te, span)
1465 if err != nil {
1466 t.Error(err.Error())
1467 }
1468
1469 want := &snapshot{
1470 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1471 TraceID: tid,
1472 TraceFlags: 0x1,
1473 }),
1474 parent: sc.WithRemote(true),
1475 name: "span0",
1476 spanKind: trace.SpanKindInternal,
1477 instrumentationScope: instrumentation.Scope{
1478 Name: "WithInstrumentationVersion",
1479 Version: "v0.1.0",
1480 SchemaURL: "https://opentelemetry.io/schemas/1.2.0",
1481 },
1482 }
1483 if diff := cmpDiff(got, want); diff != "" {
1484 t.Errorf("WithResource:\n -got +want %s", diff)
1485 }
1486 }
1487
1488 func TestSpanCapturesPanic(t *testing.T) {
1489 te := NewTestExporter()
1490 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
1491 _, span := tp.Tracer("CatchPanic").Start(
1492 context.Background(),
1493 "span",
1494 )
1495
1496 f := func() {
1497 defer span.End()
1498 panic(errors.New("error message"))
1499 }
1500 require.PanicsWithError(t, "error message", f)
1501 spans := te.Spans()
1502 require.Len(t, spans, 1)
1503 require.Len(t, spans[0].Events(), 1)
1504 assert.Equal(t, spans[0].Events()[0].Name, semconv.ExceptionEventName)
1505 assert.Equal(t, spans[0].Events()[0].Attributes, []attribute.KeyValue{
1506 semconv.ExceptionType("*errors.errorString"),
1507 semconv.ExceptionMessage("error message"),
1508 })
1509 }
1510
1511 func TestSpanCapturesPanicWithStackTrace(t *testing.T) {
1512 te := NewTestExporter()
1513 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty()))
1514 _, span := tp.Tracer("CatchPanic").Start(
1515 context.Background(),
1516 "span",
1517 )
1518
1519 f := func() {
1520 defer span.End(trace.WithStackTrace(true))
1521 panic(errors.New("error message"))
1522 }
1523 require.PanicsWithError(t, "error message", f)
1524 spans := te.Spans()
1525 require.Len(t, spans, 1)
1526 require.Len(t, spans[0].Events(), 1)
1527 assert.Equal(t, spans[0].Events()[0].Name, semconv.ExceptionEventName)
1528 assert.Equal(t, spans[0].Events()[0].Attributes[0].Value.AsString(), "*errors.errorString")
1529 assert.Equal(t, spans[0].Events()[0].Attributes[1].Value.AsString(), "error message")
1530
1531 gotStackTraceFunctionName := strings.Split(spans[0].Events()[0].Attributes[2].Value.AsString(), "\n")
1532 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[1], "go.opentelemetry.io/otel/sdk/trace.recordStackTrace"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.recordStackTrace", gotStackTraceFunctionName[1])
1533 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[3], "go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End", gotStackTraceFunctionName[3])
1534 }
1535
1536 func TestReadOnlySpan(t *testing.T) {
1537 kv := attribute.String("foo", "bar")
1538
1539 tp := NewTracerProvider(WithResource(resource.NewSchemaless(kv)))
1540 tr := tp.Tracer("ReadOnlySpan", trace.WithInstrumentationVersion("3"))
1541
1542
1543 tID, sID := tp.idGenerator.NewIDs(context.Background())
1544 parent := trace.NewSpanContext(trace.SpanContextConfig{
1545 TraceID: tID,
1546 SpanID: sID,
1547 TraceFlags: 0x1,
1548 Remote: true,
1549 })
1550 ctx := trace.ContextWithRemoteSpanContext(context.Background(), parent)
1551
1552
1553 tID, sID = tp.idGenerator.NewIDs(context.Background())
1554 linked := trace.NewSpanContext(trace.SpanContextConfig{
1555 TraceID: tID,
1556 SpanID: sID,
1557 TraceFlags: 0x1,
1558 })
1559
1560 st := time.Now()
1561 ctx, s := tr.Start(ctx, "foo", trace.WithTimestamp(st),
1562 trace.WithLinks(trace.Link{SpanContext: linked}))
1563 s.SetAttributes(kv)
1564 s.AddEvent("foo", trace.WithAttributes(kv))
1565 s.SetStatus(codes.Ok, "foo")
1566
1567
1568 ro, ok := s.(ReadOnlySpan)
1569 require.True(t, ok)
1570
1571 assert.Equal(t, "foo", ro.Name())
1572 assert.Equal(t, trace.SpanContextFromContext(ctx), ro.SpanContext())
1573 assert.Equal(t, parent, ro.Parent())
1574 assert.Equal(t, trace.SpanKindInternal, ro.SpanKind())
1575 assert.Equal(t, st, ro.StartTime())
1576 assert.True(t, ro.EndTime().IsZero())
1577 assert.Equal(t, kv.Key, ro.Attributes()[0].Key)
1578 assert.Equal(t, kv.Value, ro.Attributes()[0].Value)
1579 assert.Equal(t, linked, ro.Links()[0].SpanContext)
1580 assert.Equal(t, kv.Key, ro.Events()[0].Attributes[0].Key)
1581 assert.Equal(t, kv.Value, ro.Events()[0].Attributes[0].Value)
1582 assert.Equal(t, codes.Ok, ro.Status().Code)
1583 assert.Equal(t, "", ro.Status().Description)
1584 assert.Equal(t, "ReadOnlySpan", ro.InstrumentationLibrary().Name)
1585 assert.Equal(t, "3", ro.InstrumentationLibrary().Version)
1586 assert.Equal(t, "ReadOnlySpan", ro.InstrumentationScope().Name)
1587 assert.Equal(t, "3", ro.InstrumentationScope().Version)
1588 assert.Equal(t, kv.Key, ro.Resource().Attributes()[0].Key)
1589 assert.Equal(t, kv.Value, ro.Resource().Attributes()[0].Value)
1590
1591
1592 s.SetName("bar")
1593 assert.Equal(t, "bar", ro.Name())
1594
1595
1596
1597 d1 := s.(*recordingSpan).snapshot()
1598 s.AddEvent("baz")
1599 d2 := s.(*recordingSpan).snapshot()
1600 for _, e := range d1.Events() {
1601 if e.Name == "baz" {
1602 t.Errorf("Didn't expect to find 'baz' event")
1603 }
1604 }
1605 var exists bool
1606 for _, e := range d2.Events() {
1607 if e.Name == "baz" {
1608 exists = true
1609 }
1610 }
1611 if !exists {
1612 t.Errorf("Expected to find 'baz' event")
1613 }
1614
1615 et := st.Add(time.Millisecond)
1616 s.End(trace.WithTimestamp(et))
1617 assert.Equal(t, et, ro.EndTime())
1618 }
1619
1620 func TestReadWriteSpan(t *testing.T) {
1621 tp := NewTracerProvider(WithResource(resource.Empty()))
1622 tr := tp.Tracer("ReadWriteSpan")
1623
1624
1625 tID, sID := tp.idGenerator.NewIDs(context.Background())
1626 parent := trace.NewSpanContext(trace.SpanContextConfig{
1627 TraceID: tID,
1628 SpanID: sID,
1629 TraceFlags: 0x1,
1630 })
1631 ctx := trace.ContextWithRemoteSpanContext(context.Background(), parent)
1632
1633 _, span := tr.Start(ctx, "foo")
1634 defer span.End()
1635
1636
1637 rw, ok := span.(ReadWriteSpan)
1638 require.True(t, ok)
1639
1640
1641 assert.False(t, rw.StartTime().IsZero())
1642
1643
1644 rw.SetName("bar")
1645 assert.Equal(t, "bar", rw.Name())
1646
1647
1648
1649
1650
1651 }
1652
1653 func TestAddEventsWithMoreAttributesThanLimit(t *testing.T) {
1654 te := NewTestExporter()
1655 sl := NewSpanLimits()
1656 sl.AttributePerEventCountLimit = 2
1657 tp := NewTracerProvider(
1658 WithSpanLimits(sl),
1659 WithSyncer(te),
1660 WithResource(resource.Empty()),
1661 )
1662
1663 span := startSpan(tp, "AddSpanEventWithOverLimitedAttributes")
1664 span.AddEvent("test1", trace.WithAttributes(
1665 attribute.Bool("key1", true),
1666 attribute.String("key2", "value2"),
1667 ))
1668
1669 span.AddEvent("test2", trace.WithAttributes(
1670 attribute.Bool("key1", true),
1671 attribute.String("key2", "value2"),
1672 attribute.String("key3", "value3"),
1673 attribute.String("key4", "value4"),
1674 ))
1675 got, err := endSpan(te, span)
1676 if err != nil {
1677 t.Fatal(err)
1678 }
1679
1680 for i := range got.Events() {
1681 if !checkTime(&got.Events()[i].Time) {
1682 t.Error("exporting span: expected nonzero Event Time")
1683 }
1684 }
1685
1686 want := &snapshot{
1687 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1688 TraceID: tid,
1689 TraceFlags: 0x1,
1690 }),
1691 parent: sc.WithRemote(true),
1692 name: "span0",
1693 attributes: nil,
1694 events: []Event{
1695 {
1696 Name: "test1",
1697 Attributes: []attribute.KeyValue{
1698 attribute.Bool("key1", true),
1699 attribute.String("key2", "value2"),
1700 },
1701 },
1702 {
1703 Name: "test2",
1704 Attributes: []attribute.KeyValue{
1705 attribute.Bool("key1", true),
1706 attribute.String("key2", "value2"),
1707 },
1708 DroppedAttributeCount: 2,
1709 },
1710 },
1711 spanKind: trace.SpanKindInternal,
1712 instrumentationScope: instrumentation.Scope{Name: "AddSpanEventWithOverLimitedAttributes"},
1713 }
1714 if diff := cmpDiff(got, want); diff != "" {
1715 t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff)
1716 }
1717 }
1718
1719 func TestAddLinksWithMoreAttributesThanLimit(t *testing.T) {
1720 te := NewTestExporter()
1721 sl := NewSpanLimits()
1722 sl.AttributePerLinkCountLimit = 1
1723 tp := NewTracerProvider(
1724 WithSpanLimits(sl),
1725 WithSyncer(te),
1726 WithResource(resource.Empty()),
1727 )
1728
1729 k1v1 := attribute.String("key1", "value1")
1730 k2v2 := attribute.String("key2", "value2")
1731 k3v3 := attribute.String("key3", "value3")
1732 k4v4 := attribute.String("key4", "value4")
1733
1734 sc1 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
1735 sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
1736
1737 span := startSpan(tp, "Links", trace.WithLinks([]trace.Link{
1738 {SpanContext: sc1, Attributes: []attribute.KeyValue{k1v1, k2v2}},
1739 {SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2, k3v3, k4v4}},
1740 }...))
1741
1742 got, err := endSpan(te, span)
1743 if err != nil {
1744 t.Fatal(err)
1745 }
1746
1747 want := &snapshot{
1748 spanContext: trace.NewSpanContext(trace.SpanContextConfig{
1749 TraceID: tid,
1750 TraceFlags: 0x1,
1751 }),
1752 parent: sc.WithRemote(true),
1753 name: "span0",
1754 links: []Link{
1755 {
1756 SpanContext: sc1,
1757 Attributes: []attribute.KeyValue{k1v1},
1758 DroppedAttributeCount: 1,
1759 },
1760 {
1761 SpanContext: sc2,
1762 Attributes: []attribute.KeyValue{k2v2},
1763 DroppedAttributeCount: 2,
1764 },
1765 },
1766 spanKind: trace.SpanKindInternal,
1767 instrumentationScope: instrumentation.Scope{Name: "Links"},
1768 }
1769 if diff := cmpDiff(got, want); diff != "" {
1770 t.Errorf("Link: -got +want %s", diff)
1771 }
1772 }
1773
1774 type stateSampler struct {
1775 prefix string
1776 f func(trace.TraceState) trace.TraceState
1777 }
1778
1779 func (s *stateSampler) ShouldSample(p SamplingParameters) SamplingResult {
1780 decision := Drop
1781 if strings.HasPrefix(p.Name, s.prefix) {
1782 decision = RecordAndSample
1783 }
1784 ts := s.f(trace.SpanContextFromContext(p.ParentContext).TraceState())
1785 return SamplingResult{Decision: decision, Tracestate: ts}
1786 }
1787
1788 func (s stateSampler) Description() string {
1789 return "stateSampler"
1790 }
1791
1792
1793 func TestSamplerTraceState(t *testing.T) {
1794 mustTS := func(ts trace.TraceState, err error) trace.TraceState {
1795 require.NoError(t, err)
1796 return ts
1797 }
1798 makeInserter := func(k, v, prefix string) Sampler {
1799 return &stateSampler{
1800 prefix: prefix,
1801 f: func(t trace.TraceState) trace.TraceState { return mustTS(t.Insert(k, v)) },
1802 }
1803 }
1804 makeDeleter := func(k, prefix string) Sampler {
1805 return &stateSampler{
1806 prefix: prefix,
1807 f: func(t trace.TraceState) trace.TraceState { return t.Delete(k) },
1808 }
1809 }
1810 clearer := func(prefix string) Sampler {
1811 return &stateSampler{
1812 prefix: prefix,
1813 f: func(t trace.TraceState) trace.TraceState { return trace.TraceState{} },
1814 }
1815 }
1816
1817 tests := []struct {
1818 name string
1819 sampler Sampler
1820 spanName string
1821 input trace.TraceState
1822 want trace.TraceState
1823 exportSpan bool
1824 }{
1825 {
1826 name: "alwaysOn",
1827 sampler: AlwaysSample(),
1828 input: mustTS(trace.ParseTraceState("k1=v1")),
1829 want: mustTS(trace.ParseTraceState("k1=v1")),
1830 exportSpan: true,
1831 },
1832 {
1833 name: "alwaysOff",
1834 sampler: NeverSample(),
1835 input: mustTS(trace.ParseTraceState("k1=v1")),
1836 want: mustTS(trace.ParseTraceState("k1=v1")),
1837 exportSpan: false,
1838 },
1839 {
1840 name: "insertKeySampled",
1841 sampler: makeInserter("k2", "v2", "span"),
1842 spanName: "span0",
1843 input: mustTS(trace.ParseTraceState("k1=v1")),
1844 want: mustTS(trace.ParseTraceState("k2=v2,k1=v1")),
1845 exportSpan: true,
1846 },
1847 {
1848 name: "insertKeyDropped",
1849 sampler: makeInserter("k2", "v2", "span"),
1850 spanName: "nospan0",
1851 input: mustTS(trace.ParseTraceState("k1=v1")),
1852 want: mustTS(trace.ParseTraceState("k2=v2,k1=v1")),
1853 exportSpan: false,
1854 },
1855 {
1856 name: "deleteKeySampled",
1857 sampler: makeDeleter("k1", "span"),
1858 spanName: "span0",
1859 input: mustTS(trace.ParseTraceState("k1=v1,k2=v2")),
1860 want: mustTS(trace.ParseTraceState("k2=v2")),
1861 exportSpan: true,
1862 },
1863 {
1864 name: "deleteKeyDropped",
1865 sampler: makeDeleter("k1", "span"),
1866 spanName: "nospan0",
1867 input: mustTS(trace.ParseTraceState("k1=v1,k2=v2,k3=v3")),
1868 want: mustTS(trace.ParseTraceState("k2=v2,k3=v3")),
1869 exportSpan: false,
1870 },
1871 {
1872 name: "clearer",
1873 sampler: clearer("span"),
1874 spanName: "span0",
1875 input: mustTS(trace.ParseTraceState("k1=v1,k3=v3")),
1876 want: mustTS(trace.ParseTraceState("")),
1877 exportSpan: true,
1878 },
1879 }
1880
1881 for _, ts := range tests {
1882 ts := ts
1883 t.Run(ts.name, func(t *testing.T) {
1884 te := NewTestExporter()
1885 tp := NewTracerProvider(WithSampler(ts.sampler), WithSyncer(te), WithResource(resource.Empty()))
1886 tr := tp.Tracer("TraceState")
1887
1888 sc1 := trace.NewSpanContext(trace.SpanContextConfig{
1889 TraceID: tid,
1890 SpanID: sid,
1891 TraceFlags: trace.FlagsSampled,
1892 TraceState: ts.input,
1893 })
1894 ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc1)
1895 _, span := tr.Start(ctx, ts.spanName)
1896
1897
1898 require.Equal(t, ts.want, span.SpanContext().TraceState())
1899
1900 span.End()
1901
1902 got := te.Spans()
1903 if len(got) > 0 != ts.exportSpan {
1904 t.Errorf("unexpected number of exported spans %d", len(got))
1905 }
1906 if len(got) == 0 {
1907 return
1908 }
1909
1910 receivedState := got[0].SpanContext().TraceState()
1911
1912 if diff := cmpDiff(receivedState, ts.want); diff != "" {
1913 t.Errorf("TraceState not propagated: -got +want %s", diff)
1914 }
1915 })
1916 }
1917 }
1918
1919 type testIDGenerator struct {
1920 traceID int
1921 spanID int
1922 }
1923
1924 func (gen *testIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) {
1925 traceIDHex := fmt.Sprintf("%032x", gen.traceID)
1926 traceID, _ := trace.TraceIDFromHex(traceIDHex)
1927 gen.traceID++
1928
1929 spanID := gen.NewSpanID(ctx, traceID)
1930 return traceID, spanID
1931 }
1932
1933 func (gen *testIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID {
1934 spanIDHex := fmt.Sprintf("%016x", gen.spanID)
1935 spanID, _ := trace.SpanIDFromHex(spanIDHex)
1936 gen.spanID++
1937 return spanID
1938 }
1939
1940 var _ IDGenerator = (*testIDGenerator)(nil)
1941
1942 func TestWithIDGenerator(t *testing.T) {
1943 const (
1944 startTraceID = 1
1945 startSpanID = 1
1946 numSpan = 10
1947 )
1948
1949 gen := &testIDGenerator{traceID: startSpanID, spanID: startSpanID}
1950
1951 for i := 0; i < numSpan; i++ {
1952 te := NewTestExporter()
1953 tp := NewTracerProvider(
1954 WithSyncer(te),
1955 WithIDGenerator(gen),
1956 )
1957 span := startSpan(tp, "TestWithIDGenerator")
1958 got, err := strconv.ParseUint(span.SpanContext().SpanID().String(), 16, 64)
1959 require.NoError(t, err)
1960 want := uint64(startSpanID + i)
1961 assert.Equal(t, got, want)
1962 _, err = endSpan(te, span)
1963 require.NoError(t, err)
1964 }
1965 }
1966
1967 func TestEmptyRecordingSpanAttributes(t *testing.T) {
1968 assert.Nil(t, (&recordingSpan{}).Attributes())
1969 }
1970
1971 func TestEmptyRecordingSpanDroppedAttributes(t *testing.T) {
1972 assert.Equal(t, 0, (&recordingSpan{}).DroppedAttributes())
1973 }
1974
View as plain text