1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-22 20:06:07 +02:00
opentelemetry-go/bridge/opentracing/mix_test.go
Tyler Yahn 4bf6150fa9
Add baggage implementation based on the W3C and OpenTelemetry specification (#1967)
* Rename baggage context file

* Initial baggage implementation

* Initial tests

* More tests

* Update baggage context functionality

* Add New method to baggage pkg

* Update namedtracer example

* URL encode baggage values

* Refactor and use internal baggage pkg

* Update OpenTracing bridge

* Update baggage propagator

* Fix lint and test errors

* Add changes to changelog

* Apply suggestions from code review

* Rename testcase field per suggestion

* Update test to verify last-one-wins semantics

* Explicitly seed random numbers with static seed in tests

* Parse Member key/value with string split

* Add test for member parse with equal signs in value

* Trim whitespaces for member key/value
2021-06-08 08:06:37 -07:00

729 lines
20 KiB
Go

// 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 opentracing
import (
"context"
"fmt"
"testing"
ot "github.com/opentracing/opentracing-go"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/bridge/opentracing/internal"
)
type mixedAPIsTestCase struct {
desc string
setup func(*testing.T, *internal.MockTracer)
run func(*testing.T, context.Context)
check func(*testing.T, *internal.MockTracer)
}
func getMixedAPIsTestCases() []mixedAPIsTestCase {
st := newSimpleTest()
cast := newCurrentActiveSpanTest()
coin := newContextIntactTest()
bip := newBaggageItemsPreservationTest()
bio := newBaggageInteroperationTest()
return []mixedAPIsTestCase{
{
desc: "simple otel -> ot -> otel",
setup: st.setup,
run: st.runOtelOTOtel,
check: st.check,
},
{
desc: "simple ot -> otel -> ot",
setup: st.setup,
run: st.runOTOtelOT,
check: st.check,
},
{
desc: "current/active span otel -> ot -> otel",
setup: cast.setup,
run: cast.runOtelOTOtel,
check: cast.check,
},
{
desc: "current/active span ot -> otel -> ot",
setup: cast.setup,
run: cast.runOTOtelOT,
check: cast.check,
},
{
desc: "context intact otel -> ot -> otel",
setup: coin.setup,
run: coin.runOtelOTOtel,
check: coin.check,
},
{
desc: "context intact ot -> otel -> ot",
setup: coin.setup,
run: coin.runOTOtelOT,
check: coin.check,
},
{
desc: "baggage items preservation across layers otel -> ot -> otel",
setup: bip.setup,
run: bip.runOtelOTOtel,
check: bip.check,
},
{
desc: "baggage items preservation across layers ot -> otel -> ot",
setup: bip.setup,
run: bip.runOTOtelOT,
check: bip.check,
},
{
desc: "baggage items interoperation across layers ot -> otel -> ot",
setup: bio.setup,
run: bio.runOTOtelOT,
check: bio.check,
},
{
desc: "baggage items interoperation across layers otel -> ot -> otel",
setup: bio.setup,
run: bio.runOtelOTOtel,
check: bio.check,
},
}
}
func TestMixedAPIs(t *testing.T) {
for idx, tc := range getMixedAPIsTestCases() {
t.Logf("Running test case %d: %s", idx, tc.desc)
mockOtelTracer := internal.NewMockTracer()
ctx, otTracer, otelProvider := NewTracerPairWithContext(context.Background(), mockOtelTracer)
otTracer.SetWarningHandler(func(msg string) {
t.Log(msg)
})
otel.SetTracerProvider(otelProvider)
ot.SetGlobalTracer(otTracer)
tc.setup(t, mockOtelTracer)
tc.run(t, ctx)
tc.check(t, mockOtelTracer)
}
}
// simple test
type simpleTest struct {
traceID trace.TraceID
spanIDs []trace.SpanID
}
func newSimpleTest() *simpleTest {
return &simpleTest{
traceID: simpleTraceID(),
spanIDs: simpleSpanIDs(3),
}
}
func (st *simpleTest) setup(t *testing.T, tracer *internal.MockTracer) {
tracer.SpareTraceIDs = append(tracer.SpareTraceIDs, st.traceID)
tracer.SpareSpanIDs = append(tracer.SpareSpanIDs, st.spanIDs...)
}
func (st *simpleTest) check(t *testing.T, tracer *internal.MockTracer) {
checkTraceAndSpans(t, tracer, st.traceID, st.spanIDs)
}
func (st *simpleTest) runOtelOTOtel(t *testing.T, ctx context.Context) {
runOtelOTOtel(t, ctx, "simple", st.noop)
}
func (st *simpleTest) runOTOtelOT(t *testing.T, ctx context.Context) {
runOTOtelOT(t, ctx, "simple", st.noop)
}
func (st *simpleTest) noop(t *testing.T, ctx context.Context) context.Context {
return ctx
}
// current/active span test
type currentActiveSpanTest struct {
traceID trace.TraceID
spanIDs []trace.SpanID
recordedCurrentOtelSpanIDs []trace.SpanID
recordedActiveOTSpanIDs []trace.SpanID
}
func newCurrentActiveSpanTest() *currentActiveSpanTest {
return &currentActiveSpanTest{
traceID: simpleTraceID(),
spanIDs: simpleSpanIDs(3),
}
}
func (cast *currentActiveSpanTest) setup(t *testing.T, tracer *internal.MockTracer) {
tracer.SpareTraceIDs = append(tracer.SpareTraceIDs, cast.traceID)
tracer.SpareSpanIDs = append(tracer.SpareSpanIDs, cast.spanIDs...)
cast.recordedCurrentOtelSpanIDs = nil
cast.recordedActiveOTSpanIDs = nil
}
func (cast *currentActiveSpanTest) check(t *testing.T, tracer *internal.MockTracer) {
checkTraceAndSpans(t, tracer, cast.traceID, cast.spanIDs)
if len(cast.recordedCurrentOtelSpanIDs) != len(cast.spanIDs) {
t.Errorf("Expected to have %d recorded Otel current spans, got %d", len(cast.spanIDs), len(cast.recordedCurrentOtelSpanIDs))
}
if len(cast.recordedActiveOTSpanIDs) != len(cast.spanIDs) {
t.Errorf("Expected to have %d recorded OT active spans, got %d", len(cast.spanIDs), len(cast.recordedActiveOTSpanIDs))
}
minLen := min(len(cast.recordedCurrentOtelSpanIDs), len(cast.spanIDs))
minLen = min(minLen, len(cast.recordedActiveOTSpanIDs))
for i := 0; i < minLen; i++ {
if cast.recordedCurrentOtelSpanIDs[i] != cast.spanIDs[i] {
t.Errorf("Expected span idx %d (%d) to be recorded as current span in Otel, got %d", i, cast.spanIDs[i], cast.recordedCurrentOtelSpanIDs[i])
}
if cast.recordedActiveOTSpanIDs[i] != cast.spanIDs[i] {
t.Errorf("Expected span idx %d (%d) to be recorded as active span in OT, got %d", i, cast.spanIDs[i], cast.recordedActiveOTSpanIDs[i])
}
}
}
func (cast *currentActiveSpanTest) runOtelOTOtel(t *testing.T, ctx context.Context) {
runOtelOTOtel(t, ctx, "cast", cast.recordSpans)
}
func (cast *currentActiveSpanTest) runOTOtelOT(t *testing.T, ctx context.Context) {
runOTOtelOT(t, ctx, "cast", cast.recordSpans)
}
func (cast *currentActiveSpanTest) recordSpans(t *testing.T, ctx context.Context) context.Context {
spanID := trace.SpanContextFromContext(ctx).SpanID()
cast.recordedCurrentOtelSpanIDs = append(cast.recordedCurrentOtelSpanIDs, spanID)
spanID = trace.SpanID{}
if bridgeSpan, ok := ot.SpanFromContext(ctx).(*bridgeSpan); ok {
spanID = bridgeSpan.otelSpan.SpanContext().SpanID()
}
cast.recordedActiveOTSpanIDs = append(cast.recordedActiveOTSpanIDs, spanID)
return ctx
}
// context intact test
type contextIntactTest struct {
contextKeyValues []internal.MockContextKeyValue
recordedContextValues []interface{}
recordIdx int
}
type coin1Key struct{}
type coin1Value struct{}
type coin2Key struct{}
type coin2Value struct{}
type coin3Key struct{}
type coin3Value struct{}
func newContextIntactTest() *contextIntactTest {
return &contextIntactTest{
contextKeyValues: []internal.MockContextKeyValue{
{
Key: coin1Key{},
Value: coin1Value{},
},
{
Key: coin2Key{},
Value: coin2Value{},
},
{
Key: coin3Key{},
Value: coin3Value{},
},
},
}
}
func (coin *contextIntactTest) setup(t *testing.T, tracer *internal.MockTracer) {
tracer.SpareContextKeyValues = append(tracer.SpareContextKeyValues, coin.contextKeyValues...)
coin.recordedContextValues = nil
coin.recordIdx = 0
}
func (coin *contextIntactTest) check(t *testing.T, tracer *internal.MockTracer) {
if len(coin.recordedContextValues) != len(coin.contextKeyValues) {
t.Errorf("Expected to have %d recorded context values, got %d", len(coin.contextKeyValues), len(coin.recordedContextValues))
}
minLen := min(len(coin.recordedContextValues), len(coin.contextKeyValues))
for i := 0; i < minLen; i++ {
key := coin.contextKeyValues[i].Key
value := coin.contextKeyValues[i].Value
gotValue := coin.recordedContextValues[i]
if value != gotValue {
t.Errorf("Expected value %#v for key %#v, got %#v", value, key, gotValue)
}
}
}
func (coin *contextIntactTest) runOtelOTOtel(t *testing.T, ctx context.Context) {
runOtelOTOtel(t, ctx, "coin", coin.recordValue)
}
func (coin *contextIntactTest) runOTOtelOT(t *testing.T, ctx context.Context) {
runOTOtelOT(t, ctx, "coin", coin.recordValue)
}
func (coin *contextIntactTest) recordValue(t *testing.T, ctx context.Context) context.Context {
if coin.recordIdx >= len(coin.contextKeyValues) {
t.Errorf("Too many steps?")
return ctx
}
key := coin.contextKeyValues[coin.recordIdx].Key
coin.recordIdx++
coin.recordedContextValues = append(coin.recordedContextValues, ctx.Value(key))
return ctx
}
// baggage items preservation test
type bipBaggage struct {
key string
value string
}
type baggageItemsPreservationTest struct {
baggageItems []bipBaggage
step int
recordedBaggage []map[string]string
}
func newBaggageItemsPreservationTest() *baggageItemsPreservationTest {
return &baggageItemsPreservationTest{
baggageItems: []bipBaggage{
{
key: "First",
value: "one",
},
{
key: "Second",
value: "two",
},
{
key: "Third",
value: "three",
},
},
}
}
func (bip *baggageItemsPreservationTest) setup(t *testing.T, tracer *internal.MockTracer) {
bip.step = 0
bip.recordedBaggage = nil
}
func (bip *baggageItemsPreservationTest) check(t *testing.T, tracer *internal.MockTracer) {
if len(bip.recordedBaggage) != len(bip.baggageItems) {
t.Errorf("Expected %d recordings, got %d", len(bip.baggageItems), len(bip.recordedBaggage))
}
minLen := min(len(bip.recordedBaggage), len(bip.baggageItems))
for i := 0; i < minLen; i++ {
recordedItems := bip.recordedBaggage[i]
if len(recordedItems) != i+1 {
t.Errorf("Expected %d recorded baggage items in recording %d, got %d", i+1, i+1, len(bip.recordedBaggage[i]))
}
minItemLen := min(len(bip.baggageItems), i+1)
for j := 0; j < minItemLen; j++ {
expectedItem := bip.baggageItems[j]
if gotValue, ok := recordedItems[expectedItem.key]; !ok {
t.Errorf("Missing baggage item %q in recording %d", expectedItem.key, i+1)
} else if gotValue != expectedItem.value {
t.Errorf("Expected recorded baggage item %q in recording %d + 1to be %q, got %q", expectedItem.key, i, expectedItem.value, gotValue)
} else {
delete(recordedItems, expectedItem.key)
}
}
for key, value := range recordedItems {
t.Errorf("Unexpected baggage item in recording %d: %q -> %q", i+1, key, value)
}
}
}
func (bip *baggageItemsPreservationTest) runOtelOTOtel(t *testing.T, ctx context.Context) {
runOtelOTOtel(t, ctx, "bip", bip.addAndRecordBaggage)
}
func (bip *baggageItemsPreservationTest) runOTOtelOT(t *testing.T, ctx context.Context) {
runOTOtelOT(t, ctx, "bip", bip.addAndRecordBaggage)
}
func (bip *baggageItemsPreservationTest) addAndRecordBaggage(t *testing.T, ctx context.Context) context.Context {
if bip.step >= len(bip.baggageItems) {
t.Errorf("Too many steps?")
return ctx
}
span := ot.SpanFromContext(ctx)
if span == nil {
t.Errorf("No active OpenTracing span")
return ctx
}
idx := bip.step
bip.step++
span.SetBaggageItem(bip.baggageItems[idx].key, bip.baggageItems[idx].value)
sctx := span.Context()
recording := make(map[string]string)
sctx.ForeachBaggageItem(func(key, value string) bool {
recording[key] = value
return true
})
bip.recordedBaggage = append(bip.recordedBaggage, recording)
return ctx
}
// baggage interoperation test
type baggageInteroperationTest struct {
baggageItems []bipBaggage
step int
recordedOTBaggage []map[string]string
recordedOtelBaggage []map[string]string
}
func newBaggageInteroperationTest() *baggageInteroperationTest {
return &baggageInteroperationTest{
baggageItems: []bipBaggage{
{
key: "First",
value: "one",
},
{
key: "Second",
value: "two",
},
{
key: "Third",
value: "three",
},
},
}
}
func (bio *baggageInteroperationTest) setup(t *testing.T, tracer *internal.MockTracer) {
bio.step = 0
bio.recordedOTBaggage = nil
bio.recordedOtelBaggage = nil
}
func (bio *baggageInteroperationTest) check(t *testing.T, tracer *internal.MockTracer) {
checkBIORecording(t, "OT", bio.baggageItems, bio.recordedOTBaggage)
checkBIORecording(t, "Otel", bio.baggageItems, bio.recordedOtelBaggage)
}
func checkBIORecording(t *testing.T, apiDesc string, initialItems []bipBaggage, recordings []map[string]string) {
// expect recordings count to equal the number of initial
// items
// each recording should have a duplicated item from initial
// items, one with OT suffix, another one with Otel suffix
// expect each subsequent recording to have two more items, up
// to double of the count of the initial items
if len(initialItems) != len(recordings) {
t.Errorf("Expected %d recordings from %s, got %d", len(initialItems), apiDesc, len(recordings))
}
minRecLen := min(len(initialItems), len(recordings))
for i := 0; i < minRecLen; i++ {
recordedItems := recordings[i]
expectedItemsInStep := (i + 1) * 2
if expectedItemsInStep != len(recordedItems) {
t.Errorf("Expected %d recorded items in recording %d from %s, got %d", expectedItemsInStep, i, apiDesc, len(recordedItems))
}
recordedItemsCopy := make(map[string]string, len(recordedItems))
for k, v := range recordedItems {
recordedItemsCopy[k] = v
}
for j := 0; j < i+1; j++ {
otKey, otelKey := generateBaggageKeys(initialItems[j].key)
value := initialItems[j].value
for _, k := range []string{otKey, otelKey} {
if v, ok := recordedItemsCopy[k]; ok {
if value != v {
t.Errorf("Expected value %s under key %s in recording %d from %s, got %s", value, k, i, apiDesc, v)
}
delete(recordedItemsCopy, k)
} else {
t.Errorf("Missing key %s in recording %d from %s", k, i, apiDesc)
}
}
}
for k, v := range recordedItemsCopy {
t.Errorf("Unexpected key-value pair %s = %s in recording %d from %s", k, v, i, apiDesc)
}
}
}
func (bio *baggageInteroperationTest) runOtelOTOtel(t *testing.T, ctx context.Context) {
runOtelOTOtel(t, ctx, "bio", bio.addAndRecordBaggage)
}
func (bio *baggageInteroperationTest) runOTOtelOT(t *testing.T, ctx context.Context) {
runOTOtelOT(t, ctx, "bio", bio.addAndRecordBaggage)
}
func (bio *baggageInteroperationTest) addAndRecordBaggage(t *testing.T, ctx context.Context) context.Context {
if bio.step >= len(bio.baggageItems) {
t.Errorf("Too many steps?")
return ctx
}
otSpan := ot.SpanFromContext(ctx)
if otSpan == nil {
t.Errorf("No active OpenTracing span")
return ctx
}
idx := bio.step
bio.step++
key := bio.baggageItems[idx].key
otKey, otelKey := generateBaggageKeys(key)
value := bio.baggageItems[idx].value
otSpan.SetBaggageItem(otKey, value)
m, err := baggage.NewMember(otelKey, value)
if err != nil {
t.Error(err)
return ctx
}
b, err := baggage.FromContext(ctx).SetMember(m)
if err != nil {
t.Error(err)
return ctx
}
ctx = baggage.ContextWithBaggage(ctx, b)
otRecording := make(map[string]string)
otSpan.Context().ForeachBaggageItem(func(key, value string) bool {
otRecording[key] = value
return true
})
otelRecording := make(map[string]string)
for _, m := range baggage.FromContext(ctx).Members() {
otelRecording[m.Key()] = m.Value()
}
bio.recordedOTBaggage = append(bio.recordedOTBaggage, otRecording)
bio.recordedOtelBaggage = append(bio.recordedOtelBaggage, otelRecording)
return ctx
}
func generateBaggageKeys(key string) (otKey, otelKey string) {
otKey, otelKey = key+"-Ot", key+"-Otel"
return
}
// helpers
func checkTraceAndSpans(t *testing.T, tracer *internal.MockTracer, expectedTraceID trace.TraceID, expectedSpanIDs []trace.SpanID) {
expectedSpanCount := len(expectedSpanIDs)
// reverse spanIDs, since first span ID belongs to root, that
// finishes last
spanIDs := make([]trace.SpanID, len(expectedSpanIDs))
copy(spanIDs, expectedSpanIDs)
reverse(len(spanIDs), func(i, j int) {
spanIDs[i], spanIDs[j] = spanIDs[j], spanIDs[i]
})
// the last finished span has no parent
parentSpanIDs := append(spanIDs[1:], trace.SpanID{})
sks := map[trace.SpanID]trace.SpanKind{
{125}: trace.SpanKindProducer,
{124}: trace.SpanKindInternal,
{123}: trace.SpanKindClient,
}
if len(tracer.FinishedSpans) != expectedSpanCount {
t.Errorf("Expected %d finished spans, got %d", expectedSpanCount, len(tracer.FinishedSpans))
}
for idx, span := range tracer.FinishedSpans {
sctx := span.SpanContext()
if sctx.TraceID() != expectedTraceID {
t.Errorf("Expected trace ID %v in span %d (%d), got %v", expectedTraceID, idx, sctx.SpanID(), sctx.TraceID())
}
expectedSpanID := spanIDs[idx]
expectedParentSpanID := parentSpanIDs[idx]
if sctx.SpanID() != expectedSpanID {
t.Errorf("Expected finished span %d to have span ID %d, but got %d", idx, expectedSpanID, sctx.SpanID())
}
if span.ParentSpanID != expectedParentSpanID {
t.Errorf("Expected finished span %d (span ID: %d) to have parent span ID %d, but got %d", idx, sctx.SpanID(), expectedParentSpanID, span.ParentSpanID)
}
if span.SpanKind != sks[span.SpanContext().SpanID()] {
t.Errorf("Expected finished span %d (span ID: %d) to have span.kind to be '%v' but was '%v'", idx, sctx.SpanID(), sks[span.SpanContext().SpanID()], span.SpanKind)
}
}
}
func reverse(length int, swap func(i, j int)) {
for left, right := 0, length-1; left < right; left, right = left+1, right-1 {
swap(left, right)
}
}
func simpleTraceID() trace.TraceID {
return [16]byte{123, 42}
}
func simpleSpanIDs(count int) []trace.SpanID {
base := []trace.SpanID{
{123},
{124},
{125},
{126},
{127},
{128},
}
return base[:count]
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
func runOtelOTOtel(t *testing.T, ctx context.Context, name string, callback func(*testing.T, context.Context) context.Context) {
tr := otel.Tracer("")
ctx, span := tr.Start(ctx, fmt.Sprintf("%s_Otel_OTOtel", name), trace.WithSpanKind(trace.SpanKindClient))
defer span.End()
ctx = callback(t, ctx)
func(ctx2 context.Context) {
span, ctx2 := ot.StartSpanFromContext(ctx2, fmt.Sprintf("%sOtel_OT_Otel", name))
defer span.Finish()
ctx2 = callback(t, ctx2)
func(ctx3 context.Context) {
ctx3, span := tr.Start(ctx3, fmt.Sprintf("%sOtelOT_Otel_", name), trace.WithSpanKind(trace.SpanKindProducer))
defer span.End()
_ = callback(t, ctx3)
}(ctx2)
}(ctx)
}
func runOTOtelOT(t *testing.T, ctx context.Context, name string, callback func(*testing.T, context.Context) context.Context) {
tr := otel.Tracer("")
span, ctx := ot.StartSpanFromContext(ctx, fmt.Sprintf("%s_OT_OtelOT", name), ot.Tag{Key: "span.kind", Value: "client"})
defer span.Finish()
ctx = callback(t, ctx)
func(ctx2 context.Context) {
ctx2, span := tr.Start(ctx2, fmt.Sprintf("%sOT_Otel_OT", name))
defer span.End()
ctx2 = callback(t, ctx2)
func(ctx3 context.Context) {
span, ctx3 := ot.StartSpanFromContext(ctx3, fmt.Sprintf("%sOTOtel_OT_", name), ot.Tag{Key: "span.kind", Value: "producer"})
defer span.Finish()
_ = callback(t, ctx3)
}(ctx2)
}(ctx)
}
func TestOtTagToOTelLabel_CheckTypeConversions(t *testing.T) {
tableTest := []struct {
key string
value interface{}
expectedValueType attribute.Type
}{
{
key: "bool to bool",
value: true,
expectedValueType: attribute.BOOL,
},
{
key: "int to int64",
value: 123,
expectedValueType: attribute.INT64,
},
{
key: "uint to string",
value: uint(1234),
expectedValueType: attribute.STRING,
},
{
key: "int32 to int64",
value: int32(12345),
expectedValueType: attribute.INT64,
},
{
key: "uint32 to int64",
value: uint32(123456),
expectedValueType: attribute.INT64,
},
{
key: "int64 to int64",
value: int64(1234567),
expectedValueType: attribute.INT64,
},
{
key: "uint64 to string",
value: uint64(12345678),
expectedValueType: attribute.STRING,
},
{
key: "float32 to float64",
value: float32(3.14),
expectedValueType: attribute.FLOAT64,
},
{
key: "float64 to float64",
value: float64(3.14),
expectedValueType: attribute.FLOAT64,
},
{
key: "string to string",
value: "string_value",
expectedValueType: attribute.STRING,
},
{
key: "unexpected type to string",
value: struct{}{},
expectedValueType: attribute.STRING,
},
}
for _, test := range tableTest {
got := otTagToOTelLabel(test.key, test.value)
if test.expectedValueType != got.Value.Type() {
t.Errorf("Expected type %s, but got %s after conversion '%v' value",
test.expectedValueType,
got.Value.Type(),
test.value)
}
}
}