1168 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			1168 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright The OpenTelemetry Authors
 | 
						|
// SPDX-License-Identifier: Apache-2.0
 | 
						|
 | 
						|
package trace
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"math"
 | 
						|
	"strconv"
 | 
						|
	"sync"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/stretchr/testify/assert"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
 | 
						|
	"go.opentelemetry.io/otel/attribute"
 | 
						|
	"go.opentelemetry.io/otel/codes"
 | 
						|
	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
 | 
						|
	"go.opentelemetry.io/otel/trace/internal/telemetry"
 | 
						|
)
 | 
						|
 | 
						|
const tName = "tracer.name"
 | 
						|
 | 
						|
var (
 | 
						|
	attrs = []attribute.KeyValue{
 | 
						|
		attribute.Bool("bool", true),
 | 
						|
		attribute.Int("int", -1),
 | 
						|
		attribute.Int64("int64", 43),
 | 
						|
		attribute.Float64("float64", 0.3),
 | 
						|
		attribute.String("string", "value"),
 | 
						|
		attribute.BoolSlice("bool slice", []bool{true, false, true}),
 | 
						|
		attribute.IntSlice("int slice", []int{-1, -30, 328}),
 | 
						|
		attribute.Int64Slice("int64 slice", []int64{1030, 0, 0}),
 | 
						|
		attribute.Float64Slice("float64 slice", []float64{1e9}),
 | 
						|
		attribute.StringSlice("string slice", []string{"one", "two"}),
 | 
						|
	}
 | 
						|
 | 
						|
	tAttrs = []telemetry.Attr{
 | 
						|
		telemetry.Bool("bool", true),
 | 
						|
		telemetry.Int("int", -1),
 | 
						|
		telemetry.Int64("int64", 43),
 | 
						|
		telemetry.Float64("float64", 0.3),
 | 
						|
		telemetry.String("string", "value"),
 | 
						|
		telemetry.Slice(
 | 
						|
			"bool slice",
 | 
						|
			telemetry.BoolValue(true),
 | 
						|
			telemetry.BoolValue(false),
 | 
						|
			telemetry.BoolValue(true),
 | 
						|
		),
 | 
						|
		telemetry.Slice("int slice",
 | 
						|
			telemetry.IntValue(-1),
 | 
						|
			telemetry.IntValue(-30),
 | 
						|
			telemetry.IntValue(328),
 | 
						|
		),
 | 
						|
		telemetry.Slice("int64 slice",
 | 
						|
			telemetry.Int64Value(1030),
 | 
						|
			telemetry.Int64Value(0),
 | 
						|
			telemetry.Int64Value(0),
 | 
						|
		),
 | 
						|
		telemetry.Slice("float64 slice", telemetry.Float64Value(1e9)),
 | 
						|
		telemetry.Slice("string slice",
 | 
						|
			telemetry.StringValue("one"),
 | 
						|
			telemetry.StringValue("two"),
 | 
						|
		),
 | 
						|
	}
 | 
						|
 | 
						|
	spanContext0 = NewSpanContext(SpanContextConfig{
 | 
						|
		TraceID:    TraceID{0x1},
 | 
						|
		SpanID:     SpanID{0x1},
 | 
						|
		TraceFlags: FlagsSampled,
 | 
						|
	})
 | 
						|
	spanContext1 = NewSpanContext(SpanContextConfig{
 | 
						|
		TraceID:    TraceID{0x2},
 | 
						|
		SpanID:     SpanID{0x2},
 | 
						|
		TraceFlags: FlagsSampled,
 | 
						|
	})
 | 
						|
 | 
						|
	link0 = Link{
 | 
						|
		SpanContext: spanContext0,
 | 
						|
		Attributes: []attribute.KeyValue{
 | 
						|
			attribute.Int("n", 0),
 | 
						|
		},
 | 
						|
	}
 | 
						|
	link1 = Link{
 | 
						|
		SpanContext: spanContext1,
 | 
						|
		Attributes: []attribute.KeyValue{
 | 
						|
			attribute.Int("n", 1),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	tLink0 = &telemetry.SpanLink{
 | 
						|
		TraceID: telemetry.TraceID(spanContext0.TraceID()),
 | 
						|
		SpanID:  telemetry.SpanID(spanContext0.SpanID()),
 | 
						|
		Flags:   uint32(spanContext0.TraceFlags()),
 | 
						|
		Attrs:   []telemetry.Attr{telemetry.Int("n", 0)},
 | 
						|
	}
 | 
						|
	tLink1 = &telemetry.SpanLink{
 | 
						|
		TraceID: telemetry.TraceID(spanContext1.TraceID()),
 | 
						|
		SpanID:  telemetry.SpanID(spanContext1.SpanID()),
 | 
						|
		Flags:   uint32(spanContext1.TraceFlags()),
 | 
						|
		Attrs:   []telemetry.Attr{telemetry.Int("n", 1)},
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
func TestTracerProviderInstance(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	tp0, tp1 := newAutoTracerProvider(), newAutoTracerProvider()
 | 
						|
 | 
						|
	assert.Same(t, tracerProviderInstance, tp0)
 | 
						|
	assert.Same(t, tracerProviderInstance, tp1)
 | 
						|
}
 | 
						|
 | 
						|
func TestTracerProviderConcurrentSafe(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	const goroutines = 10
 | 
						|
 | 
						|
	run := func(tp TracerProvider) <-chan struct{} {
 | 
						|
		done := make(chan struct{})
 | 
						|
		go func(tp TracerProvider) {
 | 
						|
			defer close(done)
 | 
						|
 | 
						|
			var wg sync.WaitGroup
 | 
						|
			for i := 0; i < goroutines; i++ {
 | 
						|
				wg.Add(1)
 | 
						|
				go func(name, version string) {
 | 
						|
					defer wg.Done()
 | 
						|
					_ = tp.Tracer(name, WithInstrumentationVersion(version))
 | 
						|
				}("tracer"+strconv.Itoa(i%4), strconv.Itoa(i%2))
 | 
						|
			}
 | 
						|
 | 
						|
			wg.Wait()
 | 
						|
		}(tp)
 | 
						|
		return done
 | 
						|
	}
 | 
						|
 | 
						|
	assert.NotPanics(t, func() {
 | 
						|
		done0, done1 := run(newAutoTracerProvider()), run(newAutoTracerProvider())
 | 
						|
 | 
						|
		<-done0
 | 
						|
		<-done1
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanKindTransform(t *testing.T) {
 | 
						|
	tests := map[SpanKind]telemetry.SpanKind{
 | 
						|
		SpanKind(-1):          telemetry.SpanKind(0),
 | 
						|
		SpanKindUnspecified:   telemetry.SpanKind(0),
 | 
						|
		SpanKind(math.MaxInt): telemetry.SpanKind(0),
 | 
						|
 | 
						|
		SpanKindInternal: telemetry.SpanKindInternal,
 | 
						|
		SpanKindServer:   telemetry.SpanKindServer,
 | 
						|
		SpanKindClient:   telemetry.SpanKindClient,
 | 
						|
		SpanKindProducer: telemetry.SpanKindProducer,
 | 
						|
		SpanKindConsumer: telemetry.SpanKindConsumer,
 | 
						|
	}
 | 
						|
 | 
						|
	for in, want := range tests {
 | 
						|
		assert.Equal(t, want, spanKind(in), in.String())
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestTracerStartPropagatesOrigCtx(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	type ctxKey struct{}
 | 
						|
	var key ctxKey
 | 
						|
	val := "value"
 | 
						|
 | 
						|
	ctx := context.WithValue(context.Background(), key, val)
 | 
						|
	ctx, _ = newAutoTracerProvider().Tracer(tName).Start(ctx, "span.name")
 | 
						|
 | 
						|
	assert.Equal(t, val, ctx.Value(key))
 | 
						|
}
 | 
						|
 | 
						|
func TestTracerStartReturnsNonNilSpan(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	tr := newAutoTracerProvider().Tracer(tName)
 | 
						|
	_, s := tr.Start(context.Background(), "span.name")
 | 
						|
	assert.NotNil(t, s)
 | 
						|
}
 | 
						|
 | 
						|
func TestTracerStartAddsSpanToCtx(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	tr := newAutoTracerProvider().Tracer(tName)
 | 
						|
	ctx, s := tr.Start(context.Background(), "span.name")
 | 
						|
 | 
						|
	assert.Same(t, s, SpanFromContext(ctx))
 | 
						|
}
 | 
						|
 | 
						|
func TestTracerConcurrentSafe(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	const goroutines = 10
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
	run := func(tracer Tracer) <-chan struct{} {
 | 
						|
		done := make(chan struct{})
 | 
						|
 | 
						|
		go func(tr Tracer) {
 | 
						|
			defer close(done)
 | 
						|
 | 
						|
			var wg sync.WaitGroup
 | 
						|
			for i := 0; i < goroutines; i++ {
 | 
						|
				wg.Add(1)
 | 
						|
				go func(name string) {
 | 
						|
					defer wg.Done()
 | 
						|
					_, _ = tr.Start(ctx, name)
 | 
						|
				}("span" + strconv.Itoa(i))
 | 
						|
			}
 | 
						|
 | 
						|
			wg.Wait()
 | 
						|
		}(tracer)
 | 
						|
 | 
						|
		return done
 | 
						|
	}
 | 
						|
 | 
						|
	assert.NotPanics(t, func() {
 | 
						|
		tp := newAutoTracerProvider()
 | 
						|
		done0, done1 := run(tp.Tracer("t0")), run(tp.Tracer("t1"))
 | 
						|
 | 
						|
		<-done0
 | 
						|
		<-done1
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanCreation(t *testing.T) {
 | 
						|
	const (
 | 
						|
		spanName   = "span name"
 | 
						|
		tracerName = "go.opentelemetry.io/otel/sdk/test"
 | 
						|
		tracerVer  = "v0.1.0"
 | 
						|
	)
 | 
						|
 | 
						|
	ts := time.Now()
 | 
						|
 | 
						|
	tracer := newAutoTracerProvider().Tracer(
 | 
						|
		tracerName,
 | 
						|
		WithInstrumentationVersion(tracerVer),
 | 
						|
		WithSchemaURL(semconv.SchemaURL),
 | 
						|
	)
 | 
						|
 | 
						|
	assertTracer := func(traces *telemetry.Traces) func(*testing.T) {
 | 
						|
		return func(t *testing.T) {
 | 
						|
			t.Helper()
 | 
						|
 | 
						|
			rs := traces.ResourceSpans
 | 
						|
			require.Len(t, rs, 1)
 | 
						|
			sss := rs[0].ScopeSpans
 | 
						|
			require.Len(t, sss, 1)
 | 
						|
			ss := sss[0]
 | 
						|
			assert.Equal(t, tracerName, ss.Scope.Name, "tracer name")
 | 
						|
			assert.Equal(t, tracerVer, ss.Scope.Version, "tracer version")
 | 
						|
			assert.Equal(t, semconv.SchemaURL, ss.SchemaURL, "tracer schema URL")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	testcases := []struct {
 | 
						|
		TestName string
 | 
						|
		SpanName string
 | 
						|
		Options  []SpanStartOption
 | 
						|
		Setup    func(*testing.T)
 | 
						|
		Eval     func(*testing.T, context.Context, *autoSpan)
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			TestName: "SampledByDefault",
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
 | 
						|
				assert.True(t, s.sampled.Load(), "not sampled by default.")
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "ParentSpanContext",
 | 
						|
			Setup: func(t *testing.T) {
 | 
						|
				orig := start
 | 
						|
				t.Cleanup(func() { start = orig })
 | 
						|
				start = func(_ context.Context, _ *autoSpan, psc *SpanContext, _ *bool, _ *SpanContext) {
 | 
						|
					*psc = spanContext0
 | 
						|
				}
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
 | 
						|
				want := spanContext0.SpanID().String()
 | 
						|
				got := s.span.ParentSpanID.String()
 | 
						|
				assert.Equal(t, want, got)
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "SpanContext",
 | 
						|
			Setup: func(t *testing.T) {
 | 
						|
				orig := start
 | 
						|
				t.Cleanup(func() { start = orig })
 | 
						|
				start = func(_ context.Context, _ *autoSpan, _ *SpanContext, _ *bool, sc *SpanContext) {
 | 
						|
					*sc = spanContext0
 | 
						|
				}
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
 | 
						|
				str := func(i interface{ String() string }) string {
 | 
						|
					return i.String()
 | 
						|
				}
 | 
						|
				assert.Equal(t, str(spanContext0.TraceID()), s.span.TraceID.String(), "trace ID")
 | 
						|
				assert.Equal(t, str(spanContext0.SpanID()), s.span.SpanID.String(), "autoSpan ID")
 | 
						|
				assert.Equal(t, uint32(spanContext0.TraceFlags()), s.span.Flags, "flags")
 | 
						|
				assert.Equal(t, str(spanContext0.TraceState()), s.span.TraceState, "tracestate")
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "NotSampled",
 | 
						|
			Setup: func(t *testing.T) {
 | 
						|
				orig := start
 | 
						|
				t.Cleanup(func() { start = orig })
 | 
						|
				start = func(_ context.Context, _ *autoSpan, _ *SpanContext, s *bool, _ *SpanContext) {
 | 
						|
					*s = false
 | 
						|
				}
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				assert.False(t, s.sampled.Load(), "sampled")
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "WithName",
 | 
						|
			SpanName: spanName,
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
				assert.Equal(t, spanName, s.span.Name)
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "WithSpanKind",
 | 
						|
			Options: []SpanStartOption{
 | 
						|
				WithSpanKind(SpanKindClient),
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
				assert.Equal(t, telemetry.SpanKindClient, s.span.Kind)
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "WithTimestamp",
 | 
						|
			Options: []SpanStartOption{
 | 
						|
				WithTimestamp(ts),
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
				assert.Equal(t, ts, s.span.StartTime)
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "WithAttributes",
 | 
						|
			Options: []SpanStartOption{
 | 
						|
				WithAttributes(attrs...),
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
				assert.Equal(t, tAttrs, s.span.Attrs)
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			TestName: "WithLinks",
 | 
						|
			Options: []SpanStartOption{
 | 
						|
				WithLinks(link0, link1),
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, _ context.Context, s *autoSpan) {
 | 
						|
				t.Run("Tracer", assertTracer(s.traces))
 | 
						|
				want := []*telemetry.SpanLink{tLink0, tLink1}
 | 
						|
				assert.Equal(t, want, s.span.Links)
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	ctx := context.Background()
 | 
						|
	for _, tc := range testcases {
 | 
						|
		t.Run(tc.TestName, func(t *testing.T) {
 | 
						|
			if tc.Setup != nil {
 | 
						|
				tc.Setup(t)
 | 
						|
			}
 | 
						|
 | 
						|
			c, sIface := tracer.Start(ctx, tc.SpanName, tc.Options...)
 | 
						|
			require.IsType(t, &autoSpan{}, sIface)
 | 
						|
			s := sIface.(*autoSpan)
 | 
						|
 | 
						|
			tc.Eval(t, c, s)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanEnd(t *testing.T) {
 | 
						|
	orig := ended
 | 
						|
	t.Cleanup(func() { ended = orig })
 | 
						|
 | 
						|
	var buf []byte
 | 
						|
	ended = func(b []byte) { buf = b }
 | 
						|
 | 
						|
	timeNow := time.Unix(0, time.Now().UnixNano()) // No location.
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		Name    string
 | 
						|
		Options []SpanEndOption
 | 
						|
		Eval    func(*testing.T, time.Time)
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			Name: "Now",
 | 
						|
			Eval: func(t *testing.T, ts time.Time) {
 | 
						|
				assert.False(t, ts.IsZero(), "zero end time")
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name: "WithTimestamp",
 | 
						|
			Options: []SpanEndOption{
 | 
						|
				WithTimestamp(timeNow),
 | 
						|
			},
 | 
						|
			Eval: func(t *testing.T, ts time.Time) {
 | 
						|
				assert.Equal(t, timeNow, ts, "end time not set")
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run(test.Name, func(t *testing.T) {
 | 
						|
			s := spanBuilder{}.Build()
 | 
						|
			s.End(test.Options...)
 | 
						|
 | 
						|
			assert.False(t, s.sampled.Load(), "ended autoSpan should not be sampled")
 | 
						|
			require.NotNil(t, buf, "no span data emitted")
 | 
						|
 | 
						|
			var traces telemetry.Traces
 | 
						|
			err := json.Unmarshal(buf, &traces)
 | 
						|
			require.NoError(t, err)
 | 
						|
 | 
						|
			rs := traces.ResourceSpans
 | 
						|
			require.Len(t, rs, 1)
 | 
						|
			ss := rs[0].ScopeSpans
 | 
						|
			require.Len(t, ss, 1)
 | 
						|
			spans := ss[0].Spans
 | 
						|
			require.Len(t, spans, 1)
 | 
						|
 | 
						|
			test.Eval(t, spans[0].EndTime)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanNilUnsampledGuards(t *testing.T) {
 | 
						|
	run := func(fn func(s *autoSpan)) func(*testing.T) {
 | 
						|
		return func(t *testing.T) {
 | 
						|
			t.Helper()
 | 
						|
 | 
						|
			f := func(s *autoSpan) func() { return func() { fn(s) } }
 | 
						|
			assert.NotPanics(t, f(nil), "nil span")
 | 
						|
			assert.NotPanics(t, f(new(autoSpan)), "unsampled span")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	t.Run("End", run(func(s *autoSpan) { s.End() }))
 | 
						|
	t.Run("AddEvent", run(func(s *autoSpan) { s.AddEvent("event name") }))
 | 
						|
	t.Run("AddLink", run(func(s *autoSpan) { s.AddLink(Link{}) }))
 | 
						|
	t.Run("IsRecording", run(func(s *autoSpan) { _ = s.IsRecording() }))
 | 
						|
	t.Run("RecordError", run(func(s *autoSpan) { s.RecordError(nil) }))
 | 
						|
	t.Run("SpanContext", run(func(s *autoSpan) { _ = s.SpanContext() }))
 | 
						|
	t.Run("SetStatus", run(func(s *autoSpan) { s.SetStatus(codes.Error, "test") }))
 | 
						|
	t.Run("SetName", run(func(s *autoSpan) { s.SetName("span name") }))
 | 
						|
	t.Run("SetAttributes", run(func(s *autoSpan) { s.SetAttributes(attrs...) }))
 | 
						|
	t.Run("TracerProvider", run(func(s *autoSpan) { _ = s.TracerProvider() }))
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanAddLink(t *testing.T) {
 | 
						|
	s := spanBuilder{
 | 
						|
		Options: []SpanStartOption{WithLinks(link0)},
 | 
						|
	}.Build()
 | 
						|
	s.AddLink(link1)
 | 
						|
 | 
						|
	want := []*telemetry.SpanLink{tLink0, tLink1}
 | 
						|
	assert.Equal(t, want, s.span.Links)
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanAddLinkLimit(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		limit   int
 | 
						|
		want    []*telemetry.SpanLink
 | 
						|
		dropped uint32
 | 
						|
	}{
 | 
						|
		{0, nil, 2},
 | 
						|
		{1, []*telemetry.SpanLink{tLink1}, 1},
 | 
						|
		{2, []*telemetry.SpanLink{tLink0, tLink1}, 0},
 | 
						|
		{-1, []*telemetry.SpanLink{tLink0, tLink1}, 0},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run("Limit/"+strconv.Itoa(test.limit), func(t *testing.T) {
 | 
						|
			orig := maxSpan.Links
 | 
						|
			maxSpan.Links = test.limit
 | 
						|
			t.Cleanup(func() { maxSpan.Links = orig })
 | 
						|
 | 
						|
			builder := spanBuilder{}
 | 
						|
			s := builder.Build()
 | 
						|
			s.AddLink(link0)
 | 
						|
			s.AddLink(link1)
 | 
						|
			assert.Equal(t, test.want, s.span.Links, "AddLink")
 | 
						|
			assert.Equal(t, test.dropped, s.span.DroppedLinks, "AddLink DroppedLinks")
 | 
						|
 | 
						|
			builder.Options = []SpanStartOption{
 | 
						|
				WithLinks(link0, link1),
 | 
						|
			}
 | 
						|
			s = builder.Build()
 | 
						|
			assert.Equal(t, test.want, s.span.Links, "NewSpan")
 | 
						|
			assert.Equal(t, test.dropped, s.span.DroppedLinks, "NewSpan DroppedLinks")
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanLinkAttrLimit(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		limit   int
 | 
						|
		want    []telemetry.Attr
 | 
						|
		dropped uint32
 | 
						|
	}{
 | 
						|
		{0, nil, uint32(len(tAttrs))},
 | 
						|
		{2, tAttrs[:2], uint32(len(tAttrs) - 2)},
 | 
						|
		{len(tAttrs), tAttrs, 0},
 | 
						|
		{-1, tAttrs, 0},
 | 
						|
	}
 | 
						|
 | 
						|
	link := Link{Attributes: attrs}
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run("Limit/"+strconv.Itoa(test.limit), func(t *testing.T) {
 | 
						|
			orig := maxSpan.LinkAttrs
 | 
						|
			maxSpan.LinkAttrs = test.limit
 | 
						|
			t.Cleanup(func() { maxSpan.LinkAttrs = orig })
 | 
						|
 | 
						|
			builder := spanBuilder{}
 | 
						|
 | 
						|
			s := builder.Build()
 | 
						|
			s.AddLink(link)
 | 
						|
 | 
						|
			require.Len(t, s.span.Links, 1)
 | 
						|
			got := s.span.Links[0]
 | 
						|
			assert.Equal(t, test.want, got.Attrs, "AddLink attrs")
 | 
						|
			assert.Equal(t, test.dropped, got.DroppedAttrs, "dropped AddLink attrs")
 | 
						|
 | 
						|
			builder.Options = []SpanStartOption{WithLinks(link)}
 | 
						|
			s = builder.Build()
 | 
						|
 | 
						|
			require.Len(t, s.span.Links, 1)
 | 
						|
			got = s.span.Links[0]
 | 
						|
			assert.Equal(t, test.want, got.Attrs, "NewSpan link attrs")
 | 
						|
			assert.Equal(t, test.dropped, got.DroppedAttrs, "dropped NewSpan link attrs")
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanIsRecording(t *testing.T) {
 | 
						|
	builder := spanBuilder{}
 | 
						|
	s := builder.Build()
 | 
						|
	assert.True(t, s.IsRecording(), "sampled span should be recorded")
 | 
						|
 | 
						|
	builder.NotSampled = true
 | 
						|
	s = builder.Build()
 | 
						|
	assert.False(t, s.IsRecording(), "unsampled span should not be recorded")
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanRecordError(t *testing.T) {
 | 
						|
	s := spanBuilder{}.Build()
 | 
						|
 | 
						|
	var want []*telemetry.SpanEvent
 | 
						|
	s.RecordError(nil)
 | 
						|
	require.Equal(t, want, s.span.Events, "nil error recorded")
 | 
						|
 | 
						|
	ts := time.Now()
 | 
						|
	err := errors.New("test")
 | 
						|
	s.RecordError(
 | 
						|
		err,
 | 
						|
		WithTimestamp(ts),
 | 
						|
		WithAttributes(attribute.Bool("testing", true)),
 | 
						|
	)
 | 
						|
	want = append(want, &telemetry.SpanEvent{
 | 
						|
		Name: semconv.ExceptionEventName,
 | 
						|
		Time: ts,
 | 
						|
		Attrs: []telemetry.Attr{
 | 
						|
			telemetry.Bool("testing", true),
 | 
						|
			telemetry.String(string(semconv.ExceptionTypeKey), "*errors.errorString"),
 | 
						|
			telemetry.String(string(semconv.ExceptionMessageKey), err.Error()),
 | 
						|
		},
 | 
						|
	})
 | 
						|
	assert.Equal(t, want, s.span.Events, "nil error recorded")
 | 
						|
 | 
						|
	s.RecordError(err, WithStackTrace(true))
 | 
						|
	require.Len(t, s.span.Events, 2, "missing event")
 | 
						|
 | 
						|
	var hasST bool
 | 
						|
	for _, attr := range s.span.Events[1].Attrs {
 | 
						|
		if attr.Key == string(semconv.ExceptionStacktraceKey) {
 | 
						|
			hasST = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	assert.True(t, hasST, "missing stacktrace attribute")
 | 
						|
}
 | 
						|
 | 
						|
func TestAddEventLimit(t *testing.T) {
 | 
						|
	const a, b, c = "a", "b", "c"
 | 
						|
 | 
						|
	ts := time.Now()
 | 
						|
 | 
						|
	evtA := &telemetry.SpanEvent{Name: "a", Time: ts}
 | 
						|
	evtB := &telemetry.SpanEvent{Name: "b", Time: ts}
 | 
						|
	evtC := &telemetry.SpanEvent{Name: "c", Time: ts}
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		limit   int
 | 
						|
		want    []*telemetry.SpanEvent
 | 
						|
		dropped uint32
 | 
						|
	}{
 | 
						|
		{0, nil, 3},
 | 
						|
		{1, []*telemetry.SpanEvent{evtC}, 2},
 | 
						|
		{2, []*telemetry.SpanEvent{evtB, evtC}, 1},
 | 
						|
		{3, []*telemetry.SpanEvent{evtA, evtB, evtC}, 0},
 | 
						|
		{-1, []*telemetry.SpanEvent{evtA, evtB, evtC}, 0},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run("Limit/"+strconv.Itoa(test.limit), func(t *testing.T) {
 | 
						|
			orig := maxSpan.Events
 | 
						|
			maxSpan.Events = test.limit
 | 
						|
			t.Cleanup(func() { maxSpan.Events = orig })
 | 
						|
 | 
						|
			builder := spanBuilder{}
 | 
						|
 | 
						|
			s := builder.Build()
 | 
						|
			s.addEvent(a, ts, nil)
 | 
						|
			s.addEvent(b, ts, nil)
 | 
						|
			s.addEvent(c, ts, nil)
 | 
						|
 | 
						|
			assert.Equal(t, test.want, s.span.Events, "add event")
 | 
						|
			assert.Equal(t, test.dropped, s.span.DroppedEvents, "dropped events")
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestAddEventAttrLimit(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		limit   int
 | 
						|
		want    []telemetry.Attr
 | 
						|
		dropped uint32
 | 
						|
	}{
 | 
						|
		{0, nil, uint32(len(tAttrs))},
 | 
						|
		{2, tAttrs[:2], uint32(len(tAttrs) - 2)},
 | 
						|
		{len(tAttrs), tAttrs, 0},
 | 
						|
		{-1, tAttrs, 0},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run("Limit/"+strconv.Itoa(test.limit), func(t *testing.T) {
 | 
						|
			orig := maxSpan.EventAttrs
 | 
						|
			maxSpan.EventAttrs = test.limit
 | 
						|
			t.Cleanup(func() { maxSpan.EventAttrs = orig })
 | 
						|
 | 
						|
			builder := spanBuilder{}
 | 
						|
 | 
						|
			s := builder.Build()
 | 
						|
			s.addEvent("name", time.Now(), attrs)
 | 
						|
 | 
						|
			require.Len(t, s.span.Events, 1)
 | 
						|
			got := s.span.Events[0]
 | 
						|
			assert.Equal(t, test.want, got.Attrs, "event attrs")
 | 
						|
			assert.Equal(t, test.dropped, got.DroppedAttrs, "dropped event attrs")
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanSpanContext(t *testing.T) {
 | 
						|
	s := spanBuilder{SpanContext: spanContext0}.Build()
 | 
						|
	assert.Equal(t, spanContext0, s.SpanContext())
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanSetStatus(t *testing.T) {
 | 
						|
	s := spanBuilder{}.Build()
 | 
						|
 | 
						|
	assert.Nil(t, s.span.Status, "empty status should not be set")
 | 
						|
 | 
						|
	const msg = "test"
 | 
						|
	want := &telemetry.Status{Message: msg}
 | 
						|
 | 
						|
	for c, tCode := range map[codes.Code]telemetry.StatusCode{
 | 
						|
		codes.Error: telemetry.StatusCodeError,
 | 
						|
		codes.Ok:    telemetry.StatusCodeOK,
 | 
						|
		codes.Unset: telemetry.StatusCodeUnset,
 | 
						|
	} {
 | 
						|
		want.Code = tCode
 | 
						|
		s.SetStatus(c, msg)
 | 
						|
		assert.Equalf(t, want, s.span.Status, "code: %s, msg: %s", c, msg)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanSetName(t *testing.T) {
 | 
						|
	const name = "span name"
 | 
						|
	builder := spanBuilder{}
 | 
						|
 | 
						|
	s := builder.Build()
 | 
						|
	s.SetName(name)
 | 
						|
	assert.Equal(t, name, s.span.Name, "span name not set")
 | 
						|
 | 
						|
	builder.Name = "alt"
 | 
						|
	s = builder.Build()
 | 
						|
	s.SetName(name)
 | 
						|
	assert.Equal(t, name, s.span.Name, "SetName did not overwrite")
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanSetAttributes(t *testing.T) {
 | 
						|
	builder := spanBuilder{}
 | 
						|
 | 
						|
	s := builder.Build()
 | 
						|
	s.SetAttributes(attrs...)
 | 
						|
	assert.Equal(t, tAttrs, s.span.Attrs, "span attributes not set")
 | 
						|
 | 
						|
	builder.Options = []SpanStartOption{
 | 
						|
		WithAttributes(attrs[0].Key.Bool(!attrs[0].Value.AsBool())),
 | 
						|
	}
 | 
						|
 | 
						|
	s = builder.Build()
 | 
						|
	s.SetAttributes(attrs...)
 | 
						|
	assert.Equal(t, tAttrs, s.span.Attrs, "SpanAttributes did not override")
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanAttributeLimits(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		limit   int
 | 
						|
		want    []telemetry.Attr
 | 
						|
		dropped uint32
 | 
						|
	}{
 | 
						|
		{0, nil, uint32(len(tAttrs))},
 | 
						|
		{2, tAttrs[:2], uint32(len(tAttrs) - 2)},
 | 
						|
		{len(tAttrs), tAttrs, 0},
 | 
						|
		{-1, tAttrs, 0},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run("Limit/"+strconv.Itoa(test.limit), func(t *testing.T) {
 | 
						|
			orig := maxSpan.Attrs
 | 
						|
			maxSpan.Attrs = test.limit
 | 
						|
			t.Cleanup(func() { maxSpan.Attrs = orig })
 | 
						|
 | 
						|
			builder := spanBuilder{}
 | 
						|
 | 
						|
			s := builder.Build()
 | 
						|
			s.SetAttributes(attrs...)
 | 
						|
			assert.Equal(t, test.want, s.span.Attrs, "set span attributes")
 | 
						|
			assert.Equal(t, test.dropped, s.span.DroppedAttrs, "dropped attrs")
 | 
						|
 | 
						|
			s.SetAttributes(attrs...)
 | 
						|
			assert.Equal(t, test.want, s.span.Attrs, "set span attributes twice")
 | 
						|
			assert.Equal(t, 2*test.dropped, s.span.DroppedAttrs, "2x dropped attrs")
 | 
						|
 | 
						|
			builder.Options = []SpanStartOption{WithAttributes(attrs...)}
 | 
						|
 | 
						|
			s = builder.Build()
 | 
						|
			assert.Equal(t, test.want, s.span.Attrs, "new span attributes")
 | 
						|
			assert.Equal(t, test.dropped, s.span.DroppedAttrs, "dropped attrs")
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanAttributeValueLimits(t *testing.T) {
 | 
						|
	value := "hello world"
 | 
						|
 | 
						|
	aStr := attribute.String("string", value)
 | 
						|
	aStrSlice := attribute.StringSlice("slice", []string{value, value})
 | 
						|
 | 
						|
	eq := func(a, b []telemetry.Attr) bool {
 | 
						|
		if len(a) != len(b) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		for i := range a {
 | 
						|
			if !a[i].Equal(b[i]) {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	}
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		limit int
 | 
						|
		want  string
 | 
						|
	}{
 | 
						|
		{0, ""},
 | 
						|
		{2, value[:2]},
 | 
						|
		{11, value},
 | 
						|
		{-1, value},
 | 
						|
	}
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run("Limit/"+strconv.Itoa(test.limit), func(t *testing.T) {
 | 
						|
			orig := maxSpan.AttrValueLen
 | 
						|
			maxSpan.AttrValueLen = test.limit
 | 
						|
			t.Cleanup(func() { maxSpan.AttrValueLen = orig })
 | 
						|
 | 
						|
			builder := spanBuilder{}
 | 
						|
 | 
						|
			want := []telemetry.Attr{
 | 
						|
				telemetry.String("string", test.want),
 | 
						|
				telemetry.Slice(
 | 
						|
					"slice",
 | 
						|
					telemetry.StringValue(test.want),
 | 
						|
					telemetry.StringValue(test.want),
 | 
						|
				),
 | 
						|
			}
 | 
						|
 | 
						|
			s := builder.Build()
 | 
						|
			s.SetAttributes(aStr, aStrSlice)
 | 
						|
			assert.Truef(t, eq(want, s.span.Attrs), "set span attributes: got %#v, want %#v", s.span.Attrs, want)
 | 
						|
 | 
						|
			s.AddEvent("test", WithAttributes(aStr, aStrSlice))
 | 
						|
			assert.Truef(t, eq(want, s.span.Events[0].Attrs), "span event attributes: got %#v, want %#v", s.span.Events[0].Attrs, want)
 | 
						|
 | 
						|
			s.AddLink(Link{
 | 
						|
				Attributes: []attribute.KeyValue{aStr, aStrSlice},
 | 
						|
			})
 | 
						|
			assert.Truef(t, eq(want, s.span.Links[0].Attrs), "span link attributes: got %#v, want %#v", s.span.Links[0].Attrs, want)
 | 
						|
 | 
						|
			builder.Options = []SpanStartOption{
 | 
						|
				WithAttributes(aStr, aStrSlice),
 | 
						|
				WithLinks(Link{
 | 
						|
					Attributes: []attribute.KeyValue{aStr, aStrSlice},
 | 
						|
				}),
 | 
						|
			}
 | 
						|
			s = builder.Build()
 | 
						|
			assert.Truef(t, eq(want, s.span.Attrs), "new span attributes: got %#v, want %#v", s.span.Attrs, want)
 | 
						|
			assert.Truef(t, eq(want, s.span.Links[0].Attrs), "new span link attributes: got %#v, want %#v", s.span.Attrs, want)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanTracerProvider(t *testing.T) {
 | 
						|
	var s autoSpan
 | 
						|
 | 
						|
	got := s.TracerProvider()
 | 
						|
	assert.IsType(t, &autoTracerProvider{}, got)
 | 
						|
}
 | 
						|
 | 
						|
type spanBuilder struct {
 | 
						|
	Name        string
 | 
						|
	NotSampled  bool
 | 
						|
	SpanContext SpanContext
 | 
						|
	Options     []SpanStartOption
 | 
						|
}
 | 
						|
 | 
						|
func (b spanBuilder) Build() *autoSpan {
 | 
						|
	tracer := new(autoTracer)
 | 
						|
	s := &autoSpan{spanContext: b.SpanContext}
 | 
						|
	s.sampled.Store(!b.NotSampled)
 | 
						|
	s.traces, s.span = tracer.traces(
 | 
						|
		b.Name,
 | 
						|
		NewSpanStartConfig(b.Options...),
 | 
						|
		s.spanContext,
 | 
						|
		SpanContext{},
 | 
						|
	)
 | 
						|
 | 
						|
	return s
 | 
						|
}
 | 
						|
 | 
						|
func TestTruncate(t *testing.T) {
 | 
						|
	type group struct {
 | 
						|
		limit    int
 | 
						|
		input    string
 | 
						|
		expected string
 | 
						|
	}
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		name   string
 | 
						|
		groups []group
 | 
						|
	}{
 | 
						|
		// Edge case: limit is negative, no truncation should occur
 | 
						|
		{
 | 
						|
			name: "NoTruncation",
 | 
						|
			groups: []group{
 | 
						|
				{-1, "No truncation!", "No truncation!"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Edge case: string is already shorter than the limit, no truncation
 | 
						|
		// should occur
 | 
						|
		{
 | 
						|
			name: "ShortText",
 | 
						|
			groups: []group{
 | 
						|
				{10, "Short text", "Short text"},
 | 
						|
				{15, "Short text", "Short text"},
 | 
						|
				{100, "Short text", "Short text"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Edge case: truncation happens with ASCII characters only
 | 
						|
		{
 | 
						|
			name: "ASCIIOnly",
 | 
						|
			groups: []group{
 | 
						|
				{1, "Hello World!", "H"},
 | 
						|
				{5, "Hello World!", "Hello"},
 | 
						|
				{12, "Hello World!", "Hello World!"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Truncation including multi-byte characters (UTF-8)
 | 
						|
		{
 | 
						|
			name: "ValidUTF-8",
 | 
						|
			groups: []group{
 | 
						|
				{7, "Hello, 世界", "Hello, "},
 | 
						|
				{8, "Hello, 世界", "Hello, 世"},
 | 
						|
				{2, "こんにちは", "こん"},
 | 
						|
				{3, "こんにちは", "こんに"},
 | 
						|
				{5, "こんにちは", "こんにちは"},
 | 
						|
				{12, "こんにちは", "こんにちは"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Truncation with invalid UTF-8 characters
 | 
						|
		{
 | 
						|
			name: "InvalidUTF-8",
 | 
						|
			groups: []group{
 | 
						|
				{11, "Invalid\x80text", "Invalidtext"},
 | 
						|
				// Do not modify invalid text if equal to limit.
 | 
						|
				{11, "Valid text\x80", "Valid text\x80"},
 | 
						|
				// Do not modify invalid text if under limit.
 | 
						|
				{15, "Valid text\x80", "Valid text\x80"},
 | 
						|
				{5, "Hello\x80World", "Hello"},
 | 
						|
				{11, "Hello\x80World\x80!", "HelloWorld!"},
 | 
						|
				{15, "Hello\x80World\x80Test", "HelloWorldTest"},
 | 
						|
				{15, "Hello\x80\x80\x80World\x80Test", "HelloWorldTest"},
 | 
						|
				{15, "\x80\x80\x80Hello\x80\x80\x80World\x80Test\x80\x80", "HelloWorldTest"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Truncation with mixed validn and invalid UTF-8 characters
 | 
						|
		{
 | 
						|
			name: "MixedUTF-8",
 | 
						|
			groups: []group{
 | 
						|
				{6, "€"[0:2] + "hello€€", "hello€"},
 | 
						|
				{6, "€" + "€"[0:2] + "hello", "€hello"},
 | 
						|
				{11, "Valid text\x80📜", "Valid text📜"},
 | 
						|
				{11, "Valid text📜\x80", "Valid text📜"},
 | 
						|
				{14, "😊 Hello\x80World🌍🚀", "😊 HelloWorld🌍🚀"},
 | 
						|
				{14, "😊\x80 Hello\x80World🌍🚀", "😊 HelloWorld🌍🚀"},
 | 
						|
				{14, "😊\x80 Hello\x80World🌍\x80🚀", "😊 HelloWorld🌍🚀"},
 | 
						|
				{14, "😊\x80 Hello\x80World🌍\x80🚀\x80", "😊 HelloWorld🌍🚀"},
 | 
						|
				{14, "\x80😊\x80 Hello\x80World🌍\x80🚀\x80", "😊 HelloWorld🌍🚀"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Edge case: empty string, should return empty string
 | 
						|
		{
 | 
						|
			name: "Empty",
 | 
						|
			groups: []group{
 | 
						|
				{5, "", ""},
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		// Edge case: limit is 0, should return an empty string
 | 
						|
		{
 | 
						|
			name: "Zero",
 | 
						|
			groups: []group{
 | 
						|
				{0, "Some text", ""},
 | 
						|
				{0, "", ""},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		for _, g := range tt.groups {
 | 
						|
			t.Run(tt.name, func(t *testing.T) {
 | 
						|
				t.Parallel()
 | 
						|
 | 
						|
				got := truncate(g.limit, g.input)
 | 
						|
				assert.Equalf(
 | 
						|
					t, g.expected, got,
 | 
						|
					"input: %q([]rune%v))\ngot: %q([]rune%v)\nwant %q([]rune%v)",
 | 
						|
					g.input, []rune(g.input),
 | 
						|
					got, []rune(got),
 | 
						|
					g.expected, []rune(g.expected),
 | 
						|
				)
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func BenchmarkTruncate(b *testing.B) {
 | 
						|
	run := func(limit int, input string) func(b *testing.B) {
 | 
						|
		return func(b *testing.B) {
 | 
						|
			b.ReportAllocs()
 | 
						|
			b.RunParallel(func(pb *testing.PB) {
 | 
						|
				var out string
 | 
						|
				for pb.Next() {
 | 
						|
					out = truncate(limit, input)
 | 
						|
				}
 | 
						|
				_ = out
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	b.Run("Unlimited", run(-1, "hello 😊 world 🌍🚀"))
 | 
						|
	b.Run("Zero", run(0, "Some text"))
 | 
						|
	b.Run("Short", run(10, "Short Text"))
 | 
						|
	b.Run("ASCII", run(5, "Hello, World!"))
 | 
						|
	b.Run("ValidUTF-8", run(10, "hello 😊 world 🌍🚀"))
 | 
						|
	b.Run("InvalidUTF-8", run(6, "€"[0:2]+"hello€€"))
 | 
						|
	b.Run("MixedUTF-8", run(14, "\x80😊\x80 Hello\x80World🌍\x80🚀\x80"))
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanConcurrentSafe(t *testing.T) {
 | 
						|
	t.Parallel()
 | 
						|
 | 
						|
	const (
 | 
						|
		nTracers   = 2
 | 
						|
		nSpans     = 2
 | 
						|
		nGoroutine = 10
 | 
						|
	)
 | 
						|
 | 
						|
	runSpan := func(s Span) <-chan struct{} {
 | 
						|
		done := make(chan struct{})
 | 
						|
		go func(span Span) {
 | 
						|
			defer close(done)
 | 
						|
 | 
						|
			var wg sync.WaitGroup
 | 
						|
			for i := 0; i < nGoroutine; i++ {
 | 
						|
				wg.Add(1)
 | 
						|
				go func(n int) {
 | 
						|
					defer wg.Done()
 | 
						|
 | 
						|
					_ = span.IsRecording()
 | 
						|
					_ = span.SpanContext()
 | 
						|
					_ = span.TracerProvider()
 | 
						|
 | 
						|
					span.AddEvent("event")
 | 
						|
					span.AddLink(Link{})
 | 
						|
					span.RecordError(errors.New("err"))
 | 
						|
					span.SetStatus(codes.Error, "error")
 | 
						|
					span.SetName("span" + strconv.Itoa(n))
 | 
						|
					span.SetAttributes(attribute.Bool("key", true))
 | 
						|
 | 
						|
					span.End()
 | 
						|
				}(i)
 | 
						|
			}
 | 
						|
 | 
						|
			wg.Wait()
 | 
						|
		}(s)
 | 
						|
		return done
 | 
						|
	}
 | 
						|
 | 
						|
	runTracer := func(tr Tracer) <-chan struct{} {
 | 
						|
		done := make(chan struct{})
 | 
						|
		go func(tracer Tracer) {
 | 
						|
			defer close(done)
 | 
						|
 | 
						|
			ctx := context.Background()
 | 
						|
 | 
						|
			var wg sync.WaitGroup
 | 
						|
			for i := 0; i < nSpans; i++ {
 | 
						|
				wg.Add(1)
 | 
						|
				go func(n int) {
 | 
						|
					defer wg.Done()
 | 
						|
					_, s := tracer.Start(ctx, "span"+strconv.Itoa(n))
 | 
						|
					<-runSpan(s)
 | 
						|
				}(i)
 | 
						|
			}
 | 
						|
 | 
						|
			wg.Wait()
 | 
						|
		}(tr)
 | 
						|
		return done
 | 
						|
	}
 | 
						|
 | 
						|
	run := func(tp TracerProvider) <-chan struct{} {
 | 
						|
		done := make(chan struct{})
 | 
						|
		go func(provider TracerProvider) {
 | 
						|
			defer close(done)
 | 
						|
 | 
						|
			var wg sync.WaitGroup
 | 
						|
			for i := 0; i < nTracers; i++ {
 | 
						|
				wg.Add(1)
 | 
						|
				go func(n int) {
 | 
						|
					defer wg.Done()
 | 
						|
					<-runTracer(provider.Tracer("tracer" + strconv.Itoa(n)))
 | 
						|
				}(i)
 | 
						|
			}
 | 
						|
 | 
						|
			wg.Wait()
 | 
						|
		}(tp)
 | 
						|
		return done
 | 
						|
	}
 | 
						|
 | 
						|
	assert.NotPanics(t, func() {
 | 
						|
		done0, done1 := run(newAutoTracerProvider()), run(newAutoTracerProvider())
 | 
						|
 | 
						|
		<-done0
 | 
						|
		<-done1
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func TestSpanLimit(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name string
 | 
						|
		get  func(spanLimits) int
 | 
						|
		zero int
 | 
						|
		keys []string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "AttributeValueLengthLimit",
 | 
						|
			get:  func(sl spanLimits) int { return sl.AttrValueLen },
 | 
						|
			zero: -1,
 | 
						|
			keys: []string{
 | 
						|
				"OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT",
 | 
						|
				"OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "AttributeCountLimit",
 | 
						|
			get:  func(sl spanLimits) int { return sl.Attrs },
 | 
						|
			zero: 128,
 | 
						|
			keys: []string{
 | 
						|
				"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT",
 | 
						|
				"OTEL_ATTRIBUTE_COUNT_LIMIT",
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "EventCountLimit",
 | 
						|
			get:  func(sl spanLimits) int { return sl.Events },
 | 
						|
			zero: 128,
 | 
						|
			keys: []string{"OTEL_SPAN_EVENT_COUNT_LIMIT"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "EventAttributeCountLimit",
 | 
						|
			get:  func(sl spanLimits) int { return sl.EventAttrs },
 | 
						|
			zero: 128,
 | 
						|
			keys: []string{"OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "LinkCountLimit",
 | 
						|
			get:  func(sl spanLimits) int { return sl.Links },
 | 
						|
			zero: 128,
 | 
						|
			keys: []string{"OTEL_SPAN_LINK_COUNT_LIMIT"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "LinkAttributeCountLimit",
 | 
						|
			get:  func(sl spanLimits) int { return sl.LinkAttrs },
 | 
						|
			zero: 128,
 | 
						|
			keys: []string{"OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, test := range tests {
 | 
						|
		t.Run(test.name, func(t *testing.T) {
 | 
						|
			t.Run("Default", func(t *testing.T) {
 | 
						|
				assert.Equal(t, test.zero, test.get(newSpanLimits()))
 | 
						|
			})
 | 
						|
 | 
						|
			t.Run("ValidValue", func(t *testing.T) {
 | 
						|
				for _, key := range test.keys {
 | 
						|
					t.Run(key, func(t *testing.T) {
 | 
						|
						t.Setenv(key, "43")
 | 
						|
						assert.Equal(t, 43, test.get(newSpanLimits()))
 | 
						|
					})
 | 
						|
				}
 | 
						|
			})
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |