// Copyright The 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 oteltest_test import ( "context" "errors" "fmt" "sync" "testing" "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ottest "go.opentelemetry.io/otel/internal/internaltest" "go.opentelemetry.io/otel/internal/matchers" "go.opentelemetry.io/otel/oteltest" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" ) func TestSpan(t *testing.T) { t.Run("#End", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("ends the span", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() e.Expect(subject.Ended()).ToBeFalse() _, ok = subject.EndTime() e.Expect(ok).ToBeFalse() start := time.Now() subject.End() end := time.Now() e.Expect(subject.Ended()).ToBeTrue() endTime, ok := subject.EndTime() e.Expect(ok).ToBeTrue() e.Expect(endTime).ToBeTemporally(matchers.AfterOrSameTime, start) e.Expect(endTime).ToBeTemporally(matchers.BeforeOrSameTime, end) }) t.Run("only takes effect the first time it is called", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.End() expectedEndTime, ok := subject.EndTime() e.Expect(ok).ToBeTrue() subject.End() endTime, ok := subject.EndTime() e.Expect(ok).ToBeTrue() e.Expect(endTime).ToEqual(expectedEndTime) }) t.Run("uses the time from WithTimestamp", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() expectedEndTime := time.Now().AddDate(5, 0, 0) subject.End(trace.WithTimestamp(expectedEndTime)) e.Expect(subject.Ended()).ToBeTrue() endTime, ok := subject.EndTime() e.Expect(ok).ToBeTrue() e.Expect(endTime).ToEqual(expectedEndTime) }) }) t.Run("#RecordError", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("records an error", func(t *testing.T) { t.Parallel() scenarios := []struct { err error typ string msg string }{ { err: ottest.NewTestError("test error"), typ: "go.opentelemetry.io/otel/internal/internaltest.TestError", msg: "test error", }, { err: errors.New("test error 2"), typ: "*errors.errorString", msg: "test error 2", }, } for _, s := range scenarios { e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() testTime := time.Now() subject.RecordError(s.err, trace.WithTimestamp(testTime)) expectedEvents := []oteltest.Event{{ Timestamp: testTime, Name: semconv.ExceptionEventName, Attributes: map[attribute.Key]attribute.Value{ semconv.ExceptionTypeKey: attribute.StringValue(s.typ), semconv.ExceptionMessageKey: attribute.StringValue(s.msg), }, }} e.Expect(subject.Events()).ToEqual(expectedEvents) e.Expect(subject.StatusCode()).ToEqual(codes.Unset) e.Expect(subject.StatusMessage()).ToEqual("") } }) t.Run("cannot be set after the span has ended", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.End() subject.RecordError(errors.New("ignored error")) e.Expect(len(subject.Events())).ToEqual(0) }) t.Run("has no effect with nil error", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.RecordError(nil) e.Expect(len(subject.Events())).ToEqual(0) }) }) t.Run("#IsRecording", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns true", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, subject := tracer.Start(context.Background(), "test") e.Expect(subject.IsRecording()).ToBeTrue() }) }) t.Run("#SpanContext", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns a valid SpanContext", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, subject := tracer.Start(context.Background(), "test") e.Expect(subject.SpanContext().IsValid()).ToBeTrue() }) t.Run("returns a consistent value", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, subject := tracer.Start(context.Background(), "test") e.Expect(subject.SpanContext()).ToEqual(subject.SpanContext()) }) }) t.Run("#Name", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns the most recently set name on the span", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) originalName := "test" _, span := tracer.Start(context.Background(), originalName) subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() e.Expect(subject.Name()).ToEqual(originalName) subject.SetName("in-between") newName := "new name" subject.SetName(newName) e.Expect(subject.Name()).ToEqual(newName) }) t.Run("cannot be changed after the span has been ended", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) originalName := "test" _, span := tracer.Start(context.Background(), originalName) subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.End() subject.SetName("new name") e.Expect(subject.Name()).ToEqual(originalName) }) }) t.Run("#Attributes", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns an empty map by default", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() e.Expect(subject.Attributes()).ToEqual(map[attribute.Key]attribute.Value{}) }) t.Run("returns the most recently set attributes", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() attr1 := attribute.String("key1", "value1") attr2 := attribute.String("key2", "value2") attr3 := attribute.String("key3", "value3") unexpectedAttr := attr2.Key.String("unexpected") subject.SetAttributes(attr1, unexpectedAttr, attr3) subject.SetAttributes(attr2) attributes := subject.Attributes() e.Expect(attributes[attr1.Key]).ToEqual(attr1.Value) e.Expect(attributes[attr2.Key]).ToEqual(attr2.Value) e.Expect(attributes[attr3.Key]).ToEqual(attr3.Value) }) t.Run("cannot be changed after the span has been ended", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() expectedAttr := attribute.String("key", "value") subject.SetAttributes(expectedAttr) subject.End() unexpectedAttr := expectedAttr.Key.String("unexpected") subject.SetAttributes(unexpectedAttr) subject.End() attributes := subject.Attributes() e.Expect(attributes[expectedAttr.Key]).ToEqual(expectedAttr.Value) }) t.Run("can be used concurrently with setter", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() subject.SetAttributes(attribute.String("key", "value")) }() go func() { defer wg.Done() subject.Attributes() }() wg.Wait() }) }) t.Run("#Links", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns an empty map by default", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() e.Expect(len(subject.Links())).ToEqual(0) }) }) t.Run("#Events", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns an empty slice by default", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() e.Expect(len(subject.Events())).ToEqual(0) }) t.Run("returns all of the added events", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() event1Name := "event1" event1Attributes := []attribute.KeyValue{ attribute.String("event1Attr1", "foo"), attribute.String("event1Attr2", "bar"), } event1Start := time.Now() subject.AddEvent(event1Name, trace.WithAttributes(event1Attributes...)) event1End := time.Now() event2Timestamp := time.Now().AddDate(5, 0, 0) event2Name := "event1" event2Attributes := []attribute.KeyValue{ attribute.String("event2Attr", "abc"), } subject.AddEvent(event2Name, trace.WithTimestamp(event2Timestamp), trace.WithAttributes(event2Attributes...)) events := subject.Events() e.Expect(len(events)).ToEqual(2) event1 := events[0] e.Expect(event1.Timestamp).ToBeTemporally(matchers.AfterOrSameTime, event1Start) e.Expect(event1.Timestamp).ToBeTemporally(matchers.BeforeOrSameTime, event1End) e.Expect(event1.Name).ToEqual(event1Name) for _, attr := range event1Attributes { e.Expect(event1.Attributes[attr.Key]).ToEqual(attr.Value) } event2 := events[1] e.Expect(event2.Timestamp).ToEqual(event2Timestamp) e.Expect(event2.Name).ToEqual(event2Name) for _, attr := range event2Attributes { e.Expect(event2.Attributes[attr.Key]).ToEqual(attr.Value) } }) t.Run("cannot be changed after the span has been ended", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.AddEvent("test") e.Expect(len(subject.Events())).ToEqual(1) expectedEvent := subject.Events()[0] subject.End() subject.AddEvent("should not occur") e.Expect(len(subject.Events())).ToEqual(1) e.Expect(subject.Events()[0]).ToEqual(expectedEvent) }) }) t.Run("#Status", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("defaults to OK", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() e.Expect(subject.StatusCode()).ToEqual(codes.Unset) subject.End() e.Expect(subject.StatusCode()).ToEqual(codes.Unset) }) statuses := []codes.Code{ codes.Unset, codes.Error, codes.Ok, } for _, status := range statuses { t.Run("returns the most recently set status on the span", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.SetStatus(codes.Ok, "Ok") subject.SetStatus(status, "Yo!") e.Expect(subject.StatusCode()).ToEqual(status) e.Expect(subject.StatusMessage()).ToEqual("Yo!") }) t.Run("cannot be changed after the span has been ended", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test") subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() originalStatus := codes.Ok subject.SetStatus(originalStatus, "OK") subject.End() subject.SetStatus(status, fmt.Sprint(status)) e.Expect(subject.StatusCode()).ToEqual(originalStatus) e.Expect(subject.StatusMessage()).ToEqual("OK") }) } }) t.Run("#SpanKind", func(t *testing.T) { tp := oteltest.NewTracerProvider() t.Run("returns the value given at start", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) tracer := tp.Tracer(t.Name()) _, span := tracer.Start(context.Background(), "test", trace.WithSpanKind(trace.SpanKindConsumer)) subject, ok := span.(*oteltest.Span) e.Expect(ok).ToBeTrue() subject.End() e.Expect(subject.SpanKind()).ToEqual(trace.SpanKindConsumer) }) }) t.Run("can provide a valid TracerProvider", func(t *testing.T) { t.Parallel() e := matchers.NewExpecter(t) sr := new(oteltest.SpanRecorder) tracerA := oteltest.NewTracerProvider(oteltest.WithSpanRecorder(sr)).Tracer(t.Name()) ctx, spanA := tracerA.Start(context.Background(), "span1") e.Expect(len(sr.Started())).ToEqual(1) _, spanB := spanA.TracerProvider().Tracer("extracted").Start(ctx, "span2") spans := sr.Started() e.Expect(len(spans)).ToEqual(2) e.Expect(spans[0]).ToEqual(spanA) e.Expect(spans[1]).ToEqual(spanB) }) }