package opentracing_test
import (
"context"
"errors"
"fmt"
"reflect"
"testing"
"time"
"github.com/opentracing/opentracing-go"
otext "github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/sd"
"github.com/go-kit/kit/sd/lb"
kitot "github.com/go-kit/kit/tracing/opentracing"
)
const (
span1 = "SPAN-1"
span2 = "SPAN-2"
span3 = "SPAN-3"
span4 = "SPAN-4"
span5 = "SPAN-5"
span6 = "SPAN-6"
span7 = "SPAN-7"
span8 = "SPAN-8"
)
var (
err1 = errors.New("some error")
err2 = errors.New("some business error")
err3 = errors.New("other business error")
)
// compile time assertion
var _ endpoint.Failer = failedResponse{}
type failedResponse struct {
err error
}
func (r failedResponse) Failed() error {
return r.err
}
func TestTraceEndpoint(t *testing.T) {
tracer := mocktracer.New()
// Initialize the ctx with a parent Span.
parentSpan := tracer.StartSpan("parent").(*mocktracer.MockSpan)
defer parentSpan.Finish()
ctx := opentracing.ContextWithSpan(context.Background(), parentSpan)
tracedEndpoint := kitot.TraceEndpoint(tracer, "testOp")(endpoint.Nop)
if _, err := tracedEndpoint(ctx, struct{}{}); err != nil {
t.Fatal(err)
}
// tracedEndpoint created a new Span.
finishedSpans := tracer.FinishedSpans()
if want, have := 1, len(finishedSpans); want != have {
t.Fatalf("Want %v span(s), found %v", want, have)
}
endpointSpan := finishedSpans[0]
if want, have := "testOp", endpointSpan.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
parentContext := parentSpan.Context().(mocktracer.MockSpanContext)
endpointContext := parentSpan.Context().(mocktracer.MockSpanContext)
// ... and that the parent ID is set appropriately.
if want, have := parentContext.SpanID, endpointContext.SpanID; want != have {
t.Errorf("Want ParentID %q, have %q", want, have)
}
}
func TestTraceEndpointNoContextSpan(t *testing.T) {
tracer := mocktracer.New()
// Empty/background context.
tracedEndpoint := kitot.TraceEndpoint(tracer, "testOp")(endpoint.Nop)
if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil {
t.Fatal(err)
}
// tracedEndpoint created a new Span.
finishedSpans := tracer.FinishedSpans()
if want, have := 1, len(finishedSpans); want != have {
t.Fatalf("Want %v span(s), found %v", want, have)
}
endpointSpan := finishedSpans[0]
if want, have := "testOp", endpointSpan.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
}
func TestTraceEndpointWithOptions(t *testing.T) {
tracer := mocktracer.New()
// span 1 without options
mw := kitot.TraceEndpoint(tracer, span1)
tracedEndpoint := mw(endpoint.Nop)
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 2 with options
mw = kitot.TraceEndpoint(
tracer,
span2,
kitot.WithOptions(kitot.EndpointOptions{}),
)
tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) {
return nil, err1
})
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 3 with lb error
mw = kitot.TraceEndpoint(
tracer,
span3,
kitot.WithOptions(kitot.EndpointOptions{}),
)
tracedEndpoint = mw(
lb.Retry(
5,
1*time.Second,
lb.NewRoundRobin(
sd.FixedEndpointer{
func(context.Context, interface{}) (interface{}, error) {
return nil, err1
},
},
),
),
)
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 4 with disabled IgnoreBusinessError option
mw = kitot.TraceEndpoint(
tracer,
span4,
kitot.WithIgnoreBusinessError(false),
)
tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) {
return failedResponse{
err: err2,
}, nil
})
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 5 with enabled IgnoreBusinessError option
mw = kitot.TraceEndpoint(tracer, span5, kitot.WithIgnoreBusinessError(true))
tracedEndpoint = mw(func(context.Context, interface{}) (interface{}, error) {
return failedResponse{
err: err3,
}, nil
})
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 6 with OperationNameFunc option
mw = kitot.TraceEndpoint(
tracer,
span6,
kitot.WithOperationNameFunc(func(ctx context.Context, name string) string {
return fmt.Sprintf("%s-%s", "new", name)
}),
)
tracedEndpoint = mw(endpoint.Nop)
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 7 with Tags options
mw = kitot.TraceEndpoint(
tracer,
span7,
kitot.WithTags(map[string]interface{}{
"tag1": "tag1",
"tag2": "tag2",
}),
kitot.WithTags(map[string]interface{}{
"tag3": "tag3",
}),
)
tracedEndpoint = mw(endpoint.Nop)
_, _ = tracedEndpoint(context.Background(), struct{}{})
// span 8 with TagsFunc options
mw = kitot.TraceEndpoint(
tracer,
span8,
kitot.WithTags(map[string]interface{}{
"tag1": "tag1",
"tag2": "tag2",
}),
kitot.WithTags(map[string]interface{}{
"tag3": "tag3",
}),
kitot.WithTagsFunc(func(ctx context.Context) opentracing.Tags {
return map[string]interface{}{
"tag4": "tag4",
}
}),
)
tracedEndpoint = mw(endpoint.Nop)
_, _ = tracedEndpoint(context.Background(), struct{}{})
finishedSpans := tracer.FinishedSpans()
if want, have := 8, len(finishedSpans); want != have {
t.Fatalf("Want %v span(s), found %v", want, have)
}
// test span 1
span := finishedSpans[0]
if want, have := span1, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
// test span 2
span = finishedSpans[1]
if want, have := span2, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := true, span.Tag("error"); want != have {
t.Fatalf("Want %v, have %v", want, have)
}
// test span 3
span = finishedSpans[2]
if want, have := span3, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := true, span.Tag("error"); want != have {
t.Fatalf("Want %v, have %v", want, have)
}
if want, have := 1, len(span.Logs()); want != have {
t.Fatalf("incorrect logs count, wanted %d, got %d", want, have)
}
if want, have := []otlog.Field{
otlog.String("event", "error"),
otlog.String("error.object", "some error (previously: some error; some error; some error; some error)"),
otlog.String("gokit.retry.error.1", "some error"),
otlog.String("gokit.retry.error.2", "some error"),
otlog.String("gokit.retry.error.3", "some error"),
otlog.String("gokit.retry.error.4", "some error"),
otlog.String("gokit.retry.error.5", "some error"),
}, span.Logs()[0].Fields; reflect.DeepEqual(want, have) {
t.Fatalf("Want %q, have %q", want, have)
}
// test span 4
span = finishedSpans[3]
if want, have := span4, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := true, span.Tag("error"); want != have {
t.Fatalf("Want %v, have %v", want, have)
}
if want, have := 2, len(span.Logs()); want != have {
t.Fatalf("incorrect logs count, wanted %d, got %d", want, have)
}
if want, have := []otlog.Field{
otlog.String("gokit.business.error", "some business error"),
}, span.Logs()[0].Fields; reflect.DeepEqual(want, have) {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := []otlog.Field{
otlog.String("event", "error"),
otlog.String("error.object", "some business error"),
}, span.Logs()[1].Fields; reflect.DeepEqual(want, have) {
t.Fatalf("Want %q, have %q", want, have)
}
// test span 5
span = finishedSpans[4]
if want, have := span5, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := (interface{})(nil), span.Tag("error"); want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := 1, len(span.Logs()); want != have {
t.Fatalf("incorrect logs count, wanted %d, got %d", want, have)
}
if want, have := []otlog.Field{
otlog.String("gokit.business.error", "some business error"),
}, span.Logs()[0].Fields; reflect.DeepEqual(want, have) {
t.Fatalf("Want %q, have %q", want, have)
}
// test span 6
span = finishedSpans[5]
if want, have := fmt.Sprintf("%s-%s", "new", span6), span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
// test span 7
span = finishedSpans[6]
if want, have := span7, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := map[string]interface{}{
"tag1": "tag1",
"tag2": "tag2",
"tag3": "tag3",
}, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
t.Fatalf("Want %q, have %q", want, have)
}
// test span 8
span = finishedSpans[7]
if want, have := span8, span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := map[string]interface{}{
"tag1": "tag1",
"tag2": "tag2",
"tag3": "tag3",
"tag4": "tag4",
}, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
t.Fatalf("Want %q, have %q", want, have)
}
}
func TestTraceServer(t *testing.T) {
tracer := mocktracer.New()
// Empty/background context.
tracedEndpoint := kitot.TraceServer(tracer, "testOp")(endpoint.Nop)
if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil {
t.Fatal(err)
}
// tracedEndpoint created a new Span.
finishedSpans := tracer.FinishedSpans()
if want, have := 1, len(finishedSpans); want != have {
t.Fatalf("Want %v span(s), found %v", want, have)
}
span := finishedSpans[0]
if want, have := "testOp", span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := map[string]interface{}{
otext.SpanKindRPCServer.Key: otext.SpanKindRPCServer.Value,
}, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
t.Fatalf("Want %q, have %q", want, have)
}
}
func TestTraceClient(t *testing.T) {
tracer := mocktracer.New()
// Empty/background context.
tracedEndpoint := kitot.TraceClient(tracer, "testOp")(endpoint.Nop)
if _, err := tracedEndpoint(context.Background(), struct{}{}); err != nil {
t.Fatal(err)
}
// tracedEndpoint created a new Span.
finishedSpans := tracer.FinishedSpans()
if want, have := 1, len(finishedSpans); want != have {
t.Fatalf("Want %v span(s), found %v", want, have)
}
span := finishedSpans[0]
if want, have := "testOp", span.OperationName; want != have {
t.Fatalf("Want %q, have %q", want, have)
}
if want, have := map[string]interface{}{
otext.SpanKindRPCClient.Key: otext.SpanKindRPCClient.Value,
}, span.Tags(); fmt.Sprint(want) != fmt.Sprint(have) {
t.Fatalf("Want %q, have %q", want, have)
}
}