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