1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-01-18 03:22:12 +02:00

Decouple use of otel/internal/internaltest (#4424)

* Add internaltest templates

* Generate internaltest using gotmpl

* Generate the sdk/internal/internaltest pkg

* Use sdk/internal/internaltest in sdk module

* Generate exporters/jaeger/internal/internaltest pkg

* Use exporters/jaeger/internal/internaltest in jaeger exporter

* Generate the exporters/zipkin/internal/internaltest pkg

* Use local internaltest in zipkin exporter

* Fix import path name in trace test
This commit is contained in:
Tyler Yahn 2023-08-09 00:15:47 -07:00 committed by GitHub
parent e3ed198611
commit b7f634a4fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 4952 additions and 13 deletions

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/exporters/jaeger/internal/internaltest"
)
func TestNewRawExporterWithDefault(t *testing.T) {

View File

@ -0,0 +1,74 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/alignment.go.tmpl
// 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/exporters/jaeger/internal/internaltest"
/*
This file contains common utilities and objects to validate memory alignment
of Go types. The primary use of this functionality is intended to ensure
`struct` fields that need to be 64-bit aligned so they can be passed as
arguments to 64-bit atomic operations.
The common workflow is to define a slice of `FieldOffset` and pass them to the
`Aligned8Byte` function from within a `TestMain` function from a package's
tests. It is important to make this call from the `TestMain` function prior
to running the rest of the test suit as it can provide useful diagnostics
about field alignment instead of ambiguous nil pointer dereference and runtime
panic.
For more information:
https://github.com/open-telemetry/opentelemetry-go/issues/341
*/
import (
"fmt"
"io"
)
// FieldOffset is a preprocessor representation of a struct field alignment.
type FieldOffset struct {
// Name of the field.
Name string
// Offset of the field in bytes.
//
// To compute this at compile time use unsafe.Offsetof.
Offset uintptr
}
// Aligned8Byte returns if all fields are aligned modulo 8-bytes.
//
// Error messaging is printed to out for any field determined misaligned.
func Aligned8Byte(fields []FieldOffset, out io.Writer) bool {
misaligned := make([]FieldOffset, 0)
for _, f := range fields {
if f.Offset%8 != 0 {
misaligned = append(misaligned, f)
}
}
if len(misaligned) == 0 {
return true
}
fmt.Fprintln(out, "struct fields not aligned for 64-bit atomic operations:")
for _, f := range misaligned {
fmt.Fprintf(out, " %s: %d-byte offset\n", f.Name, f.Offset)
}
return false
}

View File

@ -0,0 +1,101 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env.go.tmpl
// 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/exporters/jaeger/internal/internaltest"
import (
"os"
)
type Env struct {
Name string
Value string
Exists bool
}
// EnvStore stores and recovers environment variables.
type EnvStore interface {
// Records the environment variable into the store.
Record(key string)
// Restore recovers the environment variables in the store.
Restore() error
}
var _ EnvStore = (*envStore)(nil)
type envStore struct {
store map[string]Env
}
func (s *envStore) add(env Env) {
s.store[env.Name] = env
}
func (s *envStore) Restore() error {
var err error
for _, v := range s.store {
if v.Exists {
err = os.Setenv(v.Name, v.Value)
} else {
err = os.Unsetenv(v.Name)
}
if err != nil {
return err
}
}
return nil
}
func (s *envStore) setEnv(key, value string) error {
s.Record(key)
err := os.Setenv(key, value)
if err != nil {
return err
}
return nil
}
func (s *envStore) Record(key string) {
originValue, exists := os.LookupEnv(key)
s.add(Env{
Name: key,
Value: originValue,
Exists: exists,
})
}
func NewEnvStore() EnvStore {
return newEnvStore()
}
func newEnvStore() *envStore {
return &envStore{store: make(map[string]Env)}
}
func SetEnvVariables(env map[string]string) (EnvStore, error) {
envStore := newEnvStore()
for k, v := range env {
err := envStore.setEnv(k, v)
if err != nil {
return nil, err
}
}
return envStore, nil
}

View File

@ -0,0 +1,237 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env_test.go.tmpl
// 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 (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type EnvStoreTestSuite struct {
suite.Suite
}
func (s *EnvStoreTestSuite) Test_add() {
envStore := newEnvStore()
e := Env{
Name: "name",
Value: "value",
Exists: true,
}
envStore.add(e)
envStore.add(e)
s.Assert().Len(envStore.store, 1)
}
func (s *EnvStoreTestSuite) TestRecord() {
testCases := []struct {
name string
env Env
expectedEnvStore *envStore
}{
{
name: "record exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "value",
Exists: true,
},
}},
},
{
name: "record exists env, but its value is empty",
env: Env{
Name: "name",
Value: "",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "",
Exists: true,
},
}},
},
{
name: "record not exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Exists: false,
},
}},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
if tc.env.Exists {
s.Assert().NoError(os.Setenv(tc.env.Name, tc.env.Value))
}
envStore := newEnvStore()
envStore.Record(tc.env.Name)
s.Assert().Equal(tc.expectedEnvStore, envStore)
if tc.env.Exists {
s.Assert().NoError(os.Unsetenv(tc.env.Name))
}
})
}
}
func (s *EnvStoreTestSuite) TestRestore() {
testCases := []struct {
name string
env Env
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvValue: "value",
expectedEnvExists: true,
},
{
name: "no exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvExists: false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
envStore.add(tc.env)
// Backup
backup := newEnvStore()
backup.Record(tc.env.Name)
s.Require().NoError(os.Unsetenv(tc.env.Name))
s.Assert().NoError(envStore.Restore())
v, exists := os.LookupEnv(tc.env.Name)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func (s *EnvStoreTestSuite) Test_setEnv() {
testCases := []struct {
name string
key string
value string
expectedEnvStore *envStore
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "normal",
key: "name",
value: "value",
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "other value",
Exists: true,
},
}},
expectedEnvValue: "value",
expectedEnvExists: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
// Backup
backup := newEnvStore()
backup.Record(tc.key)
s.Require().NoError(os.Setenv(tc.key, "other value"))
s.Assert().NoError(envStore.setEnv(tc.key, tc.value))
s.Assert().Equal(tc.expectedEnvStore, envStore)
v, exists := os.LookupEnv(tc.key)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func TestEnvStoreTestSuite(t *testing.T) {
suite.Run(t, new(EnvStoreTestSuite))
}
func TestSetEnvVariables(t *testing.T) {
envs := map[string]string{
"name1": "value1",
"name2": "value2",
}
// Backup
backup := newEnvStore()
for k := range envs {
backup.Record(k)
}
defer func() {
require.NoError(t, backup.Restore())
}()
store, err := SetEnvVariables(envs)
assert.NoError(t, err)
require.IsType(t, &envStore{}, store)
concreteStore := store.(*envStore)
assert.Len(t, concreteStore.store, 2)
assert.Equal(t, backup, concreteStore)
}

View File

@ -0,0 +1,30 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/errors.go.tmpl
// 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/exporters/jaeger/internal/internaltest"
type TestError string
var _ error = TestError("")
func NewTestError(s string) error {
return TestError(s)
}
func (e TestError) Error() string {
return string(e)
}

View File

@ -0,0 +1,25 @@
// 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/exporters/jaeger/internal/internaltest"
//go:generate gotmpl --body=../../../../internal/shared/internaltest/alignment.go.tmpl "--data={}" --out=alignment.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/env.go.tmpl "--data={}" --out=env.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/env_test.go.tmpl "--data={}" --out=env_test.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/errors.go.tmpl "--data={}" --out=errors.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/harness.go.tmpl "--data={}" --out=harness.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_carrier.go.tmpl "--data={}" --out=text_map_carrier.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_carrier_test.go.tmpl "--data={}" --out=text_map_carrier_test.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_propagator.go.tmpl "--data={}" --out=text_map_propagator.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_propagator_test.go.tmpl "--data={}" --out=text_map_propagator_test.go

View File

@ -0,0 +1,344 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/harness.go.tmpl
// 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/exporters/jaeger/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{}

View File

@ -0,0 +1,144 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier.go.tmpl
// 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/exporters/jaeger/internal/internaltest"
import (
"sync"
"testing"
"go.opentelemetry.io/otel/propagation"
)
// TextMapCarrier is a storage medium for a TextMapPropagator used in testing.
// The methods of a TextMapCarrier are concurrent safe.
type TextMapCarrier struct {
mtx sync.Mutex
gets []string
sets [][2]string
data map[string]string
}
var _ propagation.TextMapCarrier = (*TextMapCarrier)(nil)
// NewTextMapCarrier returns a new *TextMapCarrier populated with data.
func NewTextMapCarrier(data map[string]string) *TextMapCarrier {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
return &TextMapCarrier{data: copied}
}
// Keys returns the keys for which this carrier has a value.
func (c *TextMapCarrier) Keys() []string {
c.mtx.Lock()
defer c.mtx.Unlock()
result := make([]string, 0, len(c.data))
for k := range c.data {
result = append(result, k)
}
return result
}
// Get returns the value associated with the passed key.
func (c *TextMapCarrier) Get(key string) string {
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = append(c.gets, key)
return c.data[key]
}
// GotKey tests if c.Get has been called for key.
func (c *TextMapCarrier) GotKey(t *testing.T, key string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
for _, k := range c.gets {
if k == key {
return true
}
}
t.Errorf("TextMapCarrier.Get(%q) has not been called", key)
return false
}
// GotN tests if n calls to c.Get have been made.
func (c *TextMapCarrier) GotN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.gets) != n {
t.Errorf("TextMapCarrier.Get was called %d times, not %d", len(c.gets), n)
return false
}
return true
}
// Set stores the key-value pair.
func (c *TextMapCarrier) Set(key, value string) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.sets = append(c.sets, [2]string{key, value})
c.data[key] = value
}
// SetKeyValue tests if c.Set has been called for the key-value pair.
func (c *TextMapCarrier) SetKeyValue(t *testing.T, key, value string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
var vals []string
for _, pair := range c.sets {
if key == pair[0] {
if value == pair[1] {
return true
}
vals = append(vals, pair[1])
}
}
if len(vals) > 0 {
t.Errorf("TextMapCarrier.Set called with %q and %v values, but not %s", key, vals, value)
}
t.Errorf("TextMapCarrier.Set(%q,%q) has not been called", key, value)
return false
}
// SetN tests if n calls to c.Set have been made.
func (c *TextMapCarrier) SetN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.sets) != n {
t.Errorf("TextMapCarrier.Set was called %d times, not %d", len(c.sets), n)
return false
}
return true
}
// Reset zeros out the recording state and sets the carried values to data.
func (c *TextMapCarrier) Reset(data map[string]string) {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = nil
c.sets = nil
c.data = copied
}

