1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-12 10:04:29 +02:00
opentelemetry-go/sdk/trace/trace_test.go
Krzesimir Nowak 942713a02d
Replace span relationship with a potentially remote parent context (#451)
This PR removes the non-compliant ChildOf and FollowsFrom interfaces
and the Relation type, which were inherited from OpenTracing via the
initial prototype. Instead allow adding a span context to the go
context as a remote span context and use a simple algorithm for
figuring out an actual parent of the new span, which was proposed for
the OpenTelemetry specification.

Also add a way to ignore current span and remote span context in go
context, so we can force the tracer to create a new root span - a span
with a new trace ID.

That required some moderate changes in the opentracing bridge - first
reference with ChildOfRef reference type becomes a local parent, the
rest become links. This also fixes links handling in the meantime. The
downside of the approach proposed here is that we can only set the
remote parent when creating a span through the opentracing API.

Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
2020-02-04 08:55:03 -08:00

910 lines
26 KiB
Go

// Copyright 2019, OpenTelemetry 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"
"math"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/codes"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/testharness"
"go.opentelemetry.io/otel/api/trace"
apitrace "go.opentelemetry.io/otel/api/trace"
export "go.opentelemetry.io/otel/sdk/export/trace"
)
var (
tid core.TraceID
sid core.SpanID
)
func init() {
tid, _ = core.TraceIDFromHex("01020304050607080102040810203040")
sid, _ = core.SpanIDFromHex("0102040810203040")
}
func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) {
tp, err := NewProvider(WithConfig(Config{DefaultSampler: ProbabilitySampler(0)}))
if err != nil {
t.Fatalf("failed to create provider, err: %v\n", err)
}
harness := testharness.NewHarness(t)
subjectFactory := func() trace.Tracer {
return tp.Tracer("")
}
harness.TestTracer(subjectFactory)
}
type testExporter struct {
spans []*export.SpanData
}
func (t *testExporter) ExportSpan(ctx context.Context, d *export.SpanData) {
t.spans = append(t.spans, d)
}
func TestSetName(t *testing.T) {
samplerIsCalled := false
fooSampler := Sampler(func(p SamplingParameters) SamplingDecision {
samplerIsCalled = true
t.Logf("called sampler for name %q", p.Name)
return SamplingDecision{Sample: strings.HasPrefix(p.Name, "foo")}
})
tp, _ := NewProvider(WithConfig(Config{DefaultSampler: fooSampler}))
type testCase struct {
name string
newName string
sampledBefore bool
sampledAfter bool
}
for idx, tt := range []testCase{
{ // 0
name: "foobar",
newName: "foobaz",
sampledBefore: true,
sampledAfter: true,
},
{ // 1
name: "foobar",
newName: "barbaz",
sampledBefore: true,
sampledAfter: false,
},
{ // 2
name: "barbar",
newName: "barbaz",
sampledBefore: false,
sampledAfter: false,
},
{ // 3
name: "barbar",
newName: "foobar",
sampledBefore: false,
sampledAfter: true,
},
} {
span := startNamedSpan(tp, "SetName", tt.name)
if !samplerIsCalled {
t.Errorf("%d: the sampler was not even called during span creation", idx)
}
samplerIsCalled = false
if gotSampledBefore := span.SpanContext().IsSampled(); tt.sampledBefore != gotSampledBefore {
t.Errorf("%d: invalid sampling decision before rename, expected %v, got %v", idx, tt.sampledBefore, gotSampledBefore)
}
span.SetName(tt.newName)
if !samplerIsCalled {
t.Errorf("%d: the sampler was not even called during span rename", idx)
}
samplerIsCalled = false
if gotSampledAfter := span.SpanContext().IsSampled(); tt.sampledAfter != gotSampledAfter {
t.Errorf("%d: invalid sampling decision after rename, expected %v, got %v", idx, tt.sampledAfter, gotSampledAfter)
}
span.End()
}
}
func TestRecordingIsOff(t *testing.T) {
tp, _ := NewProvider()
_, span := tp.Tracer("Recording off").Start(context.Background(), "StartSpan")
defer span.End()
if span.IsRecording() == true {
t.Error("new span is recording events")
}
}
func TestSampling(t *testing.T) {
idg := defIDGenerator()
const total = 10000
for name, tc := range map[string]struct {
sampler Sampler
expect float64
parent bool
sampledParent bool
}{
// Span w/o a parent
"NeverSample": {sampler: NeverSample(), expect: 0},
"AlwaysSample": {sampler: AlwaysSample(), expect: 1.0},
"ProbabilitySampler_-1": {sampler: ProbabilitySampler(-1.0), expect: 0},
"ProbabilitySampler_.25": {sampler: ProbabilitySampler(0.25), expect: .25},
"ProbabilitySampler_.50": {sampler: ProbabilitySampler(0.50), expect: .5},
"ProbabilitySampler_.75": {sampler: ProbabilitySampler(0.75), expect: .75},
"ProbabilitySampler_2.0": {sampler: ProbabilitySampler(2.0), expect: 1},
// Spans with a parent that is *not* sampled act like spans w/o a parent
"UnsampledParentSpanWithProbabilitySampler_-1": {sampler: ProbabilitySampler(-1.0), expect: 0, parent: true},
"UnsampledParentSpanWithProbabilitySampler_.25": {sampler: ProbabilitySampler(.25), expect: .25, parent: true},
"UnsampledParentSpanWithProbabilitySampler_.50": {sampler: ProbabilitySampler(0.50), expect: .5, parent: true},
"UnsampledParentSpanWithProbabilitySampler_.75": {sampler: ProbabilitySampler(0.75), expect: .75, parent: true},
"UnsampledParentSpanWithProbabilitySampler_2.0": {sampler: ProbabilitySampler(2.0), expect: 1, parent: true},
// Spans with a parent that is sampled, will always sample, regardless of the probability
"SampledParentSpanWithProbabilitySampler_-1": {sampler: ProbabilitySampler(-1.0), expect: 1, parent: true, sampledParent: true},
"SampledParentSpanWithProbabilitySampler_.25": {sampler: ProbabilitySampler(.25), expect: 1, parent: true, sampledParent: true},
"SampledParentSpanWithProbabilitySampler_2.0": {sampler: ProbabilitySampler(2.0), expect: 1, parent: true, sampledParent: true},
// Spans with a sampled parent, but when using the NeverSample Sampler, aren't sampled
"SampledParentSpanWithNeverSample": {sampler: NeverSample(), expect: 0, parent: true, sampledParent: true},
} {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
p, err := NewProvider(WithConfig(Config{DefaultSampler: tc.sampler}))
if err != nil {
t.Fatal("unexpected error:", err)
}
tr := p.Tracer("test")
var sampled int
for i := 0; i < total; i++ {
ctx := context.Background()
if tc.parent {
psc := core.SpanContext{
TraceID: idg.NewTraceID(),
SpanID: idg.NewSpanID(),
}
if tc.sampledParent {
psc.TraceFlags = core.TraceFlagsSampled
}
ctx = apitrace.ContextWithRemoteSpanContext(ctx, psc)
}
_, span := tr.Start(ctx, "test")
if span.SpanContext().IsSampled() {
sampled++
}
}
tolerance := 0.0
got := float64(sampled) / float64(total)
if tc.expect > 0 && tc.expect < 1 {
// See https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval
const z = 4.75342 // This should succeed 99.9999% of the time
tolerance = z * math.Sqrt(got*(1-got)/total)
}
diff := math.Abs(got - tc.expect)
if diff > tolerance {
t.Errorf("got %f (diff: %f), expected %f (w/tolerance: %f)", got, diff, tc.expect, tolerance)
}
})
}
}
func TestStartSpanWithParent(t *testing.T) {
tp, _ := NewProvider()
tr := tp.Tracer("SpanWithParent")
ctx := context.Background()
sc1 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 0x0,
}
_, s1 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc1), "span1-unsampled-parent1")
if err := checkChild(sc1, s1); err != nil {
t.Error(err)
}
_, s2 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc1), "span2-unsampled-parent1")
if err := checkChild(sc1, s2); err != nil {
t.Error(err)
}
sc2 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 0x1,
//Tracestate: testTracestate,
}
_, s3 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc2), "span3-sampled-parent2")
if err := checkChild(sc2, s3); err != nil {
t.Error(err)
}
ctx2, s4 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc2), "span4-sampled-parent2")
if err := checkChild(sc2, s4); err != nil {
t.Error(err)
}
s4Sc := s4.SpanContext()
_, s5 := tr.Start(ctx2, "span5-implicit-childof-span4")
if err := checkChild(s4Sc, s5); err != nil {
t.Error(err)
}
}
func TestSetSpanAttributesOnStart(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
span := startSpan(tp,
"StartSpanAttribute",
apitrace.WithAttributes(key.String("key1", "value1")),
apitrace.WithAttributes(key.String("key2", "value2")),
)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: []core.KeyValue{
key.String("key1", "value1"),
key.String("key2", "value2"),
},
SpanKind: apitrace.SpanKindInternal,
HasRemoteParent: true,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("SetSpanAttributesOnStart: -got +want %s", diff)
}
}
func TestSetSpanAttributes(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
span := startSpan(tp, "SpanAttribute")
span.SetAttributes(key.New("key1").String("value1"))
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: []core.KeyValue{
key.String("key1", "value1"),
},
SpanKind: apitrace.SpanKindInternal,
HasRemoteParent: true,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("SetSpanAttributes: -got +want %s", diff)
}
}
func TestSetSpanAttributesOverLimit(t *testing.T) {
te := &testExporter{}
cfg := Config{MaxAttributesPerSpan: 2}
tp, _ := NewProvider(WithConfig(cfg), WithSyncer(te))
span := startSpan(tp, "SpanAttributesOverLimit")
span.SetAttributes(
key.Bool("key1", true),
key.String("key2", "value2"),
key.Bool("key1", false), // Replace key1.
key.Int64("key4", 4), // Remove key2 and add key4
)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Attributes: []core.KeyValue{
key.Bool("key1", false),
key.Int64("key4", 4),
},
SpanKind: apitrace.SpanKindInternal,
HasRemoteParent: true,
DroppedAttributeCount: 1,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff)
}
}
func TestEvents(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
span := startSpan(tp, "Events")
k1v1 := key.New("key1").String("value1")
k2v2 := key.Bool("key2", true)
k3v3 := key.Int64("key3", 3)
span.AddEvent(context.Background(), "foo", key.New("key1").String("value1"))
span.AddEvent(context.Background(), "bar",
key.Bool("key2", true),
key.Int64("key3", 3),
)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
for i := range got.MessageEvents {
if !checkTime(&got.MessageEvents[i].Time) {
t.Error("exporting span: expected nonzero Event Time")
}
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
HasRemoteParent: true,
MessageEvents: []export.Event{
{Name: "foo", Attributes: []core.KeyValue{k1v1}},
{Name: "bar", Attributes: []core.KeyValue{k2v2, k3v3}},
},
SpanKind: apitrace.SpanKindInternal,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("Message Events: -got +want %s", diff)
}
}
func TestEventsOverLimit(t *testing.T) {
te := &testExporter{}
cfg := Config{MaxEventsPerSpan: 2}
tp, _ := NewProvider(WithConfig(cfg), WithSyncer(te))
span := startSpan(tp, "EventsOverLimit")
k1v1 := key.New("key1").String("value1")
k2v2 := key.Bool("key2", false)
k3v3 := key.New("key3").String("value3")
span.AddEvent(context.Background(), "fooDrop", key.New("key1").String("value1"))
span.AddEvent(context.Background(), "barDrop",
key.Bool("key2", true),
key.New("key3").String("value3"),
)
span.AddEvent(context.Background(), "foo", key.New("key1").String("value1"))
span.AddEvent(context.Background(), "bar",
key.Bool("key2", false),
key.New("key3").String("value3"),
)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
for i := range got.MessageEvents {
if !checkTime(&got.MessageEvents[i].Time) {
t.Error("exporting span: expected nonzero Event Time")
}
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
MessageEvents: []export.Event{
{Name: "foo", Attributes: []core.KeyValue{k1v1}},
{Name: "bar", Attributes: []core.KeyValue{k2v2, k3v3}},
},
DroppedMessageEventCount: 2,
HasRemoteParent: true,
SpanKind: apitrace.SpanKindInternal,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("Message Event over limit: -got +want %s", diff)
}
}
func TestLinks(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
k1v1 := key.New("key1").String("value1")
k2v2 := key.New("key2").String("value2")
k3v3 := key.New("key3").String("value3")
sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
span := startSpan(tp, "Links",
apitrace.LinkedTo(sc1, key.New("key1").String("value1")),
apitrace.LinkedTo(sc2,
key.New("key2").String("value2"),
key.New("key3").String("value3"),
),
)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
HasRemoteParent: true,
Links: []apitrace.Link{
{SpanContext: sc1, Attributes: []core.KeyValue{k1v1}},
{SpanContext: sc2, Attributes: []core.KeyValue{k2v2, k3v3}},
},
SpanKind: apitrace.SpanKindInternal,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("Link: -got +want %s", diff)
}
}
func TestLinksOverLimit(t *testing.T) {
te := &testExporter{}
cfg := Config{MaxLinksPerSpan: 2}
sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
sc3 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
tp, _ := NewProvider(WithConfig(cfg), WithSyncer(te))
span := startSpan(tp, "LinksOverLimit",
apitrace.LinkedTo(sc1, key.New("key1").String("value1")),
apitrace.LinkedTo(sc2, key.New("key2").String("value2")),
apitrace.LinkedTo(sc3, key.New("key3").String("value3")),
)
k2v2 := key.New("key2").String("value2")
k3v3 := key.New("key3").String("value3")
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
Links: []apitrace.Link{
{SpanContext: sc2, Attributes: []core.KeyValue{k2v2}},
{SpanContext: sc3, Attributes: []core.KeyValue{k3v3}},
},
DroppedLinkCount: 1,
HasRemoteParent: true,
SpanKind: apitrace.SpanKindInternal,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("Link over limit: -got +want %s", diff)
}
}
func TestSetSpanName(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
ctx := context.Background()
want := "SpanName-1"
ctx = apitrace.ContextWithRemoteSpanContext(ctx, core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 1,
})
_, span := tp.Tracer("SetSpanName").Start(ctx, "SpanName-1")
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
if got.Name != want {
t.Errorf("span.Name: got %q; want %q", got.Name, want)
}
}
func TestSetSpanStatus(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
span := startSpan(tp, "SpanStatus")
span.SetStatus(codes.Canceled)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: core.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
SpanKind: apitrace.SpanKindInternal,
Status: codes.Canceled,
HasRemoteParent: true,
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("SetSpanStatus: -got +want %s", diff)
}
}
func cmpDiff(x, y interface{}) string {
return cmp.Diff(x, y, cmp.AllowUnexported(core.Value{}), cmp.AllowUnexported(export.Event{}))
}
func remoteSpanContext() core.SpanContext {
return core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 1,
}
}
// checkChild is test utility function that tests that c has fields set appropriately,
// given that it is a child span of p.
func checkChild(p core.SpanContext, apiSpan apitrace.Span) error {
s := apiSpan.(*span)
if s == nil {
return fmt.Errorf("got nil child span, want non-nil")
}
if got, want := s.spanContext.TraceIDString(), p.TraceIDString(); got != want {
return fmt.Errorf("got child trace ID %s, want %s", got, want)
}
if childID, parentID := s.spanContext.SpanIDString(), p.SpanIDString(); childID == parentID {
return fmt.Errorf("got child span ID %s, parent span ID %s; want unequal IDs", childID, parentID)
}
if got, want := s.spanContext.TraceFlags, p.TraceFlags; got != want {
return fmt.Errorf("got child trace options %d, want %d", got, want)
}
// TODO [rgheita] : Fix tracestate test
//if got, want := c.spanContext.Tracestate, p.Tracestate; got != want {
// return fmt.Errorf("got child tracestate %v, want %v", got, want)
//}
return nil
}
// startSpan starts a span with a name "span0". See startNamedSpan for
// details.
func startSpan(tp *Provider, trName string, args ...apitrace.StartOption) apitrace.Span {
return startNamedSpan(tp, trName, "span0", args...)
}
// startNamed Span is a test utility func that starts a span with a
// passed name and with remote span context as parent. The remote span
// context contains TraceFlags with sampled bit set. This allows the
// span to be automatically sampled.
func startNamedSpan(tp *Provider, trName, name string, args ...apitrace.StartOption) apitrace.Span {
ctx := context.Background()
ctx = apitrace.ContextWithRemoteSpanContext(ctx, remoteSpanContext())
args = append(args, apitrace.WithRecord())
_, span := tp.Tracer(trName).Start(
ctx,
name,
args...,
)
return span
}
// endSpan is a test utility function that ends the span in the context and
// returns the exported export.SpanData.
// It requires that span be sampled using one of these methods
// 1. Passing parent span context in context
// 2. Use WithSampler(AlwaysSample())
// 3. Configuring AlwaysSample() as default sampler
//
// It also does some basic tests on the span.
// It also clears spanID in the export.SpanData to make the comparison easier.
func endSpan(te *testExporter, span apitrace.Span) (*export.SpanData, error) {
if !span.IsRecording() {
return nil, fmt.Errorf("IsRecording: got false, want true")
}
if !span.SpanContext().IsSampled() {
return nil, fmt.Errorf("IsSampled: got false, want true")
}
span.End()
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.IsValid() {
return nil, fmt.Errorf("exporting span: expected nonzero SpanID")
}
got.SpanContext.SpanID = core.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
}
type fakeExporter map[string]*export.SpanData
func (f fakeExporter) ExportSpan(ctx context.Context, s *export.SpanData) {
f[s.Name] = s
}
func TestEndSpanTwice(t *testing.T) {
spans := make(fakeExporter)
tp, _ := NewProvider(WithSyncer(spans))
span := startSpan(tp, "EndSpanTwice")
span.End()
span.End()
if len(spans) != 1 {
t.Fatalf("expected only a single span, got %#v", spans)
}
}
func TestStartSpanAfterEnd(t *testing.T) {
spans := make(fakeExporter)
tp, _ := NewProvider(WithConfig(Config{DefaultSampler: AlwaysSample()}), WithSyncer(spans))
ctx := context.Background()
tr := tp.Tracer("SpanAfterEnd")
ctx, span0 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, remoteSpanContext()), "parent")
ctx1, span1 := tr.Start(ctx, "span-1")
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 := tr.Start(ctx1, "span-2")
span2.End()
span0.End()
if got, want := len(spans), 3; got != want {
t.Fatalf("len(%#v) = %d; want %d", spans, got, want)
}
gotParent, ok := spans["parent"]
if !ok {
t.Fatal("parent not recorded")
}
gotSpan1, ok := spans["span-1"]
if !ok {
t.Fatal("span-1 not recorded")
}
gotSpan2, ok := spans["span-2"]
if !ok {
t.Fatal("span-2 not recorded")
}
if got, want := gotSpan1.SpanContext.TraceID, gotParent.SpanContext.TraceID; got != want {
t.Errorf("span-1.TraceID=%q; want %q", got, want)
}
if got, want := gotSpan2.SpanContext.TraceID, gotParent.SpanContext.TraceID; got != want {
t.Errorf("span-2.TraceID=%q; want %q", got, want)
}
if got, want := gotSpan1.ParentSpanID, gotParent.SpanContext.SpanID; got != want {
t.Errorf("span-1.ParentSpanID=%q; want %q (parent.SpanID)", got, want)
}
if got, want := gotSpan2.ParentSpanID, gotSpan1.SpanContext.SpanID; got != want {
t.Errorf("span-2.ParentSpanID=%q; want %q (span1.SpanID)", got, want)
}
}
func TestChildSpanCount(t *testing.T) {
spans := make(fakeExporter)
tp, _ := NewProvider(WithConfig(Config{DefaultSampler: AlwaysSample()}), WithSyncer(spans))
tr := tp.Tracer("ChidSpanCount")
ctx, span0 := tr.Start(context.Background(), "parent")
ctx1, span1 := tr.Start(ctx, "span-1")
_, span2 := tr.Start(ctx1, "span-2")
span2.End()
span1.End()
_, span3 := tr.Start(ctx, "span-3")
span3.End()
span0.End()
if got, want := len(spans), 4; got != want {
t.Fatalf("len(%#v) = %d; want %d", spans, got, want)
}
gotParent, ok := spans["parent"]
if !ok {
t.Fatal("parent not recorded")
}
gotSpan1, ok := spans["span-1"]
if !ok {
t.Fatal("span-1 not recorded")
}
gotSpan2, ok := spans["span-2"]
if !ok {
t.Fatal("span-2 not recorded")
}
gotSpan3, ok := spans["span-3"]
if !ok {
t.Fatal("span-3 not recorded")
}
if got, want := gotSpan3.ChildSpanCount, 0; got != want {
t.Errorf("span-3.ChildSpanCount=%q; want %q", got, want)
}
if got, want := gotSpan2.ChildSpanCount, 0; got != want {
t.Errorf("span-2.ChildSpanCount=%q; want %q", got, want)
}
if got, want := gotSpan1.ChildSpanCount, 1; got != want {
t.Errorf("span-1.ChildSpanCount=%q; want %q", got, want)
}
if got, want := gotParent.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
tp, _ := NewProvider(WithConfig(Config{DefaultSampler: NeverSample()}))
tr := tp.Tracer("Execution Tracer Task End")
executionTracerTaskEnd := func() {
atomic.AddUint64(&n, 1)
}
var spans []*span
_, apiSpan := tr.Start(context.Background(), "foo")
s := apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, s) // never sample
tID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f")
sID, _ := core.SpanIDFromHex("0001020304050607")
ctx := context.Background()
ctx = apitrace.ContextWithRemoteSpanContext(ctx,
core.SpanContext{
TraceID: tID,
SpanID: sID,
TraceFlags: 0,
},
)
_, apiSpan = tr.Start(
ctx,
"foo",
)
s = apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, s) // parent not sampled
//tp.ApplyConfig(Config{DefaultSampler: AlwaysSample()})
_, apiSpan = tr.Start(context.Background(), "foo")
s = apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd
spans = append(spans, s) // 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)
}
}
func TestCustomStartEndTime(t *testing.T) {
var te testExporter
tp, _ := NewProvider(WithSyncer(&te), WithConfig(Config{DefaultSampler: AlwaysSample()}))
startTime := time.Date(2019, time.August, 27, 14, 42, 0, 0, time.UTC)
endTime := startTime.Add(time.Second * 20)
_, span := tp.Tracer("Custom Start and End time").Start(
context.Background(),
"testspan",
apitrace.WithStartTime(startTime),
)
span.End(apitrace.WithEndTime(endTime))
if len(te.spans) != 1 {
t.Fatalf("got exported spans %#v, want one span", te.spans)
}
got := te.spans[0]
if got.StartTime != startTime {
t.Errorf("expected start time to be %s, got %s", startTime, got.StartTime)
}
if got.EndTime != endTime {
t.Errorf("expected end time to be %s, got %s", endTime, got.EndTime)
}
}
func TestWithSpanKind(t *testing.T) {
var te testExporter
tp, _ := NewProvider(WithSyncer(&te), WithConfig(Config{DefaultSampler: AlwaysSample()}))
tr := tp.Tracer("withSpanKind")
_, span := tr.Start(context.Background(), "WithoutSpanKind")
spanData, err := endSpan(&te, span)
if err != nil {
t.Error(err.Error())
}
if spanData.SpanKind != apitrace.SpanKindInternal {
t.Errorf("Default value of Spankind should be Internal: got %+v, want %+v\n", spanData.SpanKind, apitrace.SpanKindInternal)
}
sks := []apitrace.SpanKind{
apitrace.SpanKindInternal,
apitrace.SpanKindServer,
apitrace.SpanKindClient,
apitrace.SpanKindProducer,
apitrace.SpanKindConsumer,
}
for _, sk := range sks {
te.spans = nil
_, span := tr.Start(context.Background(), fmt.Sprintf("SpanKind-%v", sk), apitrace.WithSpanKind(sk))
spanData, err := endSpan(&te, span)
if err != nil {
t.Error(err.Error())
}
if spanData.SpanKind != sk {
t.Errorf("WithSpanKind check: got %+v, want %+v\n", spanData.SpanKind, sks)
}
}
}