// Copyright 2019 The OpenZipkin 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 zipkin import ( "context" "errors" "reflect" "testing" "time" "github.com/openzipkin/zipkin-go/idgenerator" "github.com/openzipkin/zipkin-go/model" "github.com/openzipkin/zipkin-go/reporter" ) func TestTracerOptionLocalEndpoint(t *testing.T) { var ( err error wantEP *model.Endpoint ) tr, err := NewTracer(nil, WithLocalEndpoint(nil)) if err != nil { t.Fatalf("unexpected tracer creation failure: %+v", err.Error()) } if tr == nil { t.Error("expected valid tracer, got: nil") } if want, have := wantEP, tr.LocalEndpoint(); !reflect.DeepEqual(want, have) { t.Errorf("local Endpoint want %+v, have %+v", want, have) } wantEP, err = NewEndpoint("testService", "localhost:80") if err != nil { t.Fatalf("expected valid endpoint, got error: %+v", err) } rep := reporter.NewNoopReporter() defer rep.Close() tr, err = NewTracer(rep, WithLocalEndpoint(wantEP)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if tr == nil { t.Error("expected valid tracer, got nil") } haveEP := tr.LocalEndpoint() if want, have := wantEP.ServiceName, haveEP.ServiceName; want != have { t.Errorf("ServiceName want %s, have %s", want, have) } if !wantEP.IPv4.Equal(haveEP.IPv4) { t.Errorf(" IPv4 want %+v, have %+v", wantEP.IPv4, haveEP.IPv4) } if !wantEP.IPv6.Equal(haveEP.IPv6) { t.Errorf("IPv6 want %+v, have %+v", wantEP.IPv6, haveEP.IPv6) } } func TestTracerOptionExtractFailurePolicy(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() policies := []struct { policy ExtractFailurePolicy err error }{ {-1, ErrInvalidExtractFailurePolicy}, {ExtractFailurePolicyRestart, nil}, {ExtractFailurePolicyError, nil}, {ExtractFailurePolicyTagAndRestart, nil}, {3, ErrInvalidExtractFailurePolicy}, } for idx, item := range policies { tr, err := NewTracer(rep, WithExtractFailurePolicy(item.policy)) if want, have := item.err, err; want != have { t.Fatalf("[%d] expected tracer creation failure: want %+v, have %+v", idx, item.err, err) } if err != nil && tr != nil { t.Fatalf("[%d] expected tracer to be nil, have: %+v", idx, tr) } if err != nil { tr, _ = NewTracer(rep) tr.extractFailurePolicy = item.policy } errStr := failSpan(t, tr, idx, item.err) if item.policy == ExtractFailurePolicyTagAndRestart { if want, have := "dummy", errStr; want != have { t.Errorf("[%d] tag[error.extract tag] want %s, have %s", idx, want, have) } } } } func failSpan(t *testing.T, tr *Tracer, idx int, want error) string { sc := model.SpanContext{ Err: errors.New("dummy"), } defer func() { if err := recover(); err != nil { if err != want { t.Errorf("[%d] Context Error want %+v, have %+v", idx, want, err) } } }() sp := tr.StartSpan("test", Parent(sc)) sp.Finish() return sp.(*spanImpl).Tags["error.extract"] } func TestTracerIDGeneratorOption(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() gen := idgenerator.NewRandomTimestamped() tr, err := NewTracer(rep, WithIDGenerator(gen)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if want, have := gen, tr.generate; want != have { t.Errorf("id generator want %+v, have %+v", want, have) } } func TestTracerWithTraceID128BitOption(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithTraceID128Bit(false)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if want, have := reflect.TypeOf(idgenerator.NewRandom64()), reflect.TypeOf(tr.generate); want != have { t.Errorf("id generator want %+v, have %+v", want, have) } tr, err = NewTracer(rep, WithTraceID128Bit(true)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } if want, have := reflect.TypeOf(idgenerator.NewRandom128()), reflect.TypeOf(tr.generate); want != have { t.Errorf("id generator want %+v, have %+v", want, have) } } func TestTracerExtractor(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } testErr1 := errors.New("extractor error") extractorErr := func() (*model.SpanContext, error) { return nil, testErr1 } sc := tr.Extract(extractorErr) if want, have := testErr1, sc.Err; want != have { t.Errorf("Err want %+v, have %+v", want, have) } spanContext := model.SpanContext{} extractor := func() (*model.SpanContext, error) { return &spanContext, nil } sc = tr.Extract(extractor) if want, have := spanContext, sc; want != have { t.Errorf("SpanContext want %+v, have %+v", want, have) } if want, have := &spanContext, ≻ want == have { t.Error("expected different span context objects") } } func TestNoopTracer(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), } span := tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() tr.SetNoop(true) testErr1 := errors.New("extractor error") extractor := func() (*model.SpanContext, error) { return nil, testErr1 } sc := tr.Extract(extractor) if sc.Err != nil { t.Errorf("Err want nil, have %+v", sc.Err) } span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&noopSpan{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() tr.SetNoop(false) span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() tr, err = NewTracer(rep, WithNoopTracer(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } testErr1 = errors.New("extractor error") extractor = func() (*model.SpanContext, error) { return nil, testErr1 } sc = tr.Extract(extractor) if sc.Err != nil { t.Errorf("Err want nil, have %+v", sc.Err) } span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&noopSpan{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } tr, err = NewTracer(rep, WithNoopTracer(false)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } span = tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } } func TestNoopSpan(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithNoopSpan(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } sampled := false pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), Sampled: &sampled, } span := tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&noopSpan{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } span.Finish() } func TestUnsampledSpan(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithTraceID128Bit(false)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } sampled := false pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), Sampled: &sampled, } span := tr.StartSpan("test", Parent(pSC)) if want, have := reflect.TypeOf(&spanImpl{}), reflect.TypeOf(span); want != have { t.Errorf("span implementation type want %+v, have %+v", want, have) } cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := pSC.TraceID, cSC.TraceID; want != have { t.Errorf("TraceID want %+v, have %+v", want, have) } if cSC.ID == 0 { t.Error("ID want valid value, have 0") } if cSC.ParentID == nil { t.Errorf("ParentID want %+v, have nil", pSC.ID) } else if want, have := pSC.ID, *cSC.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } if cSC.Sampled == nil { t.Error("Sampled want false, have nil") } else if *cSC.Sampled { t.Errorf("Sampled want false, have %+v", *cSC.Sampled) } if want, have := int32(0), span.(*spanImpl).mustCollect; want != have { t.Errorf("expected mustCollect %d, got %d", want, have) } span.Finish() } func TestDefaultTags(t *testing.T) { var ( scTagKey = "spanScopedTag" scTagValue = "spanPayload" tags = make(map[string]string) ) tags["platform"] = "zipkin_test" tags["version"] = "1.0" rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithTags(tags), WithTraceID128Bit(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), } span := tr.StartSpan("test", Kind(model.Server), Parent(pSC)) span.Tag(scTagKey, scTagValue) foundTags := span.(*spanImpl).Tags for key, value := range tags { foundValue, foundKey := foundTags[key] if !foundKey { t.Errorf("Tag want %s=%s, have key not found", key, value) } else if value != foundValue { t.Errorf("Tag want %s=%s, have %s=%s", key, value, key, foundValue) } } foundValue, foundKey := foundTags[scTagKey] if !foundKey { t.Errorf("Tag want %s=%s, have key not found", scTagKey, scTagValue) } else if want, have := scTagValue, foundValue; want != have { t.Errorf("Tag want %s=%s, have %s=%s", scTagKey, scTagValue, scTagKey, foundValue) } } func TestTagOverwriteRules(t *testing.T) { var ( k1 = "key1" v1First = "value to overwrite" v1Last = "value to keep" k2 = string(TagError) ) rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithIDGenerator(idgenerator.NewRandomTimestamped())) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } s := tr.StartSpan("test_tags") defer s.Finish() s.Tag(k1, v1First) if want, have := v1First, s.(*spanImpl).Tags[k1]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } s.Tag(k1, v1Last) if want, have := v1Last, s.(*spanImpl).Tags[k1]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } s.Tag(k2, v1First) if want, have := v1First, s.(*spanImpl).Tags[k2]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } s.Tag(k2, v1Last) if want, have := v1First, s.(*spanImpl).Tags[k2]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } TagError.Set(s, v1Last) if want, have := v1First, s.(*spanImpl).Tags[k2]; want != have { t.Errorf("Tag want %s=%s, have %s=%s", k1, want, k1, have) } } func TestAnnotations(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } s := tr.StartSpan("test_tags") defer s.Finish() annotations := []model.Annotation{ { Timestamp: time.Now().Add(10 * time.Millisecond), Value: "annotation 1", }, { Timestamp: time.Now().Add(20 * time.Millisecond), Value: "annotation 2", }, { Timestamp: time.Now().Add(30 * time.Millisecond), Value: "annotation 3", }, } for _, annotation := range annotations { s.Annotate(annotation.Timestamp, annotation.Value) } time.Sleep(40 * time.Millisecond) if want, have := len(annotations), len(s.(*spanImpl).Annotations); want != have { t.Fatalf("Annotation count want %d, have %d", want, have) } for idx, annotation := range annotations { if want, have := annotation, s.(*spanImpl).Annotations[idx]; want != have { t.Errorf("Annotation #%d want %+v, have %+v", idx, want, have) } } } func TestExplicitStartTime(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSampler(NewModuloSampler(2))) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } st := time.Now() time.Sleep(10 * time.Millisecond) s := tr.StartSpan("test_tags", StartTime(st)) defer s.Finish() if want, have := st, s.(*spanImpl).Timestamp; want != have { t.Errorf("Timestamp want %+v, have %+v", want, have) } } func TestDebugFlagWithoutParentTrace(t *testing.T) { /* Test handling of a single Debug flag without an existing trace */ rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ Debug: true, } span := tr.StartSpan("test", Parent(pSC)) cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := false, cSC.TraceID.Empty(); want != have { t.Error("expected valid TraceID") } if cSC.ID == 0 { t.Error("expected valid ID") } if cSC.ParentID != nil { t.Errorf("ParentID want nil, have %+v", cSC.ParentID) } if cSC.Sampled != nil { t.Errorf("Sampled want nil, have %+v", cSC.Sampled) } if want, have := int32(1), span.(*spanImpl).mustCollect; want != have { t.Errorf("mustCollect want %d, have %d", want, have) } } func TestParentSpanInSharedMode(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } parentID := model.ID(1) pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(2), ParentID: &parentID, } span := tr.StartSpan("test", Kind(model.Server), Parent(pSC)) cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := pSC.TraceID, cSC.TraceID; want != have { t.Errorf("TraceID want %+v, have %+v", want, have) } if want, have := pSC.ID, cSC.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if cSC.ParentID == nil { t.Error("ParentID want valid value, have nil") } else if want, have := parentID, *cSC.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } if cSC.Sampled == nil { t.Error("Sampled want explicit value, have nil") } else if !*cSC.Sampled { t.Errorf("Sampled want true, have %+v", *cSC.Sampled) } if want, have := int32(1), span.(*spanImpl).mustCollect; want != have { t.Errorf("mustCollect want %d, have %d", want, have) } } func TestParentSpanInSpanPerNodeMode(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(false)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } pSC := model.SpanContext{ TraceID: model.TraceID{ High: 0, Low: 1, }, ID: model.ID(1), } span := tr.StartSpan("test", Kind(model.Server), Parent(pSC)) cSC := span.Context() if cSC.Err != nil { t.Errorf("Err want nil, have %+v", cSC.Err) } if want, have := pSC.Debug, cSC.Debug; want != have { t.Errorf("Debug want %t, have %t", want, have) } if want, have := pSC.TraceID, cSC.TraceID; want != have { t.Errorf("TraceID want %+v, have: %+v", want, have) } if cSC.ID == 0 { t.Error("expected valid ID") } if cSC.ParentID == nil { t.Error("ParentID want valid value, have nil") } else if want, have := pSC.ID, *cSC.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } if cSC.Sampled == nil { t.Error("Sampled want explicit value, have nil") } else if !*cSC.Sampled { t.Errorf("Sampled want true, have %+v", *cSC.Sampled) } if want, have := int32(1), span.(*spanImpl).mustCollect; want != have { t.Errorf("mustCollect want %d, have %d", want, have) } } func TestStartSpanFromContext(t *testing.T) { ctx := context.Background() rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSharedSpans(true)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } if ctxSpan := SpanFromContext(ctx); ctxSpan != nil { t.Errorf("SpanFromContext want nil, have %+v", ctxSpan) } cSpan := tr.StartSpan("test", Kind(model.Client)) ctx = NewContext(ctx, cSpan) sSpan, _ := tr.StartSpanFromContext(ctx, "testChild", Kind(model.Server)) cS, sS := cSpan.(*spanImpl), sSpan.(*spanImpl) if want, have := model.Client, cS.Kind; want != have { t.Errorf("Kind want %+v, have %+v", want, have) } if want, have := model.Server, sS.Kind; want != have { t.Errorf("Kind want %+v, have: %+v", want, have) } if want, have := cS.TraceID, sS.TraceID; want != have { t.Errorf("TraceID want %+v, have: %+v", want, have) } if want, have := cS.ID, sS.ID; want != have { t.Errorf("ID want %+v, have %+v", want, have) } if want, have := cS.ParentID, sS.ParentID; want != have { t.Errorf("ParentID want %+v, have %+v", want, have) } } func TestStartSpanFromContextForEmptyContext(t *testing.T) { ctx := context.Background() rep := reporter.NewNoopReporter() defer rep.Close() tr, err := NewTracer(rep, WithSampler(NeverSample)) if err != nil { t.Fatalf("unable to create tracer instance: %+v", err) } span, _ := tr.StartSpanFromContext(ctx, "my span") span.Finish() } func TestLocalEndpoint(t *testing.T) { rep := reporter.NewNoopReporter() defer rep.Close() ep, err := NewEndpoint("my service", "localhost:80") if err != nil { t.Fatalf("expected valid endpoint, got error: %+v", err) } tracer, err := NewTracer(rep, WithLocalEndpoint(ep)) if err != nil { t.Fatalf("expected valid tracer, got error: %+v", err) } want, have := ep, tracer.LocalEndpoint() if have == nil { t.Fatalf("endpoint want %+v, have nil", want) } if want.ServiceName != have.ServiceName { t.Errorf("serviceName want %s, have %s", want.ServiceName, have.ServiceName) } if !want.IPv4.Equal(have.IPv4) { t.Errorf("IPv4 endpoint want %+v, have %+v", want.IPv4, have.IPv4) } if !want.IPv6.Equal(have.IPv6) { t.Errorf("IPv6 endpoint want %+v, have %+v", want.IPv6, have.IPv6) } }