blob: c2151367f1d371f0550674b43ea7d6abc751f730 [file] [log] [blame]
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package trace
import (
"context"
"fmt"
"reflect"
"sync/atomic"
"testing"
"time"
"go.opencensus.io/trace/tracestate"
)
var (
tid = TraceID{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 4, 8, 16, 32, 64, 128}
sid = SpanID{1, 2, 4, 8, 16, 32, 64, 128}
testTracestate, _ = tracestate.New(nil, tracestate.Entry{Key: "foo", Value: "bar"})
)
func init() {
// no random sampling, but sample children of sampled spans.
ApplyConfig(Config{DefaultSampler: ProbabilitySampler(0)})
}
func TestStrings(t *testing.T) {
if got, want := tid.String(), "01020304050607080102040810204080"; got != want {
t.Errorf("TraceID.String: got %q want %q", got, want)
}
if got, want := sid.String(), "0102040810204080"; got != want {
t.Errorf("SpanID.String: got %q want %q", got, want)
}
}
func TestFromContext(t *testing.T) {
want := &Span{}
ctx := NewContext(context.Background(), want)
got := FromContext(ctx)
if got != want {
t.Errorf("got Span pointer %p want %p", got, want)
}
}
type foo int
func (f foo) String() string {
return "foo"
}
// checkChild tests that c has fields set appropriately, given that it is a child span of p.
func checkChild(p SpanContext, c *Span) error {
if c == nil {
return fmt.Errorf("got nil child span, want non-nil")
}
if got, want := c.spanContext.TraceID, p.TraceID; got != want {
return fmt.Errorf("got child trace ID %s, want %s", got, want)
}
if childID, parentID := c.spanContext.SpanID, p.SpanID; childID == parentID {
return fmt.Errorf("got child span ID %s, parent span ID %s; want unequal IDs", childID, parentID)
}
if got, want := c.spanContext.TraceOptions, p.TraceOptions; got != want {
return fmt.Errorf("got child trace options %d, want %d", got, want)
}
if got, want := c.spanContext.Tracestate, p.Tracestate; got != want {
return fmt.Errorf("got child tracestate %v, want %v", got, want)
}
return nil
}
func TestStartSpan(t *testing.T) {
ctx, _ := StartSpan(context.Background(), "StartSpan")
if FromContext(ctx).data != nil {
t.Error("StartSpan: new span is recording events")
}
}
func TestSampling(t *testing.T) {
for _, test := range []struct {
remoteParent bool
localParent bool
parentTraceOptions TraceOptions
sampler Sampler
wantTraceOptions TraceOptions
}{
{true, false, 0, nil, 0},
{true, false, 1, nil, 1},
{true, false, 0, NeverSample(), 0},
{true, false, 1, NeverSample(), 0},
{true, false, 0, AlwaysSample(), 1},
{true, false, 1, AlwaysSample(), 1},
{false, true, 0, NeverSample(), 0},
{false, true, 1, NeverSample(), 0},
{false, true, 0, AlwaysSample(), 1},
{false, true, 1, AlwaysSample(), 1},
{false, false, 0, nil, 0},
{false, false, 0, NeverSample(), 0},
{false, false, 0, AlwaysSample(), 1},
} {
var ctx context.Context
if test.remoteParent {
sc := SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: test.parentTraceOptions,
}
ctx, _ = StartSpanWithRemoteParent(context.Background(), "foo", sc, WithSampler(test.sampler))
} else if test.localParent {
sampler := NeverSample()
if test.parentTraceOptions == 1 {
sampler = AlwaysSample()
}
ctx2, _ := StartSpan(context.Background(), "foo", WithSampler(sampler))
ctx, _ = StartSpan(ctx2, "foo", WithSampler(test.sampler))
} else {
ctx, _ = StartSpan(context.Background(), "foo", WithSampler(test.sampler))
}
sc := FromContext(ctx).SpanContext()
if (sc == SpanContext{}) {
t.Errorf("case %#v: starting new span: no span in context", test)
continue
}
if sc.SpanID == (SpanID{}) {
t.Errorf("case %#v: starting new span: got zero SpanID, want nonzero", test)
}
if sc.TraceOptions != test.wantTraceOptions {
t.Errorf("case %#v: starting new span: got TraceOptions %x, want %x", test, sc.TraceOptions, test.wantTraceOptions)
}
}
// Test that for children of local spans, the default sampler has no effect.
for _, test := range []struct {
parentTraceOptions TraceOptions
wantTraceOptions TraceOptions
}{
{0, 0},
{0, 0},
{1, 1},
{1, 1},
} {
for _, defaultSampler := range []Sampler{
NeverSample(),
AlwaysSample(),
ProbabilitySampler(0),
} {
ApplyConfig(Config{DefaultSampler: defaultSampler})
sampler := NeverSample()
if test.parentTraceOptions == 1 {
sampler = AlwaysSample()
}
ctx2, _ := StartSpan(context.Background(), "foo", WithSampler(sampler))
ctx, _ := StartSpan(ctx2, "foo")
sc := FromContext(ctx).SpanContext()
if (sc == SpanContext{}) {
t.Errorf("case %#v: starting new child of local span: no span in context", test)
continue
}
if sc.SpanID == (SpanID{}) {
t.Errorf("case %#v: starting new child of local span: got zero SpanID, want nonzero", test)
}
if sc.TraceOptions != test.wantTraceOptions {
t.Errorf("case %#v: starting new child of local span: got TraceOptions %x, want %x", test, sc.TraceOptions, test.wantTraceOptions)
}
}
}
ApplyConfig(Config{DefaultSampler: ProbabilitySampler(0)}) // reset the default sampler.
}
func TestProbabilitySampler(t *testing.T) {
exported := 0
for i := 0; i < 1000; i++ {
_, span := StartSpan(context.Background(), "foo", WithSampler(ProbabilitySampler(0.3)))
if span.SpanContext().IsSampled() {
exported++
}
}
if exported < 200 || exported > 400 {
t.Errorf("got %f%% exported spans, want approximately 30%%", float64(exported)*0.1)
}
}
func TestStartSpanWithRemoteParent(t *testing.T) {
sc := SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 0x0,
}
ctx, _ := StartSpanWithRemoteParent(context.Background(), "startSpanWithRemoteParent", sc)
if err := checkChild(sc, FromContext(ctx)); err != nil {
t.Error(err)
}
ctx, _ = StartSpanWithRemoteParent(context.Background(), "startSpanWithRemoteParent", sc)
if err := checkChild(sc, FromContext(ctx)); err != nil {
t.Error(err)
}
sc = SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 0x1,
Tracestate: testTracestate,
}
ctx, _ = StartSpanWithRemoteParent(context.Background(), "startSpanWithRemoteParent", sc)
if err := checkChild(sc, FromContext(ctx)); err != nil {
t.Error(err)
}
ctx, _ = StartSpanWithRemoteParent(context.Background(), "startSpanWithRemoteParent", sc)
if err := checkChild(sc, FromContext(ctx)); err != nil {
t.Error(err)
}
ctx2, _ := StartSpan(ctx, "StartSpan")
parent := FromContext(ctx).SpanContext()
if err := checkChild(parent, FromContext(ctx2)); err != nil {
t.Error(err)
}
}
// startSpan returns a context with a new Span that is recording events and will be exported.
func startSpan(o StartOptions) *Span {
_, span := StartSpanWithRemoteParent(context.Background(), "span0",
SpanContext{
TraceID: tid,
SpanID: sid,
TraceOptions: 1,
},
WithSampler(o.Sampler),
WithSpanKind(o.SpanKind),
)
return span
}
type testExporter struct {
spans []*SpanData
}
func (t *testExporter) ExportSpan(s *SpanData) {
t.spans = append(t.spans, s)
}
// endSpan ends the Span in the context and returns the exported SpanData.
//
// It also does some tests on the Span, and tests and clears some fields in the SpanData.
func endSpan(span *Span) (*SpanData, error) {
if !span.IsRecordingEvents() {
return nil, fmt.Errorf("IsRecordingEvents: got false, want true")
}
if !span.SpanContext().IsSampled() {
return nil, fmt.Errorf("IsSampled: got false, want true")
}
var te testExporter
RegisterExporter(&te)
span.End()
UnregisterExporter(&te)
if len(te.spans) != 1 {
return nil, fmt.Errorf("got exported spans %#v, want one span", te.spans)
}
got := te.spans[0]
if got.SpanContext.SpanID == (SpanID{}) {
return nil, fmt.Errorf("exporting span: expected nonzero SpanID")
}
got.SpanContext.SpanID = SpanID{}
if !checkTime(&got.StartTime) {
return nil, fmt.Errorf("exporting span: expected nonzero StartTime")
}
if !checkTime(&got.EndTime) {
return nil, fmt.Errorf("exporting span: expected nonzero EndTime")
}
return got, nil
}
// checkTime checks that a nonzero time was set in x, then clears it.
func checkTime(x *time.Time) bool {
if x.IsZero() {
return false
}
*x = time.Time{}
return true
}
func TestSpanKind(t *testing.T) {
tests := []struct {
name string
startOptions StartOptions
want *SpanData
}{
{
name: "zero StartOptions",
startOptions: StartOptions{},
want: &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
SpanKind: SpanKindUnspecified,
HasRemoteParent: true,
},
},
{
name: "client span",
startOptions: StartOptions{
SpanKind: SpanKindClient,
},
want: &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
SpanKind: SpanKindClient,
HasRemoteParent: true,
},
},
{
name: "server span",
startOptions: StartOptions{
SpanKind: SpanKindServer,
},
want: &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
SpanKind: SpanKindServer,
HasRemoteParent: true,
},
},
}
for _, tt := range tests {
span := startSpan(tt.startOptions)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("exporting span: got %#v want %#v", got, tt.want)
}
}
}
func TestSetSpanAttributes(t *testing.T) {
span := startSpan(StartOptions{})
span.AddAttributes(StringAttribute("key1", "value1"))
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: map[string]interface{}{"key1": "value1"},
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestSetSpanAttributesOverLimit(t *testing.T) {
cfg := Config{MaxAttributesPerSpan: 2}
ApplyConfig(cfg)
span := startSpan(StartOptions{})
span.AddAttributes(StringAttribute("key1", "value1"))
span.AddAttributes(StringAttribute("key2", "value2"))
span.AddAttributes(StringAttribute("key1", "value3")) // Replace key1.
span.AddAttributes(StringAttribute("key4", "value4")) // Remove key2 and add key4
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: map[string]interface{}{"key1": "value3", "key4": "value4"},
HasRemoteParent: true,
DroppedAttributeCount: 1,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestAnnotations(t *testing.T) {
span := startSpan(StartOptions{})
span.Annotatef([]Attribute{StringAttribute("key1", "value1")}, "%f", 1.5)
span.Annotate([]Attribute{StringAttribute("key2", "value2")}, "Annotate")
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
for i := range got.Annotations {
if !checkTime(&got.Annotations[i].Time) {
t.Error("exporting span: expected nonzero Annotation Time")
}
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Annotations: []Annotation{
{Message: "1.500000", Attributes: map[string]interface{}{"key1": "value1"}},
{Message: "Annotate", Attributes: map[string]interface{}{"key2": "value2"}},
},
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestAnnotationsOverLimit(t *testing.T) {
cfg := Config{MaxAnnotationEventsPerSpan: 2}
ApplyConfig(cfg)
span := startSpan(StartOptions{})
span.Annotatef([]Attribute{StringAttribute("key4", "value4")}, "%d", 1)
span.Annotate([]Attribute{StringAttribute("key3", "value3")}, "Annotate oldest")
span.Annotatef([]Attribute{StringAttribute("key1", "value1")}, "%f", 1.5)
span.Annotate([]Attribute{StringAttribute("key2", "value2")}, "Annotate")
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
for i := range got.Annotations {
if !checkTime(&got.Annotations[i].Time) {
t.Error("exporting span: expected nonzero Annotation Time")
}
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Annotations: []Annotation{
{Message: "1.500000", Attributes: map[string]interface{}{"key1": "value1"}},
{Message: "Annotate", Attributes: map[string]interface{}{"key2": "value2"}},
},
DroppedAnnotationCount: 2,
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestMessageEvents(t *testing.T) {
span := startSpan(StartOptions{})
span.AddMessageReceiveEvent(3, 400, 300)
span.AddMessageSendEvent(1, 200, 100)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
for i := range got.MessageEvents {
if !checkTime(&got.MessageEvents[i].Time) {
t.Error("exporting span: expected nonzero MessageEvent Time")
}
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
MessageEvents: []MessageEvent{
{EventType: 2, MessageID: 0x3, UncompressedByteSize: 0x190, CompressedByteSize: 0x12c},
{EventType: 1, MessageID: 0x1, UncompressedByteSize: 0xc8, CompressedByteSize: 0x64},
},
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestMessageEventsOverLimit(t *testing.T) {
cfg := Config{MaxMessageEventsPerSpan: 2}
ApplyConfig(cfg)
span := startSpan(StartOptions{})
span.AddMessageReceiveEvent(5, 300, 120)
span.AddMessageSendEvent(4, 100, 50)
span.AddMessageReceiveEvent(3, 400, 300)
span.AddMessageSendEvent(1, 200, 100)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
for i := range got.MessageEvents {
if !checkTime(&got.MessageEvents[i].Time) {
t.Error("exporting span: expected nonzero MessageEvent Time")
}
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
MessageEvents: []MessageEvent{
{EventType: 2, MessageID: 0x3, UncompressedByteSize: 0x190, CompressedByteSize: 0x12c},
{EventType: 1, MessageID: 0x1, UncompressedByteSize: 0xc8, CompressedByteSize: 0x64},
},
DroppedMessageEventCount: 2,
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestSetSpanName(t *testing.T) {
want := "SpanName-1"
span := startSpan(StartOptions{})
span.SetName(want)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
if got.Name != want {
t.Errorf("span.Name=%q; want %q", got.Name, want)
}
}
func TestSetSpanNameUnsampledSpan(t *testing.T) {
var nilSpanData *SpanData
span := startSpan(StartOptions{Sampler: NeverSample()})
span.SetName("NoopName")
if want, got := nilSpanData, span.data; want != got {
t.Errorf("span.data=%+v; want %+v", got, want)
}
}
func TestSetSpanNameAfterSpanEnd(t *testing.T) {
want := "SpanName-2"
span := startSpan(StartOptions{})
span.SetName(want)
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
// updating name after span.End
span.SetName("NoopName")
// exported span should not be updated by previous call to SetName
if got.Name != want {
t.Errorf("span.Name=%q; want %q", got.Name, want)
}
// span should not be exported again
var te testExporter
RegisterExporter(&te)
span.End()
UnregisterExporter(&te)
if len(te.spans) != 0 {
t.Errorf("got exported spans %#v, wanted no spans", te.spans)
}
}
func TestSetSpanStatus(t *testing.T) {
span := startSpan(StartOptions{})
span.SetStatus(Status{Code: int32(1), Message: "request failed"})
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Status: Status{Code: 1, Message: "request failed"},
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestAddLink(t *testing.T) {
span := startSpan(StartOptions{})
span.AddLink(Link{
TraceID: tid,
SpanID: sid,
Type: LinkTypeParent,
Attributes: map[string]interface{}{"key5": "value5"},
})
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Links: []Link{{
TraceID: tid,
SpanID: sid,
Type: 2,
Attributes: map[string]interface{}{"key5": "value5"},
}},
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestAddLinkOverLimit(t *testing.T) {
cfg := Config{MaxLinksPerSpan: 1}
ApplyConfig(cfg)
span := startSpan(StartOptions{})
span.AddLink(Link{
TraceID: tid,
SpanID: sid,
Type: LinkTypeParent,
Attributes: map[string]interface{}{"key4": "value4"},
})
span.AddLink(Link{
TraceID: tid,
SpanID: sid,
Type: LinkTypeParent,
Attributes: map[string]interface{}{"key5": "value5"},
})
got, err := endSpan(span)
if err != nil {
t.Fatal(err)
}
want := &SpanData{
SpanContext: SpanContext{
TraceID: tid,
SpanID: SpanID{},
TraceOptions: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Links: []Link{{
TraceID: tid,
SpanID: sid,
Type: 2,
Attributes: map[string]interface{}{"key5": "value5"},
}},
DroppedLinkCount: 1,
HasRemoteParent: true,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("exporting span: got %#v want %#v", got, want)
}
}
func TestUnregisterExporter(t *testing.T) {
var te testExporter
RegisterExporter(&te)
UnregisterExporter(&te)
ctx := startSpan(StartOptions{})
endSpan(ctx)
if len(te.spans) != 0 {
t.Error("unregistered Exporter was called")
}
}
func TestBucket(t *testing.T) {
// make a bucket of size 5 and add 10 spans
b := makeBucket(5)
for i := 1; i <= 10; i++ {
b.nextTime = time.Time{} // reset the time so that the next span is accepted.
// add a span, with i stored in the TraceID so we can test for it later.
b.add(&SpanData{SpanContext: SpanContext{TraceID: TraceID{byte(i)}}, EndTime: time.Now()})
if i <= 5 {
if b.size() != i {
t.Fatalf("got bucket size %d, want %d %#v\n", b.size(), i, b)
}
for j := 0; j < i; j++ {
if b.span(j).TraceID[0] != byte(j+1) {
t.Errorf("got span index %d, want %d\n", b.span(j).TraceID[0], j+1)
}
}
} else {
if b.size() != 5 {
t.Fatalf("got bucket size %d, want 5\n", b.size())
}
for j := 0; j < 5; j++ {
want := i - 4 + j
if b.span(j).TraceID[0] != byte(want) {
t.Errorf("got span index %d, want %d\n", b.span(j).TraceID[0], want)
}
}
}
}
// expand the bucket
b.resize(20)
if b.size() != 5 {
t.Fatalf("after resizing upwards: got bucket size %d, want 5\n", b.size())
}
for i := 0; i < 5; i++ {
want := 6 + i
if b.span(i).TraceID[0] != byte(want) {
t.Errorf("after resizing upwards: got span index %d, want %d\n", b.span(i).TraceID[0], want)
}
}
// shrink the bucket
b.resize(3)
if b.size() != 3 {
t.Fatalf("after resizing downwards: got bucket size %d, want 3\n", b.size())
}
for i := 0; i < 3; i++ {
want := 8 + i
if b.span(i).TraceID[0] != byte(want) {
t.Errorf("after resizing downwards: got span index %d, want %d\n", b.span(i).TraceID[0], want)
}
}
}
type exporter map[string]*SpanData
func (e exporter) ExportSpan(s *SpanData) {
e[s.Name] = s
}
func Test_Issue328_EndSpanTwice(t *testing.T) {
spans := make(exporter)
RegisterExporter(&spans)
defer UnregisterExporter(&spans)
ctx := context.Background()
ctx, span := StartSpan(ctx, "span-1", WithSampler(AlwaysSample()))
span.End()
span.End()
UnregisterExporter(&spans)
if len(spans) != 1 {
t.Fatalf("expected only a single span, got %#v", spans)
}
}
func TestStartSpanAfterEnd(t *testing.T) {
spans := make(exporter)
RegisterExporter(&spans)
defer UnregisterExporter(&spans)
ctx, span0 := StartSpan(context.Background(), "parent", WithSampler(AlwaysSample()))
ctx1, span1 := StartSpan(ctx, "span-1", WithSampler(AlwaysSample()))
span1.End()
// Start a new span with the context containing span-1
// even though span-1 is ended, we still add this as a new child of span-1
_, span2 := StartSpan(ctx1, "span-2", WithSampler(AlwaysSample()))
span2.End()
span0.End()
UnregisterExporter(&spans)
if got, want := len(spans), 3; got != want {
t.Fatalf("len(%#v) = %d; want %d", spans, got, want)
}
if got, want := spans["span-1"].TraceID, spans["parent"].TraceID; got != want {
t.Errorf("span-1.TraceID=%q; want %q", got, want)
}
if got, want := spans["span-2"].TraceID, spans["parent"].TraceID; got != want {
t.Errorf("span-2.TraceID=%q; want %q", got, want)
}
if got, want := spans["span-1"].ParentSpanID, spans["parent"].SpanID; got != want {
t.Errorf("span-1.ParentSpanID=%q; want %q (parent.SpanID)", got, want)
}
if got, want := spans["span-2"].ParentSpanID, spans["span-1"].SpanID; got != want {
t.Errorf("span-2.ParentSpanID=%q; want %q (span1.SpanID)", got, want)
}
}
func TestChildSpanCount(t *testing.T) {
spans := make(exporter)
RegisterExporter(&spans)
defer UnregisterExporter(&spans)
ctx, span0 := StartSpan(context.Background(), "parent", WithSampler(AlwaysSample()))
ctx1, span1 := StartSpan(ctx, "span-1", WithSampler(AlwaysSample()))
_, span2 := StartSpan(ctx1, "span-2", WithSampler(AlwaysSample()))
span2.End()
span1.End()
_, span3 := StartSpan(ctx, "span-3", WithSampler(AlwaysSample()))
span3.End()
span0.End()
UnregisterExporter(&spans)
if got, want := len(spans), 4; got != want {
t.Fatalf("len(%#v) = %d; want %d", spans, got, want)
}
if got, want := spans["span-3"].ChildSpanCount, 0; got != want {
t.Errorf("span-3.ChildSpanCount=%q; want %q", got, want)
}
if got, want := spans["span-2"].ChildSpanCount, 0; got != want {
t.Errorf("span-2.ChildSpanCount=%q; want %q", got, want)
}
if got, want := spans["span-1"].ChildSpanCount, 1; got != want {
t.Errorf("span-1.ChildSpanCount=%q; want %q", got, want)
}
if got, want := spans["parent"].ChildSpanCount, 2; got != want {
t.Errorf("parent.ChildSpanCount=%q; want %q", got, want)
}
}
func TestNilSpanEnd(t *testing.T) {
var span *Span
span.End()
}
func TestExecutionTracerTaskEnd(t *testing.T) {
var n uint64
executionTracerTaskEnd := func() {
atomic.AddUint64(&n, 1)
}
var spans []*Span
_, span := StartSpan(context.Background(), "foo", WithSampler(NeverSample()))
span.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, span) // never sample
_, span = StartSpanWithRemoteParent(context.Background(), "foo", SpanContext{
TraceID: TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
SpanID: SpanID{0, 1, 2, 3, 4, 5, 6, 7},
TraceOptions: 0,
})
span.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, span) // parent not sampled
_, span = StartSpan(context.Background(), "foo", WithSampler(AlwaysSample()))
span.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, span) // always sample
for _, span := range spans {
span.End()
}
if got, want := n, uint64(len(spans)); got != want {
t.Fatalf("Execution tracer task ended for %v spans; want %v", got, want)
}
}