Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate oteltest.Harness for removal #2123

Merged
merged 6 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Deprecated

- The `TextMapCarrier` and `TextMapPropagator` from the `go.opentelemetry.io/otel/oteltest` package and their associated creation functions (`TextMapCarrier`, `NewTextMapPropagator`) are deprecated. (#2114)
- The `Harness` type from the `go.opentelemetry.io/otel/oteltest` package and its associated creation function, `NewHarness` are deprecated and will be removed in the next release. (#2123)

### Removed

Expand Down
341 changes: 341 additions & 0 deletions internal/internaltest/harness.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
// 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 internaltest // import "go.opentelemetry.io/otel/internal/internaltest"

import (
"context"
"fmt"
"sync"
"testing"
"time"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/internal/matchers"
"go.opentelemetry.io/otel/trace"
)

// Harness is a testing harness used to test implementations of the
// OpenTelemetry API.
type Harness struct {
t *testing.T
}

// NewHarness returns an instantiated *Harness using t.
func NewHarness(t *testing.T) *Harness {
return &Harness{
t: t,
}
}

// TestTracerProvider runs validation tests for an implementation of the OpenTelemetry
// TracerProvider API.
func (h *Harness) TestTracerProvider(subjectFactory func() trace.TracerProvider) {
h.t.Run("#Start", func(t *testing.T) {
t.Run("allow creating an arbitrary number of TracerProvider instances", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)

tp1 := subjectFactory()
tp2 := subjectFactory()

e.Expect(tp1).NotToEqual(tp2)
})
t.Run("all methods are safe to be called concurrently", func(t *testing.T) {
t.Parallel()

runner := func(tp trace.TracerProvider) <-chan struct{} {
done := make(chan struct{})
go func(tp trace.TracerProvider) {
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func(name, version string) {
_ = tp.Tracer(name, trace.WithInstrumentationVersion(version))
wg.Done()
}(fmt.Sprintf("tracer %d", i%5), fmt.Sprintf("%d", i))
}
wg.Wait()
done <- struct{}{}
}(tp)
return done
}

matchers.NewExpecter(t).Expect(func() {
// Run with multiple TracerProvider to ensure they encapsulate
// their own Tracers.
tp1 := subjectFactory()
tp2 := subjectFactory()

done1 := runner(tp1)
done2 := runner(tp2)

<-done1
<-done2
}).NotToPanic()
})
})
}

// TestTracer runs validation tests for an implementation of the OpenTelemetry
// Tracer API.
func (h *Harness) TestTracer(subjectFactory func() trace.Tracer) {
h.t.Run("#Start", func(t *testing.T) {
t.Run("propagates the original context", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

ctxKey := testCtxKey{}
ctxValue := "ctx value"
ctx := context.WithValue(context.Background(), ctxKey, ctxValue)

ctx, _ = subject.Start(ctx, "test")

e.Expect(ctx.Value(ctxKey)).ToEqual(ctxValue)
})

t.Run("returns a span containing the expected properties", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

_, span := subject.Start(context.Background(), "test")

e.Expect(span).NotToBeNil()

e.Expect(span.SpanContext().IsValid()).ToBeTrue()
})

t.Run("stores the span on the provided context", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

ctx, span := subject.Start(context.Background(), "test")

e.Expect(span).NotToBeNil()
e.Expect(span.SpanContext()).NotToEqual(trace.SpanContext{})
e.Expect(trace.SpanFromContext(ctx)).ToEqual(span)
})

t.Run("starts spans with unique trace and span IDs", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

_, span1 := subject.Start(context.Background(), "span1")
_, span2 := subject.Start(context.Background(), "span2")

sc1 := span1.SpanContext()
sc2 := span2.SpanContext()

e.Expect(sc1.TraceID()).NotToEqual(sc2.TraceID())
e.Expect(sc1.SpanID()).NotToEqual(sc2.SpanID())
})

t.Run("propagates a parent's trace ID through the context", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

ctx, parent := subject.Start(context.Background(), "parent")
_, child := subject.Start(ctx, "child")

psc := parent.SpanContext()
csc := child.SpanContext()

e.Expect(csc.TraceID()).ToEqual(psc.TraceID())
e.Expect(csc.SpanID()).NotToEqual(psc.SpanID())
})

t.Run("ignores parent's trace ID when new root is requested", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

ctx, parent := subject.Start(context.Background(), "parent")
_, child := subject.Start(ctx, "child", trace.WithNewRoot())

psc := parent.SpanContext()
csc := child.SpanContext()

e.Expect(csc.TraceID()).NotToEqual(psc.TraceID())
e.Expect(csc.SpanID()).NotToEqual(psc.SpanID())
})

t.Run("propagates remote parent's trace ID through the context", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

_, remoteParent := subject.Start(context.Background(), "remote parent")
parentCtx := trace.ContextWithRemoteSpanContext(context.Background(), remoteParent.SpanContext())
_, child := subject.Start(parentCtx, "child")

psc := remoteParent.SpanContext()
csc := child.SpanContext()

e.Expect(csc.TraceID()).ToEqual(psc.TraceID())
e.Expect(csc.SpanID()).NotToEqual(psc.SpanID())
})

