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:
parent
e3ed198611
commit
b7f634a4fe
@ -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) {
|
||||
|
74
exporters/jaeger/internal/internaltest/alignment.go
Normal file
74
exporters/jaeger/internal/internaltest/alignment.go
Normal 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
|
||||
}
|
101
exporters/jaeger/internal/internaltest/env.go
Normal file
101
exporters/jaeger/internal/internaltest/env.go
Normal 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
|
||||
}
|
237
exporters/jaeger/internal/internaltest/env_test.go
Normal file
237
exporters/jaeger/internal/internaltest/env_test.go
Normal 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)
|
||||
}
|
30
exporters/jaeger/internal/internaltest/errors.go
Normal file
30
exporters/jaeger/internal/internaltest/errors.go
Normal 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)
|
||||
}
|
25
exporters/jaeger/internal/internaltest/gen.go
Normal file
25
exporters/jaeger/internal/internaltest/gen.go
Normal 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
|
344
exporters/jaeger/internal/internaltest/harness.go
Normal file
344
exporters/jaeger/internal/internaltest/harness.go
Normal 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{}
|
144
exporters/jaeger/internal/internaltest/text_map_carrier.go
Normal file
144
exporters/jaeger/internal/internaltest/text_map_carrier.go
Normal 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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
115
exporters/jaeger/internal/internaltest/text_map_propagator.go
Normal file
115
exporters/jaeger/internal/internaltest/text_map_propagator.go
Normal 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} }
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
74
exporters/zipkin/internal/internaltest/alignment.go
Normal file
74
exporters/zipkin/internal/internaltest/alignment.go
Normal 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
|
||||
}
|
101
exporters/zipkin/internal/internaltest/env.go
Normal file
101
exporters/zipkin/internal/internaltest/env.go
Normal 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
|
||||
}
|
237
exporters/zipkin/internal/internaltest/env_test.go
Normal file
237
exporters/zipkin/internal/internaltest/env_test.go
Normal 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)
|
||||
}
|
30
exporters/zipkin/internal/internaltest/errors.go
Normal file
30
exporters/zipkin/internal/internaltest/errors.go
Normal 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)
|
||||
}
|
25
exporters/zipkin/internal/internaltest/gen.go
Normal file
25
exporters/zipkin/internal/internaltest/gen.go
Normal 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
|
344
exporters/zipkin/internal/internaltest/harness.go
Normal file
344
exporters/zipkin/internal/internaltest/harness.go
Normal 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{}
|
144
exporters/zipkin/internal/internaltest/text_map_carrier.go
Normal file
144
exporters/zipkin/internal/internaltest/text_map_carrier.go
Normal 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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
115
exporters/zipkin/internal/internaltest/text_map_propagator.go
Normal file
115
exporters/zipkin/internal/internaltest/text_map_propagator.go
Normal 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} }
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
25
internal/internaltest/gen.go
Normal file
25
internal/internaltest/gen.go
Normal 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
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
74
internal/shared/internaltest/alignment.go.tmpl
Normal file
74
internal/shared/internaltest/alignment.go.tmpl
Normal 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
|
||||
}
|
101
internal/shared/internaltest/env.go.tmpl
Normal file
101
internal/shared/internaltest/env.go.tmpl
Normal 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
|
||||
}
|
237
internal/shared/internaltest/env_test.go.tmpl
Normal file
237
internal/shared/internaltest/env_test.go.tmpl
Normal 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)
|
||||
}
|
30
internal/shared/internaltest/errors.go.tmpl
Normal file
30
internal/shared/internaltest/errors.go.tmpl
Normal 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)
|
||||
}
|
344
internal/shared/internaltest/harness.go.tmpl
Normal file
344
internal/shared/internaltest/harness.go.tmpl
Normal 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{}
|
144
internal/shared/internaltest/text_map_carrier.go.tmpl
Normal file
144
internal/shared/internaltest/text_map_carrier.go.tmpl
Normal 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
|
||||
}
|
86
internal/shared/internaltest/text_map_carrier_test.go.tmpl
Normal file
86
internal/shared/internaltest/text_map_carrier_test.go.tmpl
Normal 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")
|
||||
}
|
||||
}
|
115
internal/shared/internaltest/text_map_propagator.go.tmpl
Normal file
115
internal/shared/internaltest/text_map_propagator.go.tmpl
Normal 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} }
|
@ -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)
|
||||
}
|
||||
}
|
2
sdk/internal/env/env_test.go
vendored
2
sdk/internal/env/env_test.go
vendored
@ -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) {
|
||||
|
74
sdk/internal/internaltest/alignment.go
Normal file
74
sdk/internal/internaltest/alignment.go
Normal 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
|
||||
}
|
101
sdk/internal/internaltest/env.go
Normal file
101
sdk/internal/internaltest/env.go
Normal 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
|
||||
}
|
237
sdk/internal/internaltest/env_test.go
Normal file
237
sdk/internal/internaltest/env_test.go
Normal 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)
|
||||
}
|
30
sdk/internal/internaltest/errors.go
Normal file
30
sdk/internal/internaltest/errors.go
Normal 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)
|
||||
}
|
25
sdk/internal/internaltest/gen.go
Normal file
25
sdk/internal/internaltest/gen.go
Normal 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
|
344
sdk/internal/internaltest/harness.go
Normal file
344
sdk/internal/internaltest/harness.go
Normal 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{}
|
144
sdk/internal/internaltest/text_map_carrier.go
Normal file
144
sdk/internal/internaltest/text_map_carrier.go
Normal 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
|
||||
}
|
86
sdk/internal/internaltest/text_map_carrier_test.go
Normal file
86
sdk/internal/internaltest/text_map_carrier_test.go
Normal 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")
|
||||
}
|
||||
}
|
115
sdk/internal/internaltest/text_map_propagator.go
Normal file
115
sdk/internal/internaltest/text_map_propagator.go
Normal 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} }
|
72
sdk/internal/internaltest/text_map_propagator_test.go
Normal file
72
sdk/internal/internaltest/text_map_propagator_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user