View File

@ -0,0 +1,86 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier_test.go.tmpl
// 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 (
"reflect"
"testing"
)
var (
key, value = "test", "true"
)
func TestTextMapCarrierKeys(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
expected, actual := []string{key}, tmc.Keys()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("expected tmc.Keys() to be %v but it was %v", expected, actual)
}
}
func TestTextMapCarrierGet(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
if got := tmc.Get("empty"); got != "" {
t.Errorf("TextMapCarrier.Get returned %q for an empty key", got)
}
tmc.GotKey(t, "empty")
tmc.GotN(t, 1)
if got := tmc.Get(key); got != value {
t.Errorf("TextMapCarrier.Get(%q) returned %q, want %q", key, got, value)
}
tmc.GotKey(t, key)
tmc.GotN(t, 2)
}
func TestTextMapCarrierSet(t *testing.T) {
tmc := NewTextMapCarrier(nil)
tmc.SetN(t, 0)
tmc.Set(key, value)
if got, ok := tmc.data[key]; !ok {
t.Errorf("TextMapCarrier.Set(%q,%q) failed to store pair", key, value)
} else if got != value {
t.Errorf("TextMapCarrier.Set(%q,%q) stored (%q,%q), not (%q,%q)", key, value, key, got, key, value)
}
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
}
func TestTextMapCarrierReset(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
tmc.SetN(t, 0)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear initial data")
}
tmc.GotN(t, 1)
tmc.GotKey(t, key)
tmc.Set(key, value)
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear data")
}
}

View File

@ -0,0 +1,115 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator.go.tmpl
// 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/exporters/jaeger/internal/internaltest"
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"go.opentelemetry.io/otel/propagation"
)
type ctxKeyType string
type state struct {
Injections uint64
Extractions uint64
}
func newState(encoded string) state {
if encoded == "" {
return state{}
}
s0, s1, _ := strings.Cut(encoded, ",")
injects, _ := strconv.ParseUint(s0, 10, 64)
extracts, _ := strconv.ParseUint(s1, 10, 64)
return state{
Injections: injects,
Extractions: extracts,
}
}
func (s state) String() string {
return fmt.Sprintf("%d,%d", s.Injections, s.Extractions)
}
// TextMapPropagator is a propagation.TextMapPropagator used for testing.
type TextMapPropagator struct {
name string
ctxKey ctxKeyType
}
var _ propagation.TextMapPropagator = (*TextMapPropagator)(nil)
// NewTextMapPropagator returns a new TextMapPropagator for testing. It will
// use name as the key it injects into a TextMapCarrier when Inject is called.
func NewTextMapPropagator(name string) *TextMapPropagator {
return &TextMapPropagator{name: name, ctxKey: ctxKeyType(name)}
}
func (p *TextMapPropagator) stateFromContext(ctx context.Context) state {
if v := ctx.Value(p.ctxKey); v != nil {
if s, ok := v.(state); ok {
return s
}
}
return state{}
}
func (p *TextMapPropagator) stateFromCarrier(carrier propagation.TextMapCarrier) state {
return newState(carrier.Get(p.name))
}
// Inject sets cross-cutting concerns for p from ctx into carrier.
func (p *TextMapPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
s := p.stateFromContext(ctx)
s.Injections++
carrier.Set(p.name, s.String())
}
// InjectedN tests if p has made n injections to carrier.
func (p *TextMapPropagator) InjectedN(t *testing.T, carrier *TextMapCarrier, n int) bool {
if actual := p.stateFromCarrier(carrier).Injections; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} injected %d times, not %d", p.name, actual, n)
return false
}
return true
}
// Extract reads cross-cutting concerns for p from carrier into ctx.
func (p *TextMapPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
s := p.stateFromCarrier(carrier)
s.Extractions++
return context.WithValue(ctx, p.ctxKey, s)
}
// ExtractedN tests if p has made n extractions from the lineage of ctx.
// nolint (context is not first arg)
func (p *TextMapPropagator) ExtractedN(t *testing.T, ctx context.Context, n int) bool {
if actual := p.stateFromContext(ctx).Extractions; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} extracted %d time, not %d", p.name, actual, n)
return false
}
return true
}
// Fields returns the name of p as the key who's value is set with Inject.
func (p *TextMapPropagator) Fields() []string { return []string{p.name} }

View File

@ -0,0 +1,72 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator_test.go.tmpl
// 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 (
"context"
"testing"
)
func TestTextMapPropagatorInjectExtract(t *testing.T) {
name := "testing"
ctx := context.Background()
carrier := NewTextMapCarrier(map[string]string{name: value})
propagator := NewTextMapPropagator(name)
propagator.Inject(ctx, carrier)
// Carrier value overridden with state.
if carrier.SetKeyValue(t, name, "1,0") {
// Ensure nothing has been extracted yet.
propagator.ExtractedN(t, ctx, 0)
// Test the injection was counted.
propagator.InjectedN(t, carrier, 1)
}
ctx = propagator.Extract(ctx, carrier)
v := ctx.Value(ctxKeyType(name))
if v == nil {
t.Error("TextMapPropagator.Extract failed to extract state")
}
if s, ok := v.(state); !ok {
t.Error("TextMapPropagator.Extract did not extract proper state")
} else if s.Extractions != 1 {
t.Error("TextMapPropagator.Extract did not increment state.Extractions")
}
if carrier.GotKey(t, name) {
// Test the extraction was counted.
propagator.ExtractedN(t, ctx, 1)
// Ensure no additional injection was recorded.
propagator.InjectedN(t, carrier, 1)
}
}
func TestTextMapPropagatorFields(t *testing.T) {
name := "testing"
propagator := NewTextMapPropagator(name)
if got := propagator.Fields(); len(got) != 1 {
t.Errorf("TextMapPropagator.Fields returned %d fields, want 1", len(got))
} else if got[0] != name {
t.Errorf("TextMapPropagator.Fields returned %q, want %q", got[0], name)
}
}
func TestNewStateEmpty(t *testing.T) {
if want, got := (state{}), newState(""); got != want {
t.Errorf("newState(\"\") returned %v, want %v", got, want)
}
}

View File

@ -30,7 +30,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
gen "go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/exporters/jaeger/internal/internaltest"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/exporters/zipkin/internal/internaltest"
)
func TestEnvOrWithCollectorEndpointOptionsFromEnv(t *testing.T) {

View File

@ -0,0 +1,74 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/alignment.go.tmpl
// 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/exporters/zipkin/internal/internaltest"
/*
This file contains common utilities and objects to validate memory alignment
of Go types. The primary use of this functionality is intended to ensure
`struct` fields that need to be 64-bit aligned so they can be passed as
arguments to 64-bit atomic operations.
The common workflow is to define a slice of `FieldOffset` and pass them to the
`Aligned8Byte` function from within a `TestMain` function from a package's
tests. It is important to make this call from the `TestMain` function prior
to running the rest of the test suit as it can provide useful diagnostics
about field alignment instead of ambiguous nil pointer dereference and runtime
panic.
For more information:
https://github.com/open-telemetry/opentelemetry-go/issues/341
*/
import (
"fmt"
"io"
)
// FieldOffset is a preprocessor representation of a struct field alignment.
type FieldOffset struct {
// Name of the field.
Name string
// Offset of the field in bytes.
//
// To compute this at compile time use unsafe.Offsetof.
Offset uintptr
}
// Aligned8Byte returns if all fields are aligned modulo 8-bytes.
//
// Error messaging is printed to out for any field determined misaligned.
func Aligned8Byte(fields []FieldOffset, out io.Writer) bool {
misaligned := make([]FieldOffset, 0)
for _, f := range fields {
if f.Offset%8 != 0 {
misaligned = append(misaligned, f)
}
}
if len(misaligned) == 0 {
return true
}
fmt.Fprintln(out, "struct fields not aligned for 64-bit atomic operations:")
for _, f := range misaligned {
fmt.Fprintf(out, " %s: %d-byte offset\n", f.Name, f.Offset)
}
return false
}

View File

@ -0,0 +1,101 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env.go.tmpl
// 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/exporters/zipkin/internal/internaltest"
import (
"os"
)
type Env struct {
Name string
Value string
Exists bool
}
// EnvStore stores and recovers environment variables.
type EnvStore interface {
// Records the environment variable into the store.
Record(key string)
// Restore recovers the environment variables in the store.
Restore() error
}
var _ EnvStore = (*envStore)(nil)
type envStore struct {
store map[string]Env
}
func (s *envStore) add(env Env) {
s.store[env.Name] = env
}
func (s *envStore) Restore() error {
var err error
for _, v := range s.store {
if v.Exists {
err = os.Setenv(v.Name, v.Value)
} else {
err = os.Unsetenv(v.Name)
}
if err != nil {
return err
}
}
return nil
}
func (s *envStore) setEnv(key, value string) error {
s.Record(key)
err := os.Setenv(key, value)
if err != nil {
return err
}
return nil
}
func (s *envStore) Record(key string) {
originValue, exists := os.LookupEnv(key)
s.add(Env{
Name: key,
Value: originValue,
Exists: exists,
})
}
func NewEnvStore() EnvStore {
return newEnvStore()
}
func newEnvStore() *envStore {
return &envStore{store: make(map[string]Env)}
}
func SetEnvVariables(env map[string]string) (EnvStore, error) {
envStore := newEnvStore()
for k, v := range env {
err := envStore.setEnv(k, v)
if err != nil {
return nil, err
}
}
return envStore, nil
}

View File

@ -0,0 +1,237 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env_test.go.tmpl
// 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 (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type EnvStoreTestSuite struct {
suite.Suite
}
func (s *EnvStoreTestSuite) Test_add() {
envStore := newEnvStore()
e := Env{
Name: "name",
Value: "value",
Exists: true,
}
envStore.add(e)
envStore.add(e)
s.Assert().Len(envStore.store, 1)
}
func (s *EnvStoreTestSuite) TestRecord() {
testCases := []struct {
name string
env Env
expectedEnvStore *envStore
}{
{
name: "record exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "value",
Exists: true,
},
}},
},
{
name: "record exists env, but its value is empty",
env: Env{
Name: "name",
Value: "",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "",
Exists: true,
},
}},
},
{
name: "record not exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Exists: false,
},
}},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
if tc.env.Exists {
s.Assert().NoError(os.Setenv(tc.env.Name, tc.env.Value))
}
envStore := newEnvStore()
envStore.Record(tc.env.Name)
s.Assert().Equal(tc.expectedEnvStore, envStore)
if tc.env.Exists {
s.Assert().NoError(os.Unsetenv(tc.env.Name))
}
})
}
}
func (s *EnvStoreTestSuite) TestRestore() {
testCases := []struct {
name string
env Env
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvValue: "value",
expectedEnvExists: true,
},
{
name: "no exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvExists: false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
envStore.add(tc.env)
// Backup
backup := newEnvStore()
backup.Record(tc.env.Name)
s.Require().NoError(os.Unsetenv(tc.env.Name))
s.Assert().NoError(envStore.Restore())
v, exists := os.LookupEnv(tc.env.Name)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func (s *EnvStoreTestSuite) Test_setEnv() {
testCases := []struct {
name string
key string
value string
expectedEnvStore *envStore
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "normal",
key: "name",
value: "value",
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "other value",
Exists: true,
},
}},
expectedEnvValue: "value",
expectedEnvExists: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
// Backup
backup := newEnvStore()
backup.Record(tc.key)
s.Require().NoError(os.Setenv(tc.key, "other value"))
s.Assert().NoError(envStore.setEnv(tc.key, tc.value))
s.Assert().Equal(tc.expectedEnvStore, envStore)
v, exists := os.LookupEnv(tc.key)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func TestEnvStoreTestSuite(t *testing.T) {
suite.Run(t, new(EnvStoreTestSuite))
}
func TestSetEnvVariables(t *testing.T) {
envs := map[string]string{
"name1": "value1",
"name2": "value2",
}
// Backup
backup := newEnvStore()
for k := range envs {
backup.Record(k)
}
defer func() {
require.NoError(t, backup.Restore())
}()
store, err := SetEnvVariables(envs)
assert.NoError(t, err)
require.IsType(t, &envStore{}, store)
concreteStore := store.(*envStore)
assert.Len(t, concreteStore.store, 2)
assert.Equal(t, backup, concreteStore)
}

View File

@ -0,0 +1,30 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/errors.go.tmpl
// 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/exporters/zipkin/internal/internaltest"
type TestError string
var _ error = TestError("")
func NewTestError(s string) error {
return TestError(s)
}
func (e TestError) Error() string {
return string(e)
}

View File

@ -0,0 +1,25 @@
// 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/exporters/zipkin/internal/internaltest"
//go:generate gotmpl --body=../../../../internal/shared/internaltest/alignment.go.tmpl "--data={}" --out=alignment.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/env.go.tmpl "--data={}" --out=env.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/env_test.go.tmpl "--data={}" --out=env_test.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/errors.go.tmpl "--data={}" --out=errors.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/harness.go.tmpl "--data={}" --out=harness.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_carrier.go.tmpl "--data={}" --out=text_map_carrier.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_carrier_test.go.tmpl "--data={}" --out=text_map_carrier_test.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_propagator.go.tmpl "--data={}" --out=text_map_propagator.go
//go:generate gotmpl --body=../../../../internal/shared/internaltest/text_map_propagator_test.go.tmpl "--data={}" --out=text_map_propagator_test.go

View File

@ -0,0 +1,344 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/harness.go.tmpl
// 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/exporters/zipkin/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{}

View File

@ -0,0 +1,144 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier.go.tmpl
// 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/exporters/zipkin/internal/internaltest"
import (
"sync"
"testing"
"go.opentelemetry.io/otel/propagation"
)
// TextMapCarrier is a storage medium for a TextMapPropagator used in testing.
// The methods of a TextMapCarrier are concurrent safe.
type TextMapCarrier struct {
mtx sync.Mutex
gets []string
sets [][2]string
data map[string]string
}
var _ propagation.TextMapCarrier = (*TextMapCarrier)(nil)
// NewTextMapCarrier returns a new *TextMapCarrier populated with data.
func NewTextMapCarrier(data map[string]string) *TextMapCarrier {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
return &TextMapCarrier{data: copied}
}
// Keys returns the keys for which this carrier has a value.
func (c *TextMapCarrier) Keys() []string {
c.mtx.Lock()
defer c.mtx.Unlock()
result := make([]string, 0, len(c.data))
for k := range c.data {
result = append(result, k)
}
return result
}
// Get returns the value associated with the passed key.
func (c *TextMapCarrier) Get(key string) string {
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = append(c.gets, key)
return c.data[key]
}
// GotKey tests if c.Get has been called for key.
func (c *TextMapCarrier) GotKey(t *testing.T, key string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
for _, k := range c.gets {
if k == key {
return true
}
}
t.Errorf("TextMapCarrier.Get(%q) has not been called", key)
return false
}
// GotN tests if n calls to c.Get have been made.
func (c *TextMapCarrier) GotN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.gets) != n {
t.Errorf("TextMapCarrier.Get was called %d times, not %d", len(c.gets), n)
return false
}
return true
}
// Set stores the key-value pair.
func (c *TextMapCarrier) Set(key, value string) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.sets = append(c.sets, [2]string{key, value})
c.data[key] = value
}
// SetKeyValue tests if c.Set has been called for the key-value pair.
func (c *TextMapCarrier) SetKeyValue(t *testing.T, key, value string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
var vals []string
for _, pair := range c.sets {
if key == pair[0] {
if value == pair[1] {
return true
}
vals = append(vals, pair[1])
}
}
if len(vals) > 0 {
t.Errorf("TextMapCarrier.Set called with %q and %v values, but not %s", key, vals, value)
}
t.Errorf("TextMapCarrier.Set(%q,%q) has not been called", key, value)
return false
}
// SetN tests if n calls to c.Set have been made.
func (c *TextMapCarrier) SetN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.sets) != n {
t.Errorf("TextMapCarrier.Set was called %d times, not %d", len(c.sets), n)
return false
}
return true
}
// Reset zeros out the recording state and sets the carried values to data.
func (c *TextMapCarrier) Reset(data map[string]string) {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = nil
c.sets = nil
c.data = copied
}

View File

@ -0,0 +1,86 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier_test.go.tmpl
// 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 (
"reflect"
"testing"
)
var (
key, value = "test", "true"
)
func TestTextMapCarrierKeys(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
expected, actual := []string{key}, tmc.Keys()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("expected tmc.Keys() to be %v but it was %v", expected, actual)
}
}
func TestTextMapCarrierGet(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
if got := tmc.Get("empty"); got != "" {
t.Errorf("TextMapCarrier.Get returned %q for an empty key", got)
}
tmc.GotKey(t, "empty")
tmc.GotN(t, 1)
if got := tmc.Get(key); got != value {
t.Errorf("TextMapCarrier.Get(%q) returned %q, want %q", key, got, value)
}
tmc.GotKey(t, key)
tmc.GotN(t, 2)
}
func TestTextMapCarrierSet(t *testing.T) {
tmc := NewTextMapCarrier(nil)
tmc.SetN(t, 0)
tmc.Set(key, value)
if got, ok := tmc.data[key]; !ok {
t.Errorf("TextMapCarrier.Set(%q,%q) failed to store pair", key, value)
} else if got != value {
t.Errorf("TextMapCarrier.Set(%q,%q) stored (%q,%q), not (%q,%q)", key, value, key, got, key, value)
}
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
}
func TestTextMapCarrierReset(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
tmc.SetN(t, 0)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear initial data")
}
tmc.GotN(t, 1)
tmc.GotKey(t, key)
tmc.Set(key, value)
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear data")
}
}

View File

@ -0,0 +1,115 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator.go.tmpl
// 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/exporters/zipkin/internal/internaltest"
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"go.opentelemetry.io/otel/propagation"
)
type ctxKeyType string
type state struct {
Injections uint64
Extractions uint64
}
func newState(encoded string) state {
if encoded == "" {
return state{}
}
s0, s1, _ := strings.Cut(encoded, ",")
injects, _ := strconv.ParseUint(s0, 10, 64)
extracts, _ := strconv.ParseUint(s1, 10, 64)
return state{
Injections: injects,
Extractions: extracts,
}
}
func (s state) String() string {
return fmt.Sprintf("%d,%d", s.Injections, s.Extractions)
}
// TextMapPropagator is a propagation.TextMapPropagator used for testing.
type TextMapPropagator struct {
name string
ctxKey ctxKeyType
}
var _ propagation.TextMapPropagator = (*TextMapPropagator)(nil)
// NewTextMapPropagator returns a new TextMapPropagator for testing. It will
// use name as the key it injects into a TextMapCarrier when Inject is called.
func NewTextMapPropagator(name string) *TextMapPropagator {
return &TextMapPropagator{name: name, ctxKey: ctxKeyType(name)}
}
func (p *TextMapPropagator) stateFromContext(ctx context.Context) state {
if v := ctx.Value(p.ctxKey); v != nil {
if s, ok := v.(state); ok {
return s
}
}
return state{}
}
func (p *TextMapPropagator) stateFromCarrier(carrier propagation.TextMapCarrier) state {
return newState(carrier.Get(p.name))
}
// Inject sets cross-cutting concerns for p from ctx into carrier.
func (p *TextMapPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
s := p.stateFromContext(ctx)
s.Injections++
carrier.Set(p.name, s.String())
}
// InjectedN tests if p has made n injections to carrier.
func (p *TextMapPropagator) InjectedN(t *testing.T, carrier *TextMapCarrier, n int) bool {
if actual := p.stateFromCarrier(carrier).Injections; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} injected %d times, not %d", p.name, actual, n)
return false
}
return true
}
// Extract reads cross-cutting concerns for p from carrier into ctx.
func (p *TextMapPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
s := p.stateFromCarrier(carrier)
s.Extractions++
return context.WithValue(ctx, p.ctxKey, s)
}
// ExtractedN tests if p has made n extractions from the lineage of ctx.
// nolint (context is not first arg)
func (p *TextMapPropagator) ExtractedN(t *testing.T, ctx context.Context, n int) bool {
if actual := p.stateFromContext(ctx).Extractions; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} extracted %d time, not %d", p.name, actual, n)
return false
}
return true
}
// Fields returns the name of p as the key who's value is set with Inject.
func (p *TextMapPropagator) Fields() []string { return []string{p.name} }

View File

@ -0,0 +1,72 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator_test.go.tmpl
// 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 (
"context"
"testing"
)
func TestTextMapPropagatorInjectExtract(t *testing.T) {
name := "testing"
ctx := context.Background()
carrier := NewTextMapCarrier(map[string]string{name: value})
propagator := NewTextMapPropagator(name)
propagator.Inject(ctx, carrier)
// Carrier value overridden with state.
if carrier.SetKeyValue(t, name, "1,0") {
// Ensure nothing has been extracted yet.
propagator.ExtractedN(t, ctx, 0)
// Test the injection was counted.
propagator.InjectedN(t, carrier, 1)
}
ctx = propagator.Extract(ctx, carrier)
v := ctx.Value(ctxKeyType(name))
if v == nil {
t.Error("TextMapPropagator.Extract failed to extract state")
}
if s, ok := v.(state); !ok {
t.Error("TextMapPropagator.Extract did not extract proper state")
} else if s.Extractions != 1 {
t.Error("TextMapPropagator.Extract did not increment state.Extractions")
}
if carrier.GotKey(t, name) {
// Test the extraction was counted.
propagator.ExtractedN(t, ctx, 1)
// Ensure no additional injection was recorded.
propagator.InjectedN(t, carrier, 1)
}
}
func TestTextMapPropagatorFields(t *testing.T) {
name := "testing"
propagator := NewTextMapPropagator(name)
if got := propagator.Fields(); len(got) != 1 {
t.Errorf("TextMapPropagator.Fields returned %d fields, want 1", len(got))
} else if got[0] != name {
t.Errorf("TextMapPropagator.Fields returned %q, want %q", got[0], name)
}
}
func TestNewStateEmpty(t *testing.T) {
if want, got := (state{}), newState(""); got != want {
t.Errorf("newState(\"\") returned %v, want %v", got, want)
}
}

View File