t.Run("ignores remote parent's trace ID when new root is requested", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
subject := subjectFactory()

_, remoteParent := subject.Start(context.Background(), "remote parent")
parentCtx := trace.ContextWithRemoteSpanContext(context.Background(), remoteParent.SpanContext())
_, child := subject.Start(parentCtx, "child", trace.WithNewRoot())

psc := remoteParent.SpanContext()
csc := child.SpanContext()

e.Expect(csc.TraceID()).NotToEqual(psc.TraceID())
e.Expect(csc.SpanID()).NotToEqual(psc.SpanID())
})

t.Run("all methods are safe to be called concurrently", func(t *testing.T) {
t.Parallel()

e := matchers.NewExpecter(t)
tracer := subjectFactory()

ctx, parent := tracer.Start(context.Background(), "span")

runner := func(tp trace.Tracer) <-chan struct{} {
done := make(chan struct{})
go func(tp trace.Tracer) {
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func(name string) {
defer wg.Done()
_, child := tp.Start(ctx, name)

psc := parent.SpanContext()
csc := child.SpanContext()

e.Expect(csc.TraceID()).ToEqual(psc.TraceID())
e.Expect(csc.SpanID()).NotToEqual(psc.SpanID())
}(fmt.Sprintf("span %d", i))
}
wg.Wait()
done <- struct{}{}
}(tp)
return done
}

e.Expect(func() {
done := runner(tracer)

<-done
}).NotToPanic()
})
})

h.testSpan(subjectFactory)
}

func (h *Harness) testSpan(tracerFactory func() trace.Tracer) {
var methods = map[string]func(span trace.Span){
"#End": func(span trace.Span) {
span.End()
},
"#AddEvent": func(span trace.Span) {
span.AddEvent("test event")
},
"#AddEventWithTimestamp": func(span trace.Span) {
span.AddEvent("test event", trace.WithTimestamp(time.Now().Add(1*time.Second)))
},
"#SetStatus": func(span trace.Span) {
span.SetStatus(codes.Error, "internal")
},
"#SetName": func(span trace.Span) {
span.SetName("new name")
},
"#SetAttributes": func(span trace.Span) {
span.SetAttributes(attribute.String("key1", "value"), attribute.Int("key2", 123))
},
}
var mechanisms = map[string]func() trace.Span{
"Span created via Tracer#Start": func() trace.Span {
tracer := tracerFactory()
_, subject := tracer.Start(context.Background(), "test")

return subject
},
"Span created via span.TracerProvider()": func() trace.Span {
ctx, spanA := tracerFactory().Start(context.Background(), "span1")

_, spanB := spanA.TracerProvider().Tracer("second").Start(ctx, "span2")
return spanB
},
}

for mechanismName, mechanism := range mechanisms {
h.t.Run(mechanismName, func(t *testing.T) {
for methodName, method := range methods {
t.Run(methodName, func(t *testing.T) {
t.Run("is thread-safe", func(t *testing.T) {
t.Parallel()

span := mechanism()

wg := &sync.WaitGroup{}
wg.Add(2)

go func() {
defer wg.Done()

method(span)
}()

go func() {
defer wg.Done()

method(span)
}()

wg.Wait()
})
})
}

t.Run("#End", func(t *testing.T) {
t.Run("can be called multiple times", func(t *testing.T) {
t.Parallel()

span := mechanism()

span.End()
span.End()
})
})
})
}
}

type testCtxKey struct{}
4 changes: 4 additions & 0 deletions oteltest/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ import (

// Harness is a testing harness used to test implementations of the
// OpenTelemetry API.
//
// Deprecated: this will be removed in the next major release.
type Harness struct {
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
t *testing.T
}

// NewHarness returns an instantiated *Harness using t.
//
// Deprecated: this will be removed in the next major release.
func NewHarness(t *testing.T) *Harness {
return &Harness{
t: t,
Expand Down
3 changes: 2 additions & 1 deletion oteltest/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/internal/matchers"
"go.opentelemetry.io/otel/oteltest"
"go.opentelemetry.io/otel/trace"
Expand All @@ -31,7 +32,7 @@ import (
func TestTracer(t *testing.T) {
tp := oteltest.NewTracerProvider()

oteltest.NewHarness(t).TestTracer(func() func() trace.Tracer {
internaltest.NewHarness(t).TestTracer(func() func() trace.Tracer {
tp := oteltest.NewTracerProvider()
var i uint64
return func() trace.Tracer {
Expand Down
2 changes: 1 addition & 1 deletion sdk/trace/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func init() {
}

func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) {
harness := oteltest.NewHarness(t)
harness := ottest.NewHarness(t)

harness.TestTracerProvider(func() trace.TracerProvider {
return NewTracerProvider(WithSampler(TraceIDRatioBased(0)))
Expand Down