You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	* Fix Jaeger exporter status conversion The status tag needs to be a string of value `ERROR` or `OK` for Error and Ok statuses respectively according to the OTel spec. * Add PR number to changelog
		
			
				
	
	
		
			724 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			724 lines
		
	
	
		
			19 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 jaeger
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/binary"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"sort"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/google/go-cmp/cmp"
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 
 | |
| 	"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"
 | |
| 	"go.opentelemetry.io/otel/sdk/instrumentation"
 | |
| 	"go.opentelemetry.io/otel/sdk/resource"
 | |
| 	sdktrace "go.opentelemetry.io/otel/sdk/trace"
 | |
| 	"go.opentelemetry.io/otel/sdk/trace/tracetest"
 | |
| 	semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
 | |
| 	"go.opentelemetry.io/otel/trace"
 | |
| )
 | |
| 
 | |
| func TestNewRawExporter(t *testing.T) {
 | |
| 	testCases := []struct {
 | |
| 		name     string
 | |
| 		endpoint EndpointOption
 | |
| 	}{
 | |
| 		{
 | |
| 			name:     "default exporter with collector endpoint",
 | |
| 			endpoint: WithCollectorEndpoint(),
 | |
| 		},
 | |
| 		{
 | |
| 			name:     "default exporter with agent endpoint",
 | |
| 			endpoint: WithAgentEndpoint(),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			_, err := New(tc.endpoint)
 | |
| 			assert.NoError(t, err)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestNewRawExporterUseEnvVarIfOptionUnset(t *testing.T) {
 | |
| 	// Record and restore env
 | |
| 	envStore := ottest.NewEnvStore()
 | |
| 	envStore.Record(envEndpoint)
 | |
| 	defer func() {
 | |
| 		require.NoError(t, envStore.Restore())
 | |
| 	}()
 | |
| 
 | |
| 	// If the user sets the environment variable OTEL_EXPORTER_JAEGER_ENDPOINT, endpoint will always get a value.
 | |
| 	require.NoError(t, os.Unsetenv(envEndpoint))
 | |
| 	_, err := New(
 | |
| 		WithCollectorEndpoint(),
 | |
| 	)
 | |
| 
 | |
| 	assert.NoError(t, err)
 | |
| }
 | |
| 
 | |
| type testCollectorEndpoint struct {
 | |
| 	batchesUploaded []*gen.Batch
 | |
| }
 | |
| 
 | |
| func (c *testCollectorEndpoint) shutdown(context.Context) error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *testCollectorEndpoint) upload(_ context.Context, batch *gen.Batch) error {
 | |
| 	c.batchesUploaded = append(c.batchesUploaded, batch)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var _ batchUploader = (*testCollectorEndpoint)(nil)
 | |
| 
 | |
| func withTestCollectorEndpoint() EndpointOption {
 | |
| 	return endpointOptionFunc(func() (batchUploader, error) {
 | |
| 		return &testCollectorEndpoint{}, nil
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func withTestCollectorEndpointInjected(ce *testCollectorEndpoint) EndpointOption {
 | |
| 	return endpointOptionFunc(func() (batchUploader, error) {
 | |
| 		return ce, nil
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func TestExporterExportSpan(t *testing.T) {
 | |
| 	const (
 | |
| 		serviceName = "test-service"
 | |
| 		tagKey      = "key"
 | |
| 		tagVal      = "val"
 | |
| 	)
 | |
| 
 | |
| 	testCollector := &testCollectorEndpoint{}
 | |
| 	exp, err := New(withTestCollectorEndpointInjected(testCollector))
 | |
| 	require.NoError(t, err)
 | |
| 	tp := sdktrace.NewTracerProvider(
 | |
| 		sdktrace.WithBatcher(exp),
 | |
| 		sdktrace.WithResource(resource.NewSchemaless(
 | |
| 			semconv.ServiceNameKey.String(serviceName),
 | |
| 			attribute.String(tagKey, tagVal),
 | |
| 		)),
 | |
| 	)
 | |
| 
 | |
| 	tracer := tp.Tracer("test-tracer")
 | |
| 
 | |
| 	ctx := context.Background()
 | |
| 	for i := 0; i < 3; i++ {
 | |
| 		_, span := tracer.Start(ctx, fmt.Sprintf("test-span-%d", i))
 | |
| 		span.End()
 | |
| 		assert.True(t, span.SpanContext().IsValid())
 | |
| 	}
 | |
| 
 | |
| 	require.NoError(t, tp.Shutdown(ctx))
 | |
| 
 | |
| 	batchesUploaded := testCollector.batchesUploaded
 | |
| 	require.Len(t, batchesUploaded, 1)
 | |
| 	uploadedBatch := batchesUploaded[0]
 | |
| 	assert.Equal(t, serviceName, uploadedBatch.GetProcess().GetServiceName())
 | |
| 	assert.Len(t, uploadedBatch.GetSpans(), 3)
 | |
| 
 | |
| 	require.Len(t, uploadedBatch.GetProcess().GetTags(), 1)
 | |
| 	assert.Equal(t, tagKey, uploadedBatch.GetProcess().GetTags()[0].GetKey())
 | |
| 	assert.Equal(t, tagVal, uploadedBatch.GetProcess().GetTags()[0].GetVStr())
 | |
| }
 | |
| 
 | |
| func Test_spanSnapshotToThrift(t *testing.T) {
 | |
| 	now := time.Now()
 | |
| 	traceID, _ := trace.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
 | |
| 	spanID, _ := trace.SpanIDFromHex("0102030405060708")
 | |
| 	parentSpanID, _ := trace.SpanIDFromHex("0807060504030201")
 | |
| 
 | |
| 	linkTraceID, _ := trace.TraceIDFromHex("0102030405060709090a0b0c0d0e0f11")
 | |
| 	linkSpanID, _ := trace.SpanIDFromHex("0102030405060709")
 | |
| 
 | |
| 	eventNameValue := "event-test"
 | |
| 	eventDropped := int64(10)
 | |
| 	keyValue := "value"
 | |
| 	statusCodeValue := "ERROR"
 | |
| 	doubleValue := 123.456
 | |
| 	intValue := int64(123)
 | |
| 	boolTrue := true
 | |
| 	arrValue := "[0,1,2,3]"
 | |
| 	statusMessage := "this is a problem"
 | |
| 	spanKind := "client"
 | |
| 	rv1 := "rv11"
 | |
| 	rv2 := int64(5)
 | |
| 	instrLibName := "instrumentation-library"
 | |
| 	instrLibVersion := "semver:1.0.0"
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		name string
 | |
| 		data tracetest.SpanStub
 | |
| 		want *gen.Span
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "no status description",
 | |
| 			data: tracetest.SpanStub{
 | |
| 				SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 					TraceID: traceID,
 | |
| 					SpanID:  spanID,
 | |
| 				}),
 | |
| 				Name:      "/foo",
 | |
| 				StartTime: now,
 | |
| 				EndTime:   now,
 | |
| 				Status:    sdktrace.Status{Code: codes.Error},
 | |
| 				SpanKind:  trace.SpanKindClient,
 | |
| 				InstrumentationLibrary: instrumentation.Library{
 | |
| 					Name:    instrLibName,
 | |
| 					Version: instrLibVersion,
 | |
| 				},
 | |
| 			},
 | |
| 			want: &gen.Span{
 | |
| 				TraceIdLow:    651345242494996240,
 | |
| 				TraceIdHigh:   72623859790382856,
 | |
| 				SpanId:        72623859790382856,
 | |
| 				OperationName: "/foo",
 | |
| 				StartTime:     now.UnixNano() / 1000,
 | |
| 				Duration:      0,
 | |
| 				Tags: []*gen.Tag{
 | |
| 					{Key: keyError, VType: gen.TagType_BOOL, VBool: &boolTrue},
 | |
| 					{Key: keyInstrumentationLibraryName, VType: gen.TagType_STRING, VStr: &instrLibName},
 | |
| 					{Key: keyInstrumentationLibraryVersion, VType: gen.TagType_STRING, VStr: &instrLibVersion},
 | |
| 					{Key: keyStatusCode, VType: gen.TagType_STRING, VStr: &statusCodeValue},
 | |
| 					// Should not have a status message because it was unset
 | |
| 					{Key: keySpanKind, VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "no parent",
 | |
| 			data: tracetest.SpanStub{
 | |
| 				SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 					TraceID: traceID,
 | |
| 					SpanID:  spanID,
 | |
| 				}),
 | |
| 				Name:      "/foo",
 | |
| 				StartTime: now,
 | |
| 				EndTime:   now,
 | |
| 				Links: []sdktrace.Link{
 | |
| 					{
 | |
| 						SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 							TraceID: linkTraceID,
 | |
| 							SpanID:  linkSpanID,
 | |
| 						}),
 | |
| 					},
 | |
| 				},
 | |
| 				Attributes: []attribute.KeyValue{
 | |
| 					attribute.String("key", keyValue),
 | |
| 					attribute.Float64("double", doubleValue),
 | |
| 					attribute.Int64("int", intValue),
 | |
| 				},
 | |
| 				Events: []sdktrace.Event{
 | |
| 					{
 | |
| 						Name:                  eventNameValue,
 | |
| 						Attributes:            []attribute.KeyValue{attribute.String("k1", keyValue)},
 | |
| 						DroppedAttributeCount: int(eventDropped),
 | |
| 						Time:                  now,
 | |
| 					},
 | |
| 				},
 | |
| 				Status: sdktrace.Status{
 | |
| 					Code:        codes.Error,
 | |
| 					Description: statusMessage,
 | |
| 				},
 | |
| 				SpanKind: trace.SpanKindClient,
 | |
| 				InstrumentationLibrary: instrumentation.Library{
 | |
| 					Name:    instrLibName,
 | |
| 					Version: instrLibVersion,
 | |
| 				},
 | |
| 			},
 | |
| 			want: &gen.Span{
 | |
| 				TraceIdLow:    651345242494996240,
 | |
| 				TraceIdHigh:   72623859790382856,
 | |
| 				SpanId:        72623859790382856,
 | |
| 				OperationName: "/foo",
 | |
| 				StartTime:     now.UnixNano() / 1000,
 | |
| 				Duration:      0,
 | |
| 				Tags: []*gen.Tag{
 | |
| 					{Key: "double", VType: gen.TagType_DOUBLE, VDouble: &doubleValue},
 | |
| 					{Key: "key", VType: gen.TagType_STRING, VStr: &keyValue},
 | |
| 					{Key: "int", VType: gen.TagType_LONG, VLong: &intValue},
 | |
| 					{Key: keyError, VType: gen.TagType_BOOL, VBool: &boolTrue},
 | |
| 					{Key: keyInstrumentationLibraryName, VType: gen.TagType_STRING, VStr: &instrLibName},
 | |
| 					{Key: keyInstrumentationLibraryVersion, VType: gen.TagType_STRING, VStr: &instrLibVersion},
 | |
| 					{Key: keyStatusCode, VType: gen.TagType_STRING, VStr: &statusCodeValue},
 | |
| 					{Key: keyStatusMessage, VType: gen.TagType_STRING, VStr: &statusMessage},
 | |
| 					{Key: keySpanKind, VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 				},
 | |
| 				References: []*gen.SpanRef{
 | |
| 					{
 | |
| 						RefType:     gen.SpanRefType_FOLLOWS_FROM,
 | |
| 						TraceIdHigh: int64(binary.BigEndian.Uint64(linkTraceID[0:8])),
 | |
| 						TraceIdLow:  int64(binary.BigEndian.Uint64(linkTraceID[8:16])),
 | |
| 						SpanId:      int64(binary.BigEndian.Uint64(linkSpanID[:])),
 | |
| 					},
 | |
| 				},
 | |
| 				Logs: []*gen.Log{
 | |
| 					{
 | |
| 						Timestamp: now.UnixNano() / 1000,
 | |
| 						Fields: []*gen.Tag{
 | |
| 							{
 | |
| 								Key:   keyEventName,
 | |
| 								VStr:  &eventNameValue,
 | |
| 								VType: gen.TagType_STRING,
 | |
| 							},
 | |
| 							{
 | |
| 								Key:   "k1",
 | |
| 								VStr:  &keyValue,
 | |
| 								VType: gen.TagType_STRING,
 | |
| 							},
 | |
| 							{
 | |
| 								Key:   keyDroppedAttributeCount,
 | |
| 								VLong: &eventDropped,
 | |
| 								VType: gen.TagType_LONG,
 | |
| 							},
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "with parent",
 | |
| 			data: tracetest.SpanStub{
 | |
| 				SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 					TraceID: traceID,
 | |
| 					SpanID:  spanID,
 | |
| 				}),
 | |
| 				Parent: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 					TraceID: traceID,
 | |
| 					SpanID:  parentSpanID,
 | |
| 				}),
 | |
| 				Links: []sdktrace.Link{
 | |
| 					{
 | |
| 						SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 							TraceID: linkTraceID,
 | |
| 							SpanID:  linkSpanID,
 | |
| 						}),
 | |
| 					},
 | |
| 				},
 | |
| 				Name:      "/foo",
 | |
| 				StartTime: now,
 | |
| 				EndTime:   now,
 | |
| 				Attributes: []attribute.KeyValue{
 | |
| 					attribute.IntSlice("arr", []int{0, 1, 2, 3}),
 | |
| 				},
 | |
| 				Status: sdktrace.Status{
 | |
| 					Code:        codes.Unset,
 | |
| 					Description: statusMessage,
 | |
| 				},
 | |
| 				SpanKind: trace.SpanKindInternal,
 | |
| 				InstrumentationLibrary: instrumentation.Library{
 | |
| 					Name:    instrLibName,
 | |
| 					Version: instrLibVersion,
 | |
| 				},
 | |
| 			},
 | |
| 			want: &gen.Span{
 | |
| 				TraceIdLow:    651345242494996240,
 | |
| 				TraceIdHigh:   72623859790382856,
 | |
| 				SpanId:        72623859790382856,
 | |
| 				ParentSpanId:  578437695752307201,
 | |
| 				OperationName: "/foo",
 | |
| 				StartTime:     now.UnixNano() / 1000,
 | |
| 				Duration:      0,
 | |
| 				Tags: []*gen.Tag{
 | |
| 					// status code, message and span kind should NOT be populated
 | |
| 					{Key: "arr", VType: gen.TagType_STRING, VStr: &arrValue},
 | |
| 					{Key: keyInstrumentationLibraryName, VType: gen.TagType_STRING, VStr: &instrLibName},
 | |
| 					{Key: keyInstrumentationLibraryVersion, VType: gen.TagType_STRING, VStr: &instrLibVersion},
 | |
| 				},
 | |
| 				References: []*gen.SpanRef{
 | |
| 					{
 | |
| 						RefType:     gen.SpanRefType_FOLLOWS_FROM,
 | |
| 						TraceIdHigh: int64(binary.BigEndian.Uint64(linkTraceID[0:8])),
 | |
| 						TraceIdLow:  int64(binary.BigEndian.Uint64(linkTraceID[8:16])),
 | |
| 						SpanId:      int64(binary.BigEndian.Uint64(linkSpanID[:])),
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "resources do not affect the tags",
 | |
| 			data: tracetest.SpanStub{
 | |
| 				SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 					TraceID: traceID,
 | |
| 					SpanID:  spanID,
 | |
| 				}),
 | |
| 				Parent: trace.NewSpanContext(trace.SpanContextConfig{
 | |
| 					TraceID: traceID,
 | |
| 					SpanID:  parentSpanID,
 | |
| 				}),
 | |
| 				Name:      "/foo",
 | |
| 				StartTime: now,
 | |
| 				EndTime:   now,
 | |
| 				Resource: resource.NewSchemaless(
 | |
| 					attribute.String("rk1", rv1),
 | |
| 					attribute.Int64("rk2", rv2),
 | |
| 					semconv.ServiceNameKey.String("service name"),
 | |
| 				),
 | |
| 				Status: sdktrace.Status{
 | |
| 					Code:        codes.Unset,
 | |
| 					Description: statusMessage,
 | |
| 				},
 | |
| 				SpanKind: trace.SpanKindInternal,
 | |
| 				InstrumentationLibrary: instrumentation.Library{
 | |
| 					Name:    instrLibName,
 | |
| 					Version: instrLibVersion,
 | |
| 				},
 | |
| 			},
 | |
| 			want: &gen.Span{
 | |
| 				TraceIdLow:    651345242494996240,
 | |
| 				TraceIdHigh:   72623859790382856,
 | |
| 				SpanId:        72623859790382856,
 | |
| 				ParentSpanId:  578437695752307201,
 | |
| 				OperationName: "/foo",
 | |
| 				StartTime:     now.UnixNano() / 1000,
 | |
| 				Duration:      0,
 | |
| 				Tags: []*gen.Tag{
 | |
| 					{Key: keyInstrumentationLibraryName, VType: gen.TagType_STRING, VStr: &instrLibName},
 | |
| 					{Key: keyInstrumentationLibraryVersion, VType: gen.TagType_STRING, VStr: &instrLibVersion},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 	for _, tt := range tests {
 | |
| 		t.Run(tt.name, func(t *testing.T) {
 | |
| 			got := spanToThrift(tt.data.Snapshot())
 | |
| 			sort.Slice(got.Tags, func(i, j int) bool {
 | |
| 				return got.Tags[i].Key < got.Tags[j].Key
 | |
| 			})
 | |
| 			sort.Slice(tt.want.Tags, func(i, j int) bool {
 | |
| 				return tt.want.Tags[i].Key < tt.want.Tags[j].Key
 | |
| 			})
 | |
| 			if diff := cmp.Diff(got, tt.want); diff != "" {
 | |
| 				t.Errorf("Diff%v", diff)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestExporterShutdownHonorsCancel(t *testing.T) {
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	cancel()
 | |
| 
 | |
| 	e, err := New(withTestCollectorEndpoint())
 | |
| 	require.NoError(t, err)
 | |
| 	assert.EqualError(t, e.Shutdown(ctx), context.Canceled.Error())
 | |
| }
 | |
| 
 | |
| func TestExporterShutdownHonorsTimeout(t *testing.T) {
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
 | |
| 	<-ctx.Done()
 | |
| 
 | |
| 	e, err := New(withTestCollectorEndpoint())
 | |
| 	require.NoError(t, err)
 | |
| 	assert.EqualError(t, e.Shutdown(ctx), context.DeadlineExceeded.Error())
 | |
| 	cancel()
 | |
| }
 | |
| 
 | |
| func TestErrorOnExportShutdownExporter(t *testing.T) {
 | |
| 	e, err := New(withTestCollectorEndpoint())
 | |
| 	require.NoError(t, err)
 | |
| 	assert.NoError(t, e.Shutdown(context.Background()))
 | |
| 	assert.NoError(t, e.ExportSpans(context.Background(), nil))
 | |
| }
 | |
| 
 | |
| func TestExporterExportSpansHonorsCancel(t *testing.T) {
 | |
| 	e, err := New(withTestCollectorEndpoint())
 | |
| 	require.NoError(t, err)
 | |
| 	now := time.Now()
 | |
| 	ss := tracetest.SpanStubs{
 | |
| 		{
 | |
| 			Name: "s1",
 | |
| 			Resource: resource.NewSchemaless(
 | |
| 				semconv.ServiceNameKey.String("name"),
 | |
| 				attribute.Key("r1").String("v1"),
 | |
| 			),
 | |
| 			StartTime: now,
 | |
| 			EndTime:   now,
 | |
| 		},
 | |
| 		{
 | |
| 			Name: "s2",
 | |
| 			Resource: resource.NewSchemaless(
 | |
| 				semconv.ServiceNameKey.String("name"),
 | |
| 				attribute.Key("r2").String("v2"),
 | |
| 			),
 | |
| 			StartTime: now,
 | |
| 			EndTime:   now,
 | |
| 		},
 | |
| 	}
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	cancel()
 | |
| 
 | |
| 	assert.EqualError(t, e.ExportSpans(ctx, ss.Snapshots()), context.Canceled.Error())
 | |
| }
 | |
| 
 | |
| func TestExporterExportSpansHonorsTimeout(t *testing.T) {
 | |
| 	e, err := New(withTestCollectorEndpoint())
 | |
| 	require.NoError(t, err)
 | |
| 	now := time.Now()
 | |
| 	ss := tracetest.SpanStubs{
 | |
| 		{
 | |
| 			Name: "s1",
 | |
| 			Resource: resource.NewSchemaless(
 | |
| 				semconv.ServiceNameKey.String("name"),
 | |
| 				attribute.Key("r1").String("v1"),
 | |
| 			),
 | |
| 			StartTime: now,
 | |
| 			EndTime:   now,
 | |
| 		},
 | |
| 		{
 | |
| 			Name: "s2",
 | |
| 			Resource: resource.NewSchemaless(
 | |
| 				semconv.ServiceNameKey.String("name"),
 | |
| 				attribute.Key("r2").String("v2"),
 | |
| 			),
 | |
| 			StartTime: now,
 | |
| 			EndTime:   now,
 | |
| 		},
 | |
| 	}
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
 | |
| 	defer cancel()
 | |
| 	<-ctx.Done()
 | |
| 
 | |
| 	assert.EqualError(t, e.ExportSpans(ctx, ss.Snapshots()), context.DeadlineExceeded.Error())
 | |
| }
 | |
| 
 | |
| func TestJaegerBatchList(t *testing.T) {
 | |
| 	newString := func(value string) *string {
 | |
| 		return &value
 | |
| 	}
 | |
| 	spanKind := "unspecified"
 | |
| 	now := time.Now()
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name               string
 | |
| 		roSpans            []sdktrace.ReadOnlySpan
 | |
| 		defaultServiceName string
 | |
| 		expectedBatchList  []*gen.Batch
 | |
| 	}{
 | |
| 		{
 | |
| 			name:              "no span shots",
 | |
| 			roSpans:           nil,
 | |
| 			expectedBatchList: nil,
 | |
| 		},
 | |
| 		{
 | |
| 			name: "span's snapshot contains nil span",
 | |
| 			roSpans: []sdktrace.ReadOnlySpan{
 | |
| 				tracetest.SpanStub{
 | |
| 					Name: "s1",
 | |
| 					Resource: resource.NewSchemaless(
 | |
| 						semconv.ServiceNameKey.String("name"),
 | |
| 						attribute.Key("r1").String("v1"),
 | |
| 					),
 | |
| 					StartTime: now,
 | |
| 					EndTime:   now,
 | |
| 				}.Snapshot(),
 | |
| 				nil,
 | |
| 			},
 | |
| 			expectedBatchList: []*gen.Batch{
 | |
| 				{
 | |
| 					Process: &gen.Process{
 | |
| 						ServiceName: "name",
 | |
| 						Tags: []*gen.Tag{
 | |
| 							{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
 | |
| 						},
 | |
| 					},
 | |
| 					Spans: []*gen.Span{
 | |
| 						{
 | |
| 							OperationName: "s1",
 | |
| 							Tags: []*gen.Tag{
 | |
| 								{Key: keySpanKind, VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 							},
 | |
| 							StartTime: now.UnixNano() / 1000,
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "merge spans that have the same resources",
 | |
| 			roSpans: tracetest.SpanStubs{
 | |
| 				{
 | |
| 					Name: "s1",
 | |
| 					Resource: resource.NewSchemaless(
 | |
| 						semconv.ServiceNameKey.String("name"),
 | |
| 						attribute.Key("r1").String("v1"),
 | |
| 					),
 | |
| 					StartTime: now,
 | |
| 					EndTime:   now,
 | |
| 				},
 | |
| 				{
 | |
| 					Name: "s2",
 | |
| 					Resource: resource.NewSchemaless(
 | |
| 						semconv.ServiceNameKey.String("name"),
 | |
| 						attribute.Key("r1").String("v1"),
 | |
| 					),
 | |
| 					StartTime: now,
 | |
| 					EndTime:   now,
 | |
| 				},
 | |
| 				{
 | |
| 					Name: "s3",
 | |
| 					Resource: resource.NewSchemaless(
 | |
| 						semconv.ServiceNameKey.String("name"),
 | |
| 						attribute.Key("r2").String("v2"),
 | |
| 					),
 | |
| 					StartTime: now,
 | |
| 					EndTime:   now,
 | |
| 				},
 | |
| 			}.Snapshots(),
 | |
| 			expectedBatchList: []*gen.Batch{
 | |
| 				{
 | |
| 					Process: &gen.Process{
 | |
| 						ServiceName: "name",
 | |
| 						Tags: []*gen.Tag{
 | |
| 							{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
 | |
| 						},
 | |
| 					},
 | |
| 					Spans: []*gen.Span{
 | |
| 						{
 | |
| 							OperationName: "s1",
 | |
| 							Tags: []*gen.Tag{
 | |
| 								{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 							},
 | |
| 							StartTime: now.UnixNano() / 1000,
 | |
| 						},
 | |
| 						{
 | |
| 							OperationName: "s2",
 | |
| 							Tags: []*gen.Tag{
 | |
| 								{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 							},
 | |
| 							StartTime: now.UnixNano() / 1000,
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 				{
 | |
| 					Process: &gen.Process{
 | |
| 						ServiceName: "name",
 | |
| 						Tags: []*gen.Tag{
 | |
| 							{Key: "r2", VType: gen.TagType_STRING, VStr: newString("v2")},
 | |
| 						},
 | |
| 					},
 | |
| 					Spans: []*gen.Span{
 | |
| 						{
 | |
| 							OperationName: "s3",
 | |
| 							Tags: []*gen.Tag{
 | |
| 								{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 							},
 | |
| 							StartTime: now.UnixNano() / 1000,
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name: "no service name in spans",
 | |
| 			roSpans: tracetest.SpanStubs{
 | |
| 				{
 | |
| 					Name: "s1",
 | |
| 					Resource: resource.NewSchemaless(
 | |
| 						attribute.Key("r1").String("v1"),
 | |
| 					),
 | |
| 					StartTime: now,
 | |
| 					EndTime:   now,
 | |
| 				},
 | |
| 			}.Snapshots(),
 | |
| 			defaultServiceName: "default service name",
 | |
| 			expectedBatchList: []*gen.Batch{
 | |
| 				{
 | |
| 					Process: &gen.Process{
 | |
| 						ServiceName: "default service name",
 | |
| 						Tags: []*gen.Tag{
 | |
| 							{Key: "r1", VType: gen.TagType_STRING, VStr: newString("v1")},
 | |
| 						},
 | |
| 					},
 | |
| 					Spans: []*gen.Span{
 | |
| 						{
 | |
| 							OperationName: "s1",
 | |
| 							Tags: []*gen.Tag{
 | |
| 								{Key: "span.kind", VType: gen.TagType_STRING, VStr: &spanKind},
 | |
| 							},
 | |
| 							StartTime: now.UnixNano() / 1000,
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			batchList := jaegerBatchList(tc.roSpans, tc.defaultServiceName)
 | |
| 
 | |
| 			assert.ElementsMatch(t, tc.expectedBatchList, batchList)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestProcess(t *testing.T) {
 | |
| 	v1 := "v1"
 | |
| 
 | |
| 	testCases := []struct {
 | |
| 		name               string
 | |
| 		res                *resource.Resource
 | |
| 		defaultServiceName string
 | |
| 		expectedProcess    *gen.Process
 | |
| 	}{
 | |
| 		{
 | |
| 			name: "resources contain service name",
 | |
| 			res: resource.NewSchemaless(
 | |
| 				semconv.ServiceNameKey.String("service name"),
 | |
| 				attribute.Key("r1").String("v1"),
 | |
| 			),
 | |
| 			defaultServiceName: "default service name",
 | |
| 			expectedProcess: &gen.Process{
 | |
| 				ServiceName: "service name",
 | |
| 				Tags: []*gen.Tag{
 | |
| 					{Key: "r1", VType: gen.TagType_STRING, VStr: &v1},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 		{
 | |
| 			name:               "resources don't have service name",
 | |
| 			res:                resource.NewSchemaless(attribute.Key("r1").String("v1")),
 | |
| 			defaultServiceName: "default service name",
 | |
| 			expectedProcess: &gen.Process{
 | |
| 				ServiceName: "default service name",
 | |
| 				Tags: []*gen.Tag{
 | |
| 					{Key: "r1", VType: gen.TagType_STRING, VStr: &v1},
 | |
| 				},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.name, func(t *testing.T) {
 | |
| 			pro := process(tc.res, tc.defaultServiceName)
 | |
| 
 | |
| 			assert.Equal(t, tc.expectedProcess, pro)
 | |
| 		})
 | |
| 	}
 | |
| }
 |