mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-26 21:05:00 +02:00
99f830ba01
This moves the `Span` (along with the `SpanKind` and `Link`), `Tracer`, and `TracerProvider` out of the single trace.go file and into their own files. This change is intended to help developers find these types when looking at the directory and to break up the long trace.go file.
574 lines
14 KiB
Go
574 lines
14 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package trace
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestSpanContextIsValid(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
tid TraceID
|
|
sid SpanID
|
|
want bool
|
|
}{
|
|
{
|
|
name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
|
|
tid: [16]byte{1},
|
|
sid: [8]byte{42},
|
|
want: true,
|
|
}, {
|
|
name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
|
|
tid: TraceID([16]byte{}),
|
|
sid: [8]byte{},
|
|
want: false,
|
|
}, {
|
|
name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
|
|
tid: TraceID([16]byte{}),
|
|
sid: [8]byte{42},
|
|
want: false,
|
|
}, {
|
|
name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
|
|
tid: TraceID([16]byte{1}),
|
|
sid: [8]byte{},
|
|
want: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
sc := SpanContext{
|
|
traceID: testcase.tid,
|
|
spanID: testcase.sid,
|
|
}
|
|
have := sc.IsValid()
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanContextEqual(t *testing.T) {
|
|
a := SpanContext{
|
|
traceID: [16]byte{1},
|
|
spanID: [8]byte{42},
|
|
}
|
|
|
|
b := SpanContext{
|
|
traceID: [16]byte{1},
|
|
spanID: [8]byte{42},
|
|
}
|
|
|
|
c := SpanContext{
|
|
traceID: [16]byte{2},
|
|
spanID: [8]byte{42},
|
|
}
|
|
|
|
if !a.Equal(b) {
|
|
t.Error("Want: true, but have: false")
|
|
}
|
|
|
|
if a.Equal(c) {
|
|
t.Error("Want: false, but have: true")
|
|
}
|
|
}
|
|
|
|
func TestSpanContextIsSampled(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
tf TraceFlags
|
|
want bool
|
|
}{
|
|
{
|
|
name: "SpanContext.IsSampled() returns false if sc is not sampled",
|
|
want: false,
|
|
}, {
|
|
name: "SpanContext.IsSampled() returns true if sc is sampled",
|
|
tf: FlagsSampled,
|
|
want: true,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
sc := SpanContext{
|
|
traceFlags: testcase.tf,
|
|
}
|
|
|
|
have := sc.IsSampled()
|
|
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanContextIsRemote(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
remote bool
|
|
want bool
|
|
}{
|
|
{
|
|
name: "SpanContext.IsRemote() returns false if sc is not remote",
|
|
want: false,
|
|
}, {
|
|
name: "SpanContext.IsRemote() returns true if sc is remote",
|
|
remote: true,
|
|
want: true,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
sc := SpanContext{
|
|
remote: testcase.remote,
|
|
}
|
|
|
|
have := sc.IsRemote()
|
|
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanContextMarshalJSON(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
tid TraceID
|
|
sid SpanID
|
|
tstate TraceState
|
|
tflags TraceFlags
|
|
isRemote bool
|
|
want []byte
|
|
}{
|
|
{
|
|
name: "SpanContext.MarshalJSON() returns json with partial data",
|
|
tid: [16]byte{1},
|
|
sid: [8]byte{42},
|
|
want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"00","TraceState":"","Remote":false}`),
|
|
},
|
|
{
|
|
name: "SpanContext.MarshalJSON() returns json with full data",
|
|
tid: [16]byte{1},
|
|
sid: [8]byte{42},
|
|
tflags: FlagsSampled,
|
|
isRemote: true,
|
|
tstate: TraceState{list: []member{
|
|
{Key: "foo", Value: "1"},
|
|
}},
|
|
want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"01","TraceState":"foo=1","Remote":true}`),
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
sc := SpanContext{
|
|
traceID: testcase.tid,
|
|
spanID: testcase.sid,
|
|
traceFlags: testcase.tflags,
|
|
traceState: testcase.tstate,
|
|
remote: testcase.isRemote,
|
|
}
|
|
have, err := sc.MarshalJSON()
|
|
if err != nil {
|
|
t.Errorf("Marshaling failed: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(have, testcase.want) {
|
|
t.Errorf("Want: %v, but have: %v", string(testcase.want), string(have))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanIDFromHex(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
hex string
|
|
sid SpanID
|
|
valid bool
|
|
}{
|
|
{
|
|
name: "Valid SpanID",
|
|
sid: SpanID([8]byte{42}),
|
|
hex: "2a00000000000000",
|
|
valid: true,
|
|
}, {
|
|
name: "Invalid SpanID with invalid length",
|
|
hex: "80f198ee56343ba",
|
|
valid: false,
|
|
}, {
|
|
name: "Invalid SpanID with invalid char",
|
|
hex: "80f198ee563433g7",
|
|
valid: false,
|
|
}, {
|
|
name: "Invalid SpanID with uppercase",
|
|
hex: "80f198ee53ba86F7",
|
|
valid: false,
|
|
}, {
|
|
name: "Invalid SpanID with zero value",
|
|
hex: "0000000000000000",
|
|
valid: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
sid, err := SpanIDFromHex(testcase.hex)
|
|
|
|
if testcase.valid && err != nil {
|
|
t.Errorf("Expected SpanID %s to be valid but end with error %s", testcase.hex, err.Error())
|
|
} else if !testcase.valid && err == nil {
|
|
t.Errorf("Expected SpanID %s to be invalid but end no error", testcase.hex)
|
|
}
|
|
|
|
if sid != testcase.sid {
|
|
t.Errorf("Want: %v, but have: %v", testcase.sid, sid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsValidFromHex(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
hex string
|
|
tid TraceID
|
|
valid bool
|
|
}{
|
|
{
|
|
name: "Valid TraceID",
|
|
tid: TraceID([16]byte{128, 241, 152, 238, 86, 52, 59, 168, 100, 254, 139, 42, 87, 211, 239, 247}),
|
|
hex: "80f198ee56343ba864fe8b2a57d3eff7",
|
|
valid: true,
|
|
}, {
|
|
name: "Invalid TraceID with invalid length",
|
|
hex: "80f198ee56343ba864fe8b2a57d3eff",
|
|
valid: false,
|
|
}, {
|
|
name: "Invalid TraceID with invalid char",
|
|
hex: "80f198ee56343ba864fe8b2a57d3efg7",
|
|
valid: false,
|
|
}, {
|
|
name: "Invalid TraceID with uppercase",
|
|
hex: "80f198ee56343ba864fe8b2a57d3efF7",
|
|
valid: false,
|
|
}, {
|
|
name: "Invalid TraceID with zero value",
|
|
hex: "00000000000000000000000000000000",
|
|
valid: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
tid, err := TraceIDFromHex(testcase.hex)
|
|
|
|
if testcase.valid && err != nil {
|
|
t.Errorf("Expected TraceID %s to be valid but end with error %s", testcase.hex, err.Error())
|
|
}
|
|
|
|
if !testcase.valid && err == nil {
|
|
t.Errorf("Expected TraceID %s to be invalid but end no error", testcase.hex)
|
|
}
|
|
|
|
if tid != testcase.tid {
|
|
t.Errorf("Want: %v, but have: %v", testcase.tid, tid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanContextHasTraceID(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
tid TraceID
|
|
want bool
|
|
}{
|
|
{
|
|
name: "SpanContext.HasTraceID() returns true if both Low and High are nonzero",
|
|
tid: TraceID([16]byte{1}),
|
|
want: true,
|
|
}, {
|
|
name: "SpanContext.HasTraceID() returns false if neither Low nor High are nonzero",
|
|
tid: TraceID{},
|
|
want: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
// proto: func (sc SpanContext) HasTraceID() bool{}
|
|
sc := SpanContext{traceID: testcase.tid}
|
|
have := sc.HasTraceID()
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanContextHasSpanID(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
sc SpanContext
|
|
want bool
|
|
}{
|
|
{
|
|
name: "SpanContext.HasSpanID() returns true if self.SpanID != 0",
|
|
sc: SpanContext{spanID: [8]byte{42}},
|
|
want: true,
|
|
}, {
|
|
name: "SpanContext.HasSpanID() returns false if self.SpanID == 0",
|
|
sc: SpanContext{},
|
|
want: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
// proto: func (sc SpanContext) HasSpanID() bool {}
|
|
have := testcase.sc.HasSpanID()
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTraceFlagsIsSampled(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
tf TraceFlags
|
|
want bool
|
|
}{
|
|
{
|
|
name: "sampled",
|
|
tf: FlagsSampled,
|
|
want: true,
|
|
}, {
|
|
name: "unused bits are ignored, still not sampled",
|
|
tf: ^FlagsSampled,
|
|
want: false,
|
|
}, {
|
|
name: "unused bits are ignored, still sampled",
|
|
tf: FlagsSampled | ^FlagsSampled,
|
|
want: true,
|
|
}, {
|
|
name: "not sampled/default",
|
|
want: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
have := testcase.tf.IsSampled()
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTraceFlagsWithSampled(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
start TraceFlags
|
|
sample bool
|
|
want TraceFlags
|
|
}{
|
|
{
|
|
name: "sampled unchanged",
|
|
start: FlagsSampled,
|
|
want: FlagsSampled,
|
|
sample: true,
|
|
}, {
|
|
name: "become sampled",
|
|
want: FlagsSampled,
|
|
sample: true,
|
|
}, {
|
|
name: "unused bits are ignored, still not sampled",
|
|
start: ^FlagsSampled,
|
|
want: ^FlagsSampled,
|
|
sample: false,
|
|
}, {
|
|
name: "unused bits are ignored, becomes sampled",
|
|
start: ^FlagsSampled,
|
|
want: FlagsSampled | ^FlagsSampled,
|
|
sample: true,
|
|
}, {
|
|
name: "not sampled/default",
|
|
sample: false,
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
have := testcase.start.WithSampled(testcase.sample)
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %v, but have: %v", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStringTraceID(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
tid TraceID
|
|
want string
|
|
}{
|
|
{
|
|
name: "TraceID.String returns string representation of self.TraceID values > 0",
|
|
tid: TraceID([16]byte{255}),
|
|
want: "ff000000000000000000000000000000",
|
|
},
|
|
{
|
|
name: "TraceID.String returns string representation of self.TraceID values == 0",
|
|
tid: TraceID([16]byte{}),
|
|
want: "00000000000000000000000000000000",
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
// proto: func (t TraceID) String() string {}
|
|
have := testcase.tid.String()
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %s, but have: %s", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStringSpanID(t *testing.T) {
|
|
for _, testcase := range []struct {
|
|
name string
|
|
sid SpanID
|
|
want string
|
|
}{
|
|
{
|
|
name: "SpanID.String returns string representation of self.SpanID values > 0",
|
|
sid: SpanID([8]byte{255}),
|
|
want: "ff00000000000000",
|
|
},
|
|
{
|
|
name: "SpanID.String returns string representation of self.SpanID values == 0",
|
|
sid: SpanID([8]byte{}),
|
|
want: "0000000000000000",
|
|
},
|
|
} {
|
|
t.Run(testcase.name, func(t *testing.T) {
|
|
// proto: func (t TraceID) String() string {}
|
|
have := testcase.sid.String()
|
|
if have != testcase.want {
|
|
t.Errorf("Want: %s, but have: %s", testcase.want, have)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertSpanContextEqual(got SpanContext, want SpanContext) bool {
|
|
return got.spanID == want.spanID &&
|
|
got.traceID == want.traceID &&
|
|
got.traceFlags == want.traceFlags &&
|
|
got.remote == want.remote &&
|
|
got.traceState.String() == want.traceState.String()
|
|
}
|
|
|
|
func TestNewSpanContext(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
config SpanContextConfig
|
|
expectedSpanContext SpanContext
|
|
}{
|
|
{
|
|
name: "Complete SpanContext",
|
|
config: SpanContextConfig{
|
|
TraceID: TraceID([16]byte{1}),
|
|
SpanID: SpanID([8]byte{42}),
|
|
TraceFlags: 0x1,
|
|
TraceState: TraceState{list: []member{
|
|
{"foo", "bar"},
|
|
}},
|
|
},
|
|
expectedSpanContext: SpanContext{
|
|
traceID: TraceID([16]byte{1}),
|
|
spanID: SpanID([8]byte{42}),
|
|
traceFlags: 0x1,
|
|
traceState: TraceState{list: []member{
|
|
{"foo", "bar"},
|
|
}},
|
|
},
|
|
},
|
|
{
|
|
name: "Empty SpanContext",
|
|
config: SpanContextConfig{},
|
|
expectedSpanContext: SpanContext{},
|
|
},
|
|
{
|
|
name: "Partial SpanContext",
|
|
config: SpanContextConfig{
|
|
TraceID: TraceID([16]byte{1}),
|
|
SpanID: SpanID([8]byte{42}),
|
|
},
|
|
expectedSpanContext: SpanContext{
|
|
traceID: TraceID([16]byte{1}),
|
|
spanID: SpanID([8]byte{42}),
|
|
traceFlags: 0x0,
|
|
traceState: TraceState{},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sctx := NewSpanContext(tc.config)
|
|
if !assertSpanContextEqual(sctx, tc.expectedSpanContext) {
|
|
t.Fatalf("%s: Unexpected context created: %s", tc.name, cmp.Diff(sctx, tc.expectedSpanContext))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSpanContextDerivation(t *testing.T) {
|
|
from := SpanContext{}
|
|
to := SpanContext{traceID: TraceID([16]byte{1})}
|
|
|
|
modified := from.WithTraceID(to.TraceID())
|
|
if !assertSpanContextEqual(modified, to) {
|
|
t.Fatalf("WithTraceID: Unexpected context created: %s", cmp.Diff(modified, to))
|
|
}
|
|
|
|
from = to
|
|
to.spanID = SpanID([8]byte{42})
|
|
|
|
modified = from.WithSpanID(to.SpanID())
|
|
if !assertSpanContextEqual(modified, to) {
|
|
t.Fatalf("WithSpanID: Unexpected context created: %s", cmp.Diff(modified, to))
|
|
}
|
|
|
|
from = to
|
|
to.traceFlags = 0x13
|
|
|
|
modified = from.WithTraceFlags(to.TraceFlags())
|
|
if !assertSpanContextEqual(modified, to) {
|
|
t.Fatalf("WithTraceFlags: Unexpected context created: %s", cmp.Diff(modified, to))
|
|
}
|
|
|
|
from = to
|
|
to.traceState = TraceState{list: []member{{"foo", "bar"}}}
|
|
|
|
modified = from.WithTraceState(to.TraceState())
|
|
if !assertSpanContextEqual(modified, to) {
|
|
t.Fatalf("WithTraceState: Unexpected context created: %s", cmp.Diff(modified, to))
|
|
}
|
|
}
|
|
|
|
func TestConfigLinkMutability(t *testing.T) {
|
|
sc0 := NewSpanContext(SpanContextConfig{TraceID: [16]byte{1}})
|
|
sc1 := NewSpanContext(SpanContextConfig{TraceID: [16]byte{2}})
|
|
sc2 := NewSpanContext(SpanContextConfig{TraceID: [16]byte{3}})
|
|
l0 := Link{SpanContext: sc0}
|
|
l1 := Link{SpanContext: sc1}
|
|
l2 := Link{SpanContext: sc2}
|
|
|
|
links := []Link{l0, l1}
|
|
conf := NewSpanStartConfig(WithLinks(links...))
|
|
|
|
// Mutating passed arg should not change configured links.
|
|
links[0] = l2
|
|
|
|
want := SpanConfig{links: []Link{l0, l1}}
|
|
assert.Equal(t, want, conf)
|
|
}
|