@ -27,7 +27,7 @@ import (
"testing"
"time"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/exporters/zipkin/internal/internaltest"
"github.com/go-logr/logr/funcr"
zkmodel "github.com/openzipkin/zipkin-go/model"

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/alignment.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env_test.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/errors.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -0,0 +1,25 @@
// 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"
//go:generate gotmpl --body=../shared/internaltest/alignment.go.tmpl "--data={}" --out=alignment.go
//go:generate gotmpl --body=../shared/internaltest/env.go.tmpl "--data={}" --out=env.go
//go:generate gotmpl --body=../shared/internaltest/env_test.go.tmpl "--data={}" --out=env_test.go
//go:generate gotmpl --body=../shared/internaltest/errors.go.tmpl "--data={}" --out=errors.go
//go:generate gotmpl --body=../shared/internaltest/harness.go.tmpl "--data={}" --out=harness.go
//go:generate gotmpl --body=../shared/internaltest/text_map_carrier.go.tmpl "--data={}" --out=text_map_carrier.go
//go:generate gotmpl --body=../shared/internaltest/text_map_carrier_test.go.tmpl "--data={}" --out=text_map_carrier_test.go
//go:generate gotmpl --body=../shared/internaltest/text_map_propagator.go.tmpl "--data={}" --out=text_map_propagator.go
//go:generate gotmpl --body=../shared/internaltest/text_map_propagator_test.go.tmpl "--data={}" --out=text_map_propagator_test.go

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/harness.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier_test.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,3 +1,6 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator_test.go.tmpl
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -0,0 +1,74 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/alignment.go.tmpl
// 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
/*
This file contains common utilities and objects to validate memory alignment
of Go types. The primary use of this functionality is intended to ensure
`struct` fields that need to be 64-bit aligned so they can be passed as
arguments to 64-bit atomic operations.
The common workflow is to define a slice of `FieldOffset` and pass them to the
`Aligned8Byte` function from within a `TestMain` function from a package's
tests. It is important to make this call from the `TestMain` function prior
to running the rest of the test suit as it can provide useful diagnostics
about field alignment instead of ambiguous nil pointer dereference and runtime
panic.
For more information:
https://github.com/open-telemetry/opentelemetry-go/issues/341
*/
import (
"fmt"
"io"
)
// FieldOffset is a preprocessor representation of a struct field alignment.
type FieldOffset struct {
// Name of the field.
Name string
// Offset of the field in bytes.
//
// To compute this at compile time use unsafe.Offsetof.
Offset uintptr
}
// Aligned8Byte returns if all fields are aligned modulo 8-bytes.
//
// Error messaging is printed to out for any field determined misaligned.
func Aligned8Byte(fields []FieldOffset, out io.Writer) bool {
misaligned := make([]FieldOffset, 0)
for _, f := range fields {
if f.Offset%8 != 0 {
misaligned = append(misaligned, f)
}
}
if len(misaligned) == 0 {
return true
}
fmt.Fprintln(out, "struct fields not aligned for 64-bit atomic operations:")
for _, f := range misaligned {
fmt.Fprintf(out, " %s: %d-byte offset\n", f.Name, f.Offset)
}
return false
}

View File

@ -0,0 +1,101 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env.go.tmpl
// 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 (
"os"
)
type Env struct {
Name string
Value string
Exists bool
}
// EnvStore stores and recovers environment variables.
type EnvStore interface {
// Records the environment variable into the store.
Record(key string)
// Restore recovers the environment variables in the store.
Restore() error
}
var _ EnvStore = (*envStore)(nil)
type envStore struct {
store map[string]Env
}
func (s *envStore) add(env Env) {
s.store[env.Name] = env
}
func (s *envStore) Restore() error {
var err error
for _, v := range s.store {
if v.Exists {
err = os.Setenv(v.Name, v.Value)
} else {
err = os.Unsetenv(v.Name)
}
if err != nil {
return err
}
}
return nil
}
func (s *envStore) setEnv(key, value string) error {
s.Record(key)
err := os.Setenv(key, value)
if err != nil {
return err
}
return nil
}
func (s *envStore) Record(key string) {
originValue, exists := os.LookupEnv(key)
s.add(Env{
Name: key,
Value: originValue,
Exists: exists,
})
}
func NewEnvStore() EnvStore {
return newEnvStore()
}
func newEnvStore() *envStore {
return &envStore{store: make(map[string]Env)}
}
func SetEnvVariables(env map[string]string) (EnvStore, error) {
envStore := newEnvStore()
for k, v := range env {
err := envStore.setEnv(k, v)
if err != nil {
return nil, err
}
}
return envStore, nil
}

View File

@ -0,0 +1,237 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env_test.go.tmpl
// 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 (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type EnvStoreTestSuite struct {
suite.Suite
}
func (s *EnvStoreTestSuite) Test_add() {
envStore := newEnvStore()
e := Env{
Name: "name",
Value: "value",
Exists: true,
}
envStore.add(e)
envStore.add(e)
s.Assert().Len(envStore.store, 1)
}
func (s *EnvStoreTestSuite) TestRecord() {
testCases := []struct {
name string
env Env
expectedEnvStore *envStore
}{
{
name: "record exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "value",
Exists: true,
},
}},
},
{
name: "record exists env, but its value is empty",
env: Env{
Name: "name",
Value: "",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "",
Exists: true,
},
}},
},
{
name: "record not exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Exists: false,
},
}},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
if tc.env.Exists {
s.Assert().NoError(os.Setenv(tc.env.Name, tc.env.Value))
}
envStore := newEnvStore()
envStore.Record(tc.env.Name)
s.Assert().Equal(tc.expectedEnvStore, envStore)
if tc.env.Exists {
s.Assert().NoError(os.Unsetenv(tc.env.Name))
}
})
}
}
func (s *EnvStoreTestSuite) TestRestore() {
testCases := []struct {
name string
env Env
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvValue: "value",
expectedEnvExists: true,
},
{
name: "no exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvExists: false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
envStore.add(tc.env)
// Backup
backup := newEnvStore()
backup.Record(tc.env.Name)
s.Require().NoError(os.Unsetenv(tc.env.Name))
s.Assert().NoError(envStore.Restore())
v, exists := os.LookupEnv(tc.env.Name)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func (s *EnvStoreTestSuite) Test_setEnv() {
testCases := []struct {
name string
key string
value string
expectedEnvStore *envStore
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "normal",
key: "name",
value: "value",
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "other value",
Exists: true,
},
}},
expectedEnvValue: "value",
expectedEnvExists: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
// Backup
backup := newEnvStore()
backup.Record(tc.key)
s.Require().NoError(os.Setenv(tc.key, "other value"))
s.Assert().NoError(envStore.setEnv(tc.key, tc.value))
s.Assert().Equal(tc.expectedEnvStore, envStore)
v, exists := os.LookupEnv(tc.key)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func TestEnvStoreTestSuite(t *testing.T) {
suite.Run(t, new(EnvStoreTestSuite))
}
func TestSetEnvVariables(t *testing.T) {
envs := map[string]string{
"name1": "value1",
"name2": "value2",
}
// Backup
backup := newEnvStore()
for k := range envs {
backup.Record(k)
}
defer func() {
require.NoError(t, backup.Restore())
}()
store, err := SetEnvVariables(envs)
assert.NoError(t, err)
require.IsType(t, &envStore{}, store)
concreteStore := store.(*envStore)
assert.Len(t, concreteStore.store, 2)
assert.Equal(t, backup, concreteStore)
}

View File

@ -0,0 +1,30 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/errors.go.tmpl
// 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
type TestError string
var _ error = TestError("")
func NewTestError(s string) error {
return TestError(s)
}
func (e TestError) Error() string {
return string(e)
}

View File

@ -0,0 +1,344 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/harness.go.tmpl
// 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 (
"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{}

View File

@ -0,0 +1,144 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier.go.tmpl
// 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 (
"sync"
"testing"
"go.opentelemetry.io/otel/propagation"
)
// TextMapCarrier is a storage medium for a TextMapPropagator used in testing.
// The methods of a TextMapCarrier are concurrent safe.
type TextMapCarrier struct {
mtx sync.Mutex
gets []string
sets [][2]string
data map[string]string
}
var _ propagation.TextMapCarrier = (*TextMapCarrier)(nil)
// NewTextMapCarrier returns a new *TextMapCarrier populated with data.
func NewTextMapCarrier(data map[string]string) *TextMapCarrier {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
return &TextMapCarrier{data: copied}
}
// Keys returns the keys for which this carrier has a value.
func (c *TextMapCarrier) Keys() []string {
c.mtx.Lock()
defer c.mtx.Unlock()
result := make([]string, 0, len(c.data))
for k := range c.data {
result = append(result, k)
}
return result
}
// Get returns the value associated with the passed key.
func (c *TextMapCarrier) Get(key string) string {
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = append(c.gets, key)
return c.data[key]
}
// GotKey tests if c.Get has been called for key.
func (c *TextMapCarrier) GotKey(t *testing.T, key string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
for _, k := range c.gets {
if k == key {
return true
}
}
t.Errorf("TextMapCarrier.Get(%q) has not been called", key)
return false
}
// GotN tests if n calls to c.Get have been made.
func (c *TextMapCarrier) GotN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.gets) != n {
t.Errorf("TextMapCarrier.Get was called %d times, not %d", len(c.gets), n)
return false
}
return true
}
// Set stores the key-value pair.
func (c *TextMapCarrier) Set(key, value string) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.sets = append(c.sets, [2]string{key, value})
c.data[key] = value
}
// SetKeyValue tests if c.Set has been called for the key-value pair.
func (c *TextMapCarrier) SetKeyValue(t *testing.T, key, value string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
var vals []string
for _, pair := range c.sets {
if key == pair[0] {
if value == pair[1] {
return true
}
vals = append(vals, pair[1])
}
}
if len(vals) > 0 {
t.Errorf("TextMapCarrier.Set called with %q and %v values, but not %s", key, vals, value)
}
t.Errorf("TextMapCarrier.Set(%q,%q) has not been called", key, value)
return false
}
// SetN tests if n calls to c.Set have been made.
func (c *TextMapCarrier) SetN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.sets) != n {
t.Errorf("TextMapCarrier.Set was called %d times, not %d", len(c.sets), n)
return false
}
return true
}
// Reset zeros out the recording state and sets the carried values to data.
func (c *TextMapCarrier) Reset(data map[string]string) {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = nil
c.sets = nil
c.data = copied
}

View File

@ -0,0 +1,86 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier_test.go.tmpl
// 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 (
"reflect"
"testing"
)
var (
key, value = "test", "true"
)
func TestTextMapCarrierKeys(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
expected, actual := []string{key}, tmc.Keys()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("expected tmc.Keys() to be %v but it was %v", expected, actual)
}
}
func TestTextMapCarrierGet(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
if got := tmc.Get("empty"); got != "" {
t.Errorf("TextMapCarrier.Get returned %q for an empty key", got)
}
tmc.GotKey(t, "empty")
tmc.GotN(t, 1)
if got := tmc.Get(key); got != value {
t.Errorf("TextMapCarrier.Get(%q) returned %q, want %q", key, got, value)
}
tmc.GotKey(t, key)
tmc.GotN(t, 2)
}
func TestTextMapCarrierSet(t *testing.T) {
tmc := NewTextMapCarrier(nil)
tmc.SetN(t, 0)
tmc.Set(key, value)
if got, ok := tmc.data[key]; !ok {
t.Errorf("TextMapCarrier.Set(%q,%q) failed to store pair", key, value)
} else if got != value {
t.Errorf("TextMapCarrier.Set(%q,%q) stored (%q,%q), not (%q,%q)", key, value, key, got, key, value)
}
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
}
func TestTextMapCarrierReset(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
tmc.SetN(t, 0)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear initial data")
}
tmc.GotN(t, 1)
tmc.GotKey(t, key)
tmc.Set(key, value)
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear data")
}
}

View File

@ -0,0 +1,115 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator.go.tmpl
// 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 (
"context"
"fmt"
"strconv"
"strings"
"testing"
"go.opentelemetry.io/otel/propagation"
)
type ctxKeyType string
type state struct {
Injections uint64
Extractions uint64
}
func newState(encoded string) state {
if encoded == "" {
return state{}
}
s0, s1, _ := strings.Cut(encoded, ",")
injects, _ := strconv.ParseUint(s0, 10, 64)
extracts, _ := strconv.ParseUint(s1, 10, 64)
return state{
Injections: injects,
Extractions: extracts,
}
}
func (s state) String() string {
return fmt.Sprintf("%d,%d", s.Injections, s.Extractions)
}
// TextMapPropagator is a propagation.TextMapPropagator used for testing.
type TextMapPropagator struct {
name string
ctxKey ctxKeyType
}
var _ propagation.TextMapPropagator = (*TextMapPropagator)(nil)
// NewTextMapPropagator returns a new TextMapPropagator for testing. It will
// use name as the key it injects into a TextMapCarrier when Inject is called.
func NewTextMapPropagator(name string) *TextMapPropagator {
return &TextMapPropagator{name: name, ctxKey: ctxKeyType(name)}
}
func (p *TextMapPropagator) stateFromContext(ctx context.Context) state {
if v := ctx.Value(p.ctxKey); v != nil {
if s, ok := v.(state); ok {
return s
}
}
return state{}
}
func (p *TextMapPropagator) stateFromCarrier(carrier propagation.TextMapCarrier) state {
return newState(carrier.Get(p.name))
}
// Inject sets cross-cutting concerns for p from ctx into carrier.
func (p *TextMapPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
s := p.stateFromContext(ctx)
s.Injections++
carrier.Set(p.name, s.String())
}
// InjectedN tests if p has made n injections to carrier.
func (p *TextMapPropagator) InjectedN(t *testing.T, carrier *TextMapCarrier, n int) bool {
if actual := p.stateFromCarrier(carrier).Injections; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} injected %d times, not %d", p.name, actual, n)
return false
}
return true
}
// Extract reads cross-cutting concerns for p from carrier into ctx.
func (p *TextMapPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
s := p.stateFromCarrier(carrier)
s.Extractions++
return context.WithValue(ctx, p.ctxKey, s)
}
// ExtractedN tests if p has made n extractions from the lineage of ctx.
// nolint (context is not first arg)
func (p *TextMapPropagator) ExtractedN(t *testing.T, ctx context.Context, n int) bool {
if actual := p.stateFromContext(ctx).Extractions; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} extracted %d time, not %d", p.name, actual, n)
return false
}
return true
}
// Fields returns the name of p as the key who's value is set with Inject.
func (p *TextMapPropagator) Fields() []string { return []string{p.name} }

View File

