package tracer import ( "errors" "testing" "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "github.com/stretchr/testify/assert" ) // newSpan creates a new span. This is a low-level function, required for testing and advanced usage. // Most of the time one should prefer the Tracer NewRootSpan or NewChildSpan methods. func newSpan(name, service, resource string, spanID, traceID, parentID uint64) *span { span := &span{ Name: name, Service: service, Resource: resource, Meta: map[string]string{}, Metrics: map[string]float64{}, SpanID: spanID, TraceID: traceID, ParentID: parentID, Start: now(), } span.context = newSpanContext(span, nil) return span } // newBasicSpan is the OpenTracing Span constructor func newBasicSpan(operationName string) *span { return newSpan(operationName, "", "", 0, 0, 0) } func TestSpanBaggage(t *testing.T) { assert := assert.New(t) span := newBasicSpan("web.request") span.SetBaggageItem("key", "value") assert.Equal("value", span.BaggageItem("key")) } func TestSpanContext(t *testing.T) { assert := assert.New(t) span := newBasicSpan("web.request") assert.NotNil(span.Context()) } func TestSpanOperationName(t *testing.T) { assert := assert.New(t) span := newBasicSpan("web.request") span.SetOperationName("http.request") assert.Equal("http.request", span.Name) } func TestSpanFinish(t *testing.T) { assert := assert.New(t) wait := time.Millisecond * 2 tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // the finish should set finished and the duration time.Sleep(wait) span.Finish() assert.True(span.Duration > int64(wait)) assert.True(span.finished) } func TestSpanFinishTwice(t *testing.T) { assert := assert.New(t) wait := time.Millisecond * 2 tracer, _, stop := startTestTracer() defer stop() assert.Equal(tracer.payload.itemCount(), 0) // the finish must be idempotent span := tracer.newRootSpan("pylons.request", "pylons", "/") time.Sleep(wait) span.Finish() assert.Equal(tracer.payload.itemCount(), 1) previousDuration := span.Duration time.Sleep(wait) span.Finish() assert.Equal(previousDuration, span.Duration) assert.Equal(tracer.payload.itemCount(), 1) } func TestSpanFinishWithTime(t *testing.T) { assert := assert.New(t) finishTime := time.Now().Add(10 * time.Second) span := newBasicSpan("web.request") span.Finish(FinishTime(finishTime)) duration := finishTime.UnixNano() - span.Start assert.Equal(duration, span.Duration) } func TestSpanFinishWithError(t *testing.T) { assert := assert.New(t) err := errors.New("test error") span := newBasicSpan("web.request") span.Finish(WithError(err)) assert.Equal(int32(1), span.Error) assert.Equal("test error", span.Meta[ext.ErrorMsg]) assert.Equal("*errors.errorString", span.Meta[ext.ErrorType]) assert.NotEmpty(span.Meta[ext.ErrorStack]) } func TestSpanSetTag(t *testing.T) { assert := assert.New(t) span := newBasicSpan("web.request") span.SetTag("component", "tracer") assert.Equal("tracer", span.Meta["component"]) span.SetTag("tagInt", 1234) assert.Equal(float64(1234), span.Metrics["tagInt"]) span.SetTag("tagStruct", struct{ A, B int }{1, 2}) assert.Equal("{1 2}", span.Meta["tagStruct"]) span.SetTag(ext.Error, true) assert.Equal(int32(1), span.Error) span.SetTag(ext.Error, nil) assert.Equal(int32(0), span.Error) span.SetTag(ext.Error, errors.New("abc")) assert.Equal(int32(1), span.Error) assert.Equal("abc", span.Meta[ext.ErrorMsg]) assert.Equal("*errors.errorString", span.Meta[ext.ErrorType]) assert.NotEmpty(span.Meta[ext.ErrorStack]) span.SetTag(ext.Error, "something else") assert.Equal(int32(1), span.Error) span.SetTag(ext.Error, false) assert.Equal(int32(0), span.Error) span.SetTag(ext.SamplingPriority, 2) assert.Equal(float64(2), span.Metrics[samplingPriorityKey]) } func TestSpanSetDatadogTags(t *testing.T) { assert := assert.New(t) span := newBasicSpan("web.request") span.SetTag(ext.SpanType, "http") span.SetTag(ext.ServiceName, "db-cluster") span.SetTag(ext.ResourceName, "SELECT * FROM users;") assert.Equal("http", span.Type) assert.Equal("db-cluster", span.Service) assert.Equal("SELECT * FROM users;", span.Resource) } func TestSpanStart(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // a new span sets the Start after the initialization assert.NotEqual(int64(0), span.Start) } func TestSpanString(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // don't bother checking the contents, just make sure it works. assert.NotEqual("", span.String()) span.Finish() assert.NotEqual("", span.String()) } func TestSpanSetMetric(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // check the map is properly initialized span.SetTag("bytes", 1024.42) assert.Equal(1, len(span.Metrics)) assert.Equal(1024.42, span.Metrics["bytes"]) // operating on a finished span is a no-op span.Finish() span.SetTag("finished.test", 1337) assert.Equal(1, len(span.Metrics)) assert.Equal(0.0, span.Metrics["finished.test"]) } func TestSpanError(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // check the error is set in the default meta err := errors.New("Something wrong") span.SetTag(ext.Error, err) assert.Equal(int32(1), span.Error) assert.Equal("Something wrong", span.Meta["error.msg"]) assert.Equal("*errors.errorString", span.Meta["error.type"]) assert.NotEqual("", span.Meta["error.stack"]) // operating on a finished span is a no-op span = tracer.newRootSpan("flask.request", "flask", "/") nMeta := len(span.Meta) span.Finish() span.SetTag(ext.Error, err) assert.Equal(int32(0), span.Error) assert.Equal(nMeta, len(span.Meta)) assert.Equal("", span.Meta["error.msg"]) assert.Equal("", span.Meta["error.type"]) assert.Equal("", span.Meta["error.stack"]) } func TestSpanError_Typed(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // check the error is set in the default meta err := &boomError{} span.SetTag(ext.Error, err) assert.Equal(int32(1), span.Error) assert.Equal("boom", span.Meta["error.msg"]) assert.Equal("*tracer.boomError", span.Meta["error.type"]) assert.NotEqual("", span.Meta["error.stack"]) } func TestSpanErrorNil(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("pylons.request", "pylons", "/") // don't set the error if it's nil nMeta := len(span.Meta) span.SetTag(ext.Error, nil) assert.Equal(int32(0), span.Error) assert.Equal(nMeta, len(span.Meta)) } // Prior to a bug fix, this failed when running `go test -race` func TestSpanModifyWhileFlushing(t *testing.T) { tracer, _, stop := startTestTracer() defer stop() done := make(chan struct{}) go func() { span := tracer.newRootSpan("pylons.request", "pylons", "/") span.Finish() // It doesn't make much sense to update the span after it's been finished, // but an error in a user's code could lead to this. span.SetTag("race_test", "true") span.SetTag("race_test2", 133.7) span.SetTag("race_test3", 133.7) span.SetTag(ext.Error, errors.New("t")) done <- struct{}{} }() for { select { case <-done: return default: tracer.forceFlush() } } } func TestSpanSamplingPriority(t *testing.T) { assert := assert.New(t) tracer := newTracer(withTransport(newDefaultTransport())) span := tracer.newRootSpan("my.name", "my.service", "my.resource") _, ok := span.Metrics[samplingPriorityKey] assert.False(ok) for _, priority := range []int{ ext.PriorityUserReject, ext.PriorityAutoReject, ext.PriorityAutoKeep, ext.PriorityUserKeep, 999, // not used, but we should allow it } { span.SetTag(ext.SamplingPriority, priority) v, ok := span.Metrics[samplingPriorityKey] assert.True(ok) assert.EqualValues(priority, v) assert.EqualValues(span.context.priority, v) assert.True(span.context.hasPriority) childSpan := tracer.newChildSpan("my.child", span) v0, ok0 := span.Metrics[samplingPriorityKey] v1, ok1 := childSpan.Metrics[samplingPriorityKey] assert.Equal(ok0, ok1) assert.Equal(v0, v1) assert.EqualValues(childSpan.context.priority, v0) assert.EqualValues(childSpan.context.hasPriority, ok0) } } func BenchmarkSetTagMetric(b *testing.B) { span := newBasicSpan("bench.span") keys := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" b.ResetTimer() for i := 0; i < b.N; i++ { k := string(keys[i%len(keys)]) span.SetTag(k, float64(12.34)) } } func BenchmarkSetTagString(b *testing.B) { span := newBasicSpan("bench.span") keys := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" b.ResetTimer() for i := 0; i < b.N; i++ { k := string(keys[i%len(keys)]) span.SetTag(k, "some text") } } func BenchmarkSetTagField(b *testing.B) { span := newBasicSpan("bench.span") keys := []string{ext.ServiceName, ext.ResourceName, ext.SpanType} b.ResetTimer() for i := 0; i < b.N; i++ { k := keys[i%len(keys)] span.SetTag(k, "some text") } } type boomError struct{} func (e *boomError) Error() string { return "boom" }