@ -0,0 +1,72 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator_test.go.tmpl
// 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 (
"context"
"testing"
)
func TestTextMapPropagatorInjectExtract(t *testing.T) {
name := "testing"
ctx := context.Background()
carrier := NewTextMapCarrier(map[string]string{name: value})
propagator := NewTextMapPropagator(name)
propagator.Inject(ctx, carrier)
// Carrier value overridden with state.
if carrier.SetKeyValue(t, name, "1,0") {
// Ensure nothing has been extracted yet.
propagator.ExtractedN(t, ctx, 0)
// Test the injection was counted.
propagator.InjectedN(t, carrier, 1)
}
ctx = propagator.Extract(ctx, carrier)
v := ctx.Value(ctxKeyType(name))
if v == nil {
t.Error("TextMapPropagator.Extract failed to extract state")
}
if s, ok := v.(state); !ok {
t.Error("TextMapPropagator.Extract did not extract proper state")
} else if s.Extractions != 1 {
t.Error("TextMapPropagator.Extract did not increment state.Extractions")
}
if carrier.GotKey(t, name) {
// Test the extraction was counted.
propagator.ExtractedN(t, ctx, 1)
// Ensure no additional injection was recorded.
propagator.InjectedN(t, carrier, 1)
}
}
func TestTextMapPropagatorFields(t *testing.T) {
name := "testing"
propagator := NewTextMapPropagator(name)
if got := propagator.Fields(); len(got) != 1 {
t.Errorf("TextMapPropagator.Fields returned %d fields, want 1", len(got))
} else if got[0] != name {
t.Errorf("TextMapPropagator.Fields returned %q, want %q", got[0], name)
}
}
func TestNewStateEmpty(t *testing.T) {
if want, got := (state{}), newState(""); got != want {
t.Errorf("newState(\"\") returned %v, want %v", got, want)
}
}

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
)
func TestEnvParse(t *testing.T) {

View File

@ -0,0 +1,74 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/alignment.go.tmpl
// 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/sdk/internal/internaltest"
/*
This file contains common utilities and objects to validate memory alignment
of Go types. The primary use of this functionality is intended to ensure
`struct` fields that need to be 64-bit aligned so they can be passed as
arguments to 64-bit atomic operations.
The common workflow is to define a slice of `FieldOffset` and pass them to the
`Aligned8Byte` function from within a `TestMain` function from a package's
tests. It is important to make this call from the `TestMain` function prior
to running the rest of the test suit as it can provide useful diagnostics
about field alignment instead of ambiguous nil pointer dereference and runtime
panic.
For more information:
https://github.com/open-telemetry/opentelemetry-go/issues/341
*/
import (
"fmt"
"io"
)
// FieldOffset is a preprocessor representation of a struct field alignment.
type FieldOffset struct {
// Name of the field.
Name string
// Offset of the field in bytes.
//
// To compute this at compile time use unsafe.Offsetof.
Offset uintptr
}
// Aligned8Byte returns if all fields are aligned modulo 8-bytes.
//
// Error messaging is printed to out for any field determined misaligned.
func Aligned8Byte(fields []FieldOffset, out io.Writer) bool {
misaligned := make([]FieldOffset, 0)
for _, f := range fields {
if f.Offset%8 != 0 {
misaligned = append(misaligned, f)
}
}
if len(misaligned) == 0 {
return true
}
fmt.Fprintln(out, "struct fields not aligned for 64-bit atomic operations:")
for _, f := range misaligned {
fmt.Fprintf(out, " %s: %d-byte offset\n", f.Name, f.Offset)
}
return false
}

View File

@ -0,0 +1,101 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env.go.tmpl
// 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/sdk/internal/internaltest"
import (
"os"
)
type Env struct {
Name string
Value string
Exists bool
}
// EnvStore stores and recovers environment variables.
type EnvStore interface {
// Records the environment variable into the store.
Record(key string)
// Restore recovers the environment variables in the store.
Restore() error
}
var _ EnvStore = (*envStore)(nil)
type envStore struct {
store map[string]Env
}
func (s *envStore) add(env Env) {
s.store[env.Name] = env
}
func (s *envStore) Restore() error {
var err error
for _, v := range s.store {
if v.Exists {
err = os.Setenv(v.Name, v.Value)
} else {
err = os.Unsetenv(v.Name)
}
if err != nil {
return err
}
}
return nil
}
func (s *envStore) setEnv(key, value string) error {
s.Record(key)
err := os.Setenv(key, value)
if err != nil {
return err
}
return nil
}
func (s *envStore) Record(key string) {
originValue, exists := os.LookupEnv(key)
s.add(Env{
Name: key,
Value: originValue,
Exists: exists,
})
}
func NewEnvStore() EnvStore {
return newEnvStore()
}
func newEnvStore() *envStore {
return &envStore{store: make(map[string]Env)}
}
func SetEnvVariables(env map[string]string) (EnvStore, error) {
envStore := newEnvStore()
for k, v := range env {
err := envStore.setEnv(k, v)
if err != nil {
return nil, err
}
}
return envStore, nil
}

View File

@ -0,0 +1,237 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/env_test.go.tmpl
// 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 (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type EnvStoreTestSuite struct {
suite.Suite
}
func (s *EnvStoreTestSuite) Test_add() {
envStore := newEnvStore()
e := Env{
Name: "name",
Value: "value",
Exists: true,
}
envStore.add(e)
envStore.add(e)
s.Assert().Len(envStore.store, 1)
}
func (s *EnvStoreTestSuite) TestRecord() {
testCases := []struct {
name string
env Env
expectedEnvStore *envStore
}{
{
name: "record exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "value",
Exists: true,
},
}},
},
{
name: "record exists env, but its value is empty",
env: Env{
Name: "name",
Value: "",
Exists: true,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "",
Exists: true,
},
}},
},
{
name: "record not exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Exists: false,
},
}},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
if tc.env.Exists {
s.Assert().NoError(os.Setenv(tc.env.Name, tc.env.Value))
}
envStore := newEnvStore()
envStore.Record(tc.env.Name)
s.Assert().Equal(tc.expectedEnvStore, envStore)
if tc.env.Exists {
s.Assert().NoError(os.Unsetenv(tc.env.Name))
}
})
}
}
func (s *EnvStoreTestSuite) TestRestore() {
testCases := []struct {
name string
env Env
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "exists env",
env: Env{
Name: "name",
Value: "value",
Exists: true,
},
expectedEnvValue: "value",
expectedEnvExists: true,
},
{
name: "no exists env",
env: Env{
Name: "name",
Exists: false,
},
expectedEnvExists: false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
envStore.add(tc.env)
// Backup
backup := newEnvStore()
backup.Record(tc.env.Name)
s.Require().NoError(os.Unsetenv(tc.env.Name))
s.Assert().NoError(envStore.Restore())
v, exists := os.LookupEnv(tc.env.Name)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func (s *EnvStoreTestSuite) Test_setEnv() {
testCases := []struct {
name string
key string
value string
expectedEnvStore *envStore
expectedEnvValue string
expectedEnvExists bool
}{
{
name: "normal",
key: "name",
value: "value",
expectedEnvStore: &envStore{store: map[string]Env{
"name": {
Name: "name",
Value: "other value",
Exists: true,
},
}},
expectedEnvValue: "value",
expectedEnvExists: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
envStore := newEnvStore()
// Backup
backup := newEnvStore()
backup.Record(tc.key)
s.Require().NoError(os.Setenv(tc.key, "other value"))
s.Assert().NoError(envStore.setEnv(tc.key, tc.value))
s.Assert().Equal(tc.expectedEnvStore, envStore)
v, exists := os.LookupEnv(tc.key)
s.Assert().Equal(tc.expectedEnvValue, v)
s.Assert().Equal(tc.expectedEnvExists, exists)
// Restore
s.Require().NoError(backup.Restore())
})
}
}
func TestEnvStoreTestSuite(t *testing.T) {
suite.Run(t, new(EnvStoreTestSuite))
}
func TestSetEnvVariables(t *testing.T) {
envs := map[string]string{
"name1": "value1",
"name2": "value2",
}
// Backup
backup := newEnvStore()
for k := range envs {
backup.Record(k)
}
defer func() {
require.NoError(t, backup.Restore())
}()
store, err := SetEnvVariables(envs)
assert.NoError(t, err)
require.IsType(t, &envStore{}, store)
concreteStore := store.(*envStore)
assert.Len(t, concreteStore.store, 2)
assert.Equal(t, backup, concreteStore)
}

View File

@ -0,0 +1,30 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/errors.go.tmpl
// 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/sdk/internal/internaltest"
type TestError string
var _ error = TestError("")
func NewTestError(s string) error {
return TestError(s)
}
func (e TestError) Error() string {
return string(e)
}

View File

@ -0,0 +1,25 @@
// 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/sdk/internal/internaltest"
//go:generate gotmpl --body=../../../internal/shared/internaltest/alignment.go.tmpl "--data={}" --out=alignment.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/env.go.tmpl "--data={}" --out=env.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/env_test.go.tmpl "--data={}" --out=env_test.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/errors.go.tmpl "--data={}" --out=errors.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/harness.go.tmpl "--data={}" --out=harness.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/text_map_carrier.go.tmpl "--data={}" --out=text_map_carrier.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/text_map_carrier_test.go.tmpl "--data={}" --out=text_map_carrier_test.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/text_map_propagator.go.tmpl "--data={}" --out=text_map_propagator.go
//go:generate gotmpl --body=../../../internal/shared/internaltest/text_map_propagator_test.go.tmpl "--data={}" --out=text_map_propagator_test.go

View File

@ -0,0 +1,344 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/harness.go.tmpl
// 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/sdk/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{}

View File

@ -0,0 +1,144 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier.go.tmpl
// 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/sdk/internal/internaltest"
import (
"sync"
"testing"
"go.opentelemetry.io/otel/propagation"
)
// TextMapCarrier is a storage medium for a TextMapPropagator used in testing.
// The methods of a TextMapCarrier are concurrent safe.
type TextMapCarrier struct {
mtx sync.Mutex
gets []string
sets [][2]string
data map[string]string
}
var _ propagation.TextMapCarrier = (*TextMapCarrier)(nil)
// NewTextMapCarrier returns a new *TextMapCarrier populated with data.
func NewTextMapCarrier(data map[string]string) *TextMapCarrier {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
return &TextMapCarrier{data: copied}
}
// Keys returns the keys for which this carrier has a value.
func (c *TextMapCarrier) Keys() []string {
c.mtx.Lock()
defer c.mtx.Unlock()
result := make([]string, 0, len(c.data))
for k := range c.data {
result = append(result, k)
}
return result
}
// Get returns the value associated with the passed key.
func (c *TextMapCarrier) Get(key string) string {
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = append(c.gets, key)
return c.data[key]
}
// GotKey tests if c.Get has been called for key.
func (c *TextMapCarrier) GotKey(t *testing.T, key string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
for _, k := range c.gets {
if k == key {
return true
}
}
t.Errorf("TextMapCarrier.Get(%q) has not been called", key)
return false
}
// GotN tests if n calls to c.Get have been made.
func (c *TextMapCarrier) GotN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.gets) != n {
t.Errorf("TextMapCarrier.Get was called %d times, not %d", len(c.gets), n)
return false
}
return true
}
// Set stores the key-value pair.
func (c *TextMapCarrier) Set(key, value string) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.sets = append(c.sets, [2]string{key, value})
c.data[key] = value
}
// SetKeyValue tests if c.Set has been called for the key-value pair.
func (c *TextMapCarrier) SetKeyValue(t *testing.T, key, value string) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
var vals []string
for _, pair := range c.sets {
if key == pair[0] {
if value == pair[1] {
return true
}
vals = append(vals, pair[1])
}
}
if len(vals) > 0 {
t.Errorf("TextMapCarrier.Set called with %q and %v values, but not %s", key, vals, value)
}
t.Errorf("TextMapCarrier.Set(%q,%q) has not been called", key, value)
return false
}
// SetN tests if n calls to c.Set have been made.
func (c *TextMapCarrier) SetN(t *testing.T, n int) bool {
c.mtx.Lock()
defer c.mtx.Unlock()
if len(c.sets) != n {
t.Errorf("TextMapCarrier.Set was called %d times, not %d", len(c.sets), n)
return false
}
return true
}
// Reset zeros out the recording state and sets the carried values to data.
func (c *TextMapCarrier) Reset(data map[string]string) {
copied := make(map[string]string, len(data))
for k, v := range data {
copied[k] = v
}
c.mtx.Lock()
defer c.mtx.Unlock()
c.gets = nil
c.sets = nil
c.data = copied
}

View File

@ -0,0 +1,86 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_carrier_test.go.tmpl
// 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 (
"reflect"
"testing"
)
var (
key, value = "test", "true"
)
func TestTextMapCarrierKeys(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
expected, actual := []string{key}, tmc.Keys()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("expected tmc.Keys() to be %v but it was %v", expected, actual)
}
}
func TestTextMapCarrierGet(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
if got := tmc.Get("empty"); got != "" {
t.Errorf("TextMapCarrier.Get returned %q for an empty key", got)
}
tmc.GotKey(t, "empty")
tmc.GotN(t, 1)
if got := tmc.Get(key); got != value {
t.Errorf("TextMapCarrier.Get(%q) returned %q, want %q", key, got, value)
}
tmc.GotKey(t, key)
tmc.GotN(t, 2)
}
func TestTextMapCarrierSet(t *testing.T) {
tmc := NewTextMapCarrier(nil)
tmc.SetN(t, 0)
tmc.Set(key, value)
if got, ok := tmc.data[key]; !ok {
t.Errorf("TextMapCarrier.Set(%q,%q) failed to store pair", key, value)
} else if got != value {
t.Errorf("TextMapCarrier.Set(%q,%q) stored (%q,%q), not (%q,%q)", key, value, key, got, key, value)
}
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
}
func TestTextMapCarrierReset(t *testing.T) {
tmc := NewTextMapCarrier(map[string]string{key: value})
tmc.GotN(t, 0)
tmc.SetN(t, 0)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear initial data")
}
tmc.GotN(t, 1)
tmc.GotKey(t, key)
tmc.Set(key, value)
tmc.SetKeyValue(t, key, value)
tmc.SetN(t, 1)
tmc.Reset(nil)
tmc.GotN(t, 0)
tmc.SetN(t, 0)
if got := tmc.Get(key); got != "" {
t.Error("TextMapCarrier.Reset() failed to clear data")
}
}

View File

@ -0,0 +1,115 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator.go.tmpl
// 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/sdk/internal/internaltest"
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"go.opentelemetry.io/otel/propagation"
)
type ctxKeyType string
type state struct {
Injections uint64
Extractions uint64
}
func newState(encoded string) state {
if encoded == "" {
return state{}
}
s0, s1, _ := strings.Cut(encoded, ",")
injects, _ := strconv.ParseUint(s0, 10, 64)
extracts, _ := strconv.ParseUint(s1, 10, 64)
return state{
Injections: injects,
Extractions: extracts,
}
}
func (s state) String() string {
return fmt.Sprintf("%d,%d", s.Injections, s.Extractions)
}
// TextMapPropagator is a propagation.TextMapPropagator used for testing.
type TextMapPropagator struct {
name string
ctxKey ctxKeyType
}
var _ propagation.TextMapPropagator = (*TextMapPropagator)(nil)
// NewTextMapPropagator returns a new TextMapPropagator for testing. It will
// use name as the key it injects into a TextMapCarrier when Inject is called.
func NewTextMapPropagator(name string) *TextMapPropagator {
return &TextMapPropagator{name: name, ctxKey: ctxKeyType(name)}
}
func (p *TextMapPropagator) stateFromContext(ctx context.Context) state {
if v := ctx.Value(p.ctxKey); v != nil {
if s, ok := v.(state); ok {
return s
}
}
return state{}
}
func (p *TextMapPropagator) stateFromCarrier(carrier propagation.TextMapCarrier) state {
return newState(carrier.Get(p.name))
}
// Inject sets cross-cutting concerns for p from ctx into carrier.
func (p *TextMapPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
s := p.stateFromContext(ctx)
s.Injections++
carrier.Set(p.name, s.String())
}
// InjectedN tests if p has made n injections to carrier.
func (p *TextMapPropagator) InjectedN(t *testing.T, carrier *TextMapCarrier, n int) bool {
if actual := p.stateFromCarrier(carrier).Injections; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} injected %d times, not %d", p.name, actual, n)
return false
}
return true
}
// Extract reads cross-cutting concerns for p from carrier into ctx.
func (p *TextMapPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
s := p.stateFromCarrier(carrier)
s.Extractions++
return context.WithValue(ctx, p.ctxKey, s)
}
// ExtractedN tests if p has made n extractions from the lineage of ctx.
// nolint (context is not first arg)
func (p *TextMapPropagator) ExtractedN(t *testing.T, ctx context.Context, n int) bool {
if actual := p.stateFromContext(ctx).Extractions; actual != uint64(n) {
t.Errorf("TextMapPropagator{%q} extracted %d time, not %d", p.name, actual, n)
return false
}
return true
}
// Fields returns the name of p as the key who's value is set with Inject.
func (p *TextMapPropagator) Fields() []string { return []string{p.name} }

View File

@ -0,0 +1,72 @@
// Code created by gotmpl. DO NOT MODIFY.
// source: internal/shared/internaltest/text_map_propagator_test.go.tmpl
// 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 (
"context"
"testing"
)
func TestTextMapPropagatorInjectExtract(t *testing.T) {
name := "testing"
ctx := context.Background()
carrier := NewTextMapCarrier(map[string]string{name: value})
propagator := NewTextMapPropagator(name)
propagator.Inject(ctx, carrier)
// Carrier value overridden with state.
if carrier.SetKeyValue(t, name, "1,0") {
// Ensure nothing has been extracted yet.
propagator.ExtractedN(t, ctx, 0)
// Test the injection was counted.
propagator.InjectedN(t, carrier, 1)
}
ctx = propagator.Extract(ctx, carrier)
v := ctx.Value(ctxKeyType(name))
if v == nil {
t.Error("TextMapPropagator.Extract failed to extract state")
}
if s, ok := v.(state); !ok {
t.Error("TextMapPropagator.Extract did not extract proper state")
} else if s.Extractions != 1 {
t.Error("TextMapPropagator.Extract did not increment state.Extractions")
}
if carrier.GotKey(t, name) {
// Test the extraction was counted.
propagator.ExtractedN(t, ctx, 1)
// Ensure no additional injection was recorded.
propagator.InjectedN(t, carrier, 1)
}
}
func TestTextMapPropagatorFields(t *testing.T) {
name := "testing"
propagator := NewTextMapPropagator(name)
if got := propagator.Fields(); len(got) != 1 {
t.Errorf("TextMapPropagator.Fields returned %d fields, want 1", len(got))
} else if got[0] != name {
t.Errorf("TextMapPropagator.Fields returned %q, want %q", got[0], name)
}
}
func TestNewStateEmpty(t *testing.T) {
if want, got := (state{}), newState(""); got != want {
t.Errorf("newState(\"\") returned %v, want %v", got, want)
}
}

View File

@ -23,7 +23,7 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)

View File

@ -29,8 +29,8 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
ottest "go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/sdk"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)

View File

@ -24,7 +24,7 @@ import (
"testing"
"time"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
"github.com/go-logr/logr/funcr"
"github.com/stretchr/testify/assert"

View File

@ -24,7 +24,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/internaltest"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
"go.opentelemetry.io/otel/trace"
)

View File

@ -23,8 +23,8 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
ottest "go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/sdk/internal/env"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
"go.opentelemetry.io/otel/trace"
)

View File

@ -33,8 +33,8 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
ottest "go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/sdk/instrumentation"
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
@ -1198,7 +1198,7 @@ func TestRecordError(t *testing.T) {
}{
{
err: ottest.NewTestError("test error"),
typ: "go.opentelemetry.io/otel/internal/internaltest.TestError",
typ: "go.opentelemetry.io/otel/sdk/internal/internaltest.TestError",
msg: "test error",
},
{
@ -1250,7 +1250,7 @@ func TestRecordError(t *testing.T) {
func TestRecordErrorWithStackTrace(t *testing.T) {
err := ottest.NewTestError("test error")
typ := "go.opentelemetry.io/otel/internal/internaltest.TestError"
typ := "go.opentelemetry.io/otel/sdk/internal/internaltest.TestError"
msg := "test error"
te := NewTestExporter()