1// Copyright (c) 2017 Uber Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package jaeger
16
17import (
18	"io"
19	"net/http"
20	"testing"
21	"time"
22
23	"github.com/opentracing/opentracing-go"
24	"github.com/opentracing/opentracing-go/ext"
25	"github.com/opentracing/opentracing-go/harness"
26	"github.com/stretchr/testify/assert"
27	"github.com/stretchr/testify/require"
28	"github.com/stretchr/testify/suite"
29	"github.com/uber/jaeger-lib/metrics/metricstest"
30
31	"github.com/uber/jaeger-client-go/internal/baggage"
32	"github.com/uber/jaeger-client-go/log"
33	"github.com/uber/jaeger-client-go/utils"
34)
35
36type tracerSuite struct {
37	suite.Suite
38	tracer         opentracing.Tracer
39	closer         io.Closer
40	metricsFactory *metricstest.Factory
41}
42
43func (s *tracerSuite) SetupTest() {
44	s.metricsFactory = metricstest.NewFactory(0)
45	metrics := NewMetrics(s.metricsFactory, nil)
46
47	s.tracer, s.closer = NewTracer("DOOP", // respect the classics, man!
48		NewConstSampler(true),
49		NewNullReporter(),
50		TracerOptions.Metrics(metrics),
51		TracerOptions.ZipkinSharedRPCSpan(true),
52		TracerOptions.BaggageRestrictionManager(baggage.NewDefaultRestrictionManager(0)),
53		TracerOptions.PoolSpans(false),
54	)
55	s.NotNil(s.tracer)
56}
57
58func (s *tracerSuite) TearDownTest() {
59	if s.tracer != nil {
60		s.closer.Close()
61		s.tracer = nil
62	}
63}
64
65func TestTracerSuite(t *testing.T) {
66	suite.Run(t, new(tracerSuite))
67}
68
69func (s *tracerSuite) TestBeginRootSpan() {
70	s.metricsFactory.Clear()
71	startTime := time.Now()
72	s.tracer.(*Tracer).timeNow = func() time.Time { return startTime }
73	someID := uint64(12345)
74	s.tracer.(*Tracer).randomNumber = func() uint64 { return someID }
75
76	sp := s.tracer.StartSpan("get_name")
77	ext.SpanKindRPCServer.Set(sp)
78	ext.PeerService.Set(sp, "peer-service")
79	s.NotNil(sp)
80	ss := sp.(*Span)
81	s.NotNil(ss.tracer, "Tracer must be referenced from span")
82	s.Equal("get_name", ss.operationName)
83	s.Len(ss.tags, 4, "Span should have 2 sampler tags, span.kind tag and peer.service tag")
84	s.EqualValues(Tag{key: "span.kind", value: ext.SpanKindRPCServerEnum}, ss.tags[2], "Span must be server-side")
85	s.EqualValues(Tag{key: "peer.service", value: "peer-service"}, ss.tags[3], "Client is 'peer-service'")
86
87	s.EqualValues(someID, ss.context.traceID.Low)
88	s.EqualValues(0, ss.context.parentID)
89
90	s.Equal(startTime, ss.startTime)
91
92	sp.Finish()
93	s.NotNil(ss.duration)
94
95	s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{
96		{Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 1},
97		{Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 1},
98		{Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 1},
99	}...)
100}
101
102func (s *tracerSuite) TestStartRootSpanWithOptions() {
103	ts := time.Now()
104	sp := s.tracer.StartSpan("get_address", opentracing.StartTime(ts))
105	ss := sp.(*Span)
106	s.Equal("get_address", ss.operationName)
107	s.Equal(ts, ss.startTime)
108}
109
110func (s *tracerSuite) TestStartChildSpan() {
111	s.metricsFactory.Clear()
112	sp1 := s.tracer.StartSpan("get_address")
113	sp2 := s.tracer.StartSpan("get_street", opentracing.ChildOf(sp1.Context()))
114	s.Equal(sp1.(*Span).context.spanID, sp2.(*Span).context.parentID)
115	sp2.Finish()
116	s.NotNil(sp2.(*Span).duration)
117	sp1.Finish()
118	s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{
119		{Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 2},
120		{Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 1},
121		{Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 2},
122	}...)
123}
124
125type nonJaegerSpanContext struct{}
126
127func (c nonJaegerSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
128
129func (s *tracerSuite) TestStartSpanWithMultipleReferences() {
130	s.metricsFactory.Clear()
131	sp1 := s.tracer.StartSpan("A")
132	sp2 := s.tracer.StartSpan("B")
133	sp3 := s.tracer.StartSpan("C")
134	sp4 := s.tracer.StartSpan(
135		"D",
136		opentracing.ChildOf(sp1.Context()),
137		opentracing.ChildOf(sp2.Context()),
138		opentracing.FollowsFrom(sp3.Context()),
139		opentracing.FollowsFrom(nonJaegerSpanContext{}),
140		opentracing.FollowsFrom(SpanContext{}), // Empty span context should be excluded
141	)
142	// Should use the first ChildOf ref span as the parent
143	s.Equal(sp1.(*Span).context.spanID, sp4.(*Span).context.parentID)
144	sp4.Finish()
145	s.NotNil(sp4.(*Span).duration)
146	sp3.Finish()
147	sp2.Finish()
148	sp1.Finish()
149	s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{
150		{Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 4},
151		{Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 3},
152		{Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 4},
153	}...)
154	assert.Len(s.T(), sp4.(*Span).references, 3)
155}
156
157func (s *tracerSuite) TestStartSpanWithOnlyFollowFromReference() {
158	s.metricsFactory.Clear()
159	sp1 := s.tracer.StartSpan("A")
160	sp2 := s.tracer.StartSpan(
161		"B",
162		opentracing.FollowsFrom(sp1.Context()),
163	)
164	// Should use the first ChildOf ref span as the parent
165	s.Equal(sp1.(*Span).context.spanID, sp2.(*Span).context.parentID)
166	sp2.Finish()
167	s.NotNil(sp2.(*Span).duration)
168	sp1.Finish()
169	s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{
170		{Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 2},
171		{Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 1},
172		{Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 2},
173	}...)
174	assert.Len(s.T(), sp2.(*Span).references, 1)
175}
176
177func (s *tracerSuite) TestTraceStartedOrJoinedMetrics() {
178	tests := []struct {
179		sampled bool
180		label   string
181	}{
182		{true, "y"},
183		{false, "n"},
184	}
185	for _, test := range tests {
186		s.metricsFactory.Clear()
187		s.tracer.(*Tracer).sampler = NewConstSampler(test.sampled)
188		sp1 := s.tracer.StartSpan("parent", ext.RPCServerOption(nil))
189		sp2 := s.tracer.StartSpan("child1", opentracing.ChildOf(sp1.Context()))
190		sp3 := s.tracer.StartSpan("child2", ext.RPCServerOption(sp2.Context()))
191		s.Equal(sp2.(*Span).context.spanID, sp3.(*Span).context.spanID)
192		s.Equal(sp2.(*Span).context.parentID, sp3.(*Span).context.parentID)
193		sp3.Finish()
194		sp2.Finish()
195		sp1.Finish()
196		s.Equal(test.sampled, sp1.Context().(SpanContext).IsSampled())
197		s.Equal(test.sampled, sp2.Context().(SpanContext).IsSampled())
198
199		s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{
200			{Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": test.label}, Value: 3},
201			{Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": test.label}, Value: 3},
202			{Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": test.label, "state": "started"}, Value: 1},
203			{Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": test.label, "state": "joined"}, Value: 1},
204		}...)
205	}
206}
207
208func (s *tracerSuite) TestSetOperationName() {
209	sp1 := s.tracer.StartSpan("get_address")
210	sp1.SetOperationName("get_street")
211	s.Equal("get_street", sp1.(*Span).operationName)
212}
213
214func (s *tracerSuite) TestSamplerEffects() {
215	s.tracer.(*Tracer).sampler = NewConstSampler(true)
216	sp := s.tracer.StartSpan("test")
217	s.True(sp.(*Span).context.IsSampled())
218
219	s.tracer.(*Tracer).sampler = NewConstSampler(false)
220	sp = s.tracer.StartSpan("test")
221	s.False(sp.(*Span).context.IsSampled())
222}
223
224func (s *tracerSuite) TestRandomIDNotZero() {
225	val := uint64(0)
226	s.tracer.(*Tracer).randomNumber = func() (r uint64) {
227		r = val
228		val++
229		return
230	}
231	sp := s.tracer.StartSpan("get_name").(*Span)
232	s.EqualValues(TraceID{Low: 1}, sp.context.traceID)
233
234	rng := utils.NewRand(0)
235	rng.Seed(1) // for test coverage
236}
237
238func (s *tracerSuite) TestReferenceSelfUsesProvidedContext() {
239	ctx := NewSpanContext(
240		TraceID{
241			High: 1,
242			Low:  2,
243		},
244		SpanID(2),
245		SpanID(1),
246		false,
247		nil,
248	)
249	sp1 := s.tracer.StartSpan(
250		"continued_span",
251		SelfRef(ctx),
252	)
253	s.Equal(ctx, sp1.(*Span).context)
254}
255
256func TestTracerOptions(t *testing.T) {
257	t1, e := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00")
258	assert.NoError(t, e)
259
260	timeNow := func() time.Time {
261		return t1
262	}
263	rnd := func() uint64 {
264		return 1
265	}
266	isPoolAllocator := func(allocator SpanAllocator) bool {
267		_, ok := allocator.(*syncPollSpanAllocator)
268		return ok
269	}
270
271	openTracer, closer := NewTracer("DOOP", // respect the classics, man!
272		NewConstSampler(true),
273		NewNullReporter(),
274		TracerOptions.Logger(log.StdLogger),
275		TracerOptions.TimeNow(timeNow),
276		TracerOptions.RandomNumber(rnd),
277		TracerOptions.PoolSpans(true),
278		TracerOptions.Tag("tag_key", "tag_value"),
279		TracerOptions.NoDebugFlagOnForcedSampling(true),
280	)
281	defer closer.Close()
282
283	tracer := openTracer.(*Tracer)
284	assert.Equal(t, log.StdLogger, tracer.logger)
285	assert.Equal(t, t1, tracer.timeNow())
286	assert.Equal(t, uint64(1), tracer.randomNumber())
287	assert.Equal(t, uint64(1), tracer.randomNumber())
288	assert.Equal(t, uint64(1), tracer.randomNumber()) // always 1
289	assert.Equal(t, true, isPoolAllocator(tracer.spanAllocator))
290	assert.Equal(t, opentracing.Tag{Key: "tag_key", Value: "tag_value"}, tracer.Tags()[0])
291	assert.True(t, tracer.options.noDebugFlagOnForcedSampling)
292}
293
294func TestInjectorExtractorOptions(t *testing.T) {
295	tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(),
296		TracerOptions.Injector("dummy", &dummyPropagator{}),
297		TracerOptions.Extractor("dummy", &dummyPropagator{}),
298	)
299	defer tc.Close()
300
301	sp := tracer.StartSpan("x")
302	c := &dummyCarrier{}
303	err := tracer.Inject(sp.Context(), "dummy", []int{})
304	assert.Equal(t, opentracing.ErrInvalidCarrier, err)
305	err = tracer.Inject(sp.Context(), "dummy", c)
306	assert.NoError(t, err)
307	assert.True(t, c.ok)
308
309	c.ok = false
310	_, err = tracer.Extract("dummy", []int{})
311	assert.Equal(t, opentracing.ErrInvalidCarrier, err)
312	_, err = tracer.Extract("dummy", c)
313	assert.Equal(t, opentracing.ErrSpanContextNotFound, err)
314	c.ok = true
315	_, err = tracer.Extract("dummy", c)
316	assert.NoError(t, err)
317}
318
319func TestEmptySpanContextAsParent(t *testing.T) {
320	tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter())
321	defer tc.Close()
322
323	span := tracer.StartSpan("test", opentracing.ChildOf(emptyContext))
324	ctx := span.Context().(SpanContext)
325	assert.True(t, ctx.traceID.IsValid())
326	assert.True(t, ctx.IsValid())
327}
328
329func TestGen128Bit(t *testing.T) {
330	tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.Gen128Bit(true))
331	defer tc.Close()
332
333	span := tracer.StartSpan("test", opentracing.ChildOf(emptyContext))
334	defer span.Finish()
335	traceID := span.Context().(SpanContext).TraceID()
336	assert.True(t, traceID.High != 0)
337	assert.True(t, traceID.Low != 0)
338}
339
340func TestZipkinSharedRPCSpan(t *testing.T) {
341	tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.ZipkinSharedRPCSpan(false))
342
343	sp1 := tracer.StartSpan("client", ext.SpanKindRPCClient)
344	sp2 := tracer.StartSpan("server", opentracing.ChildOf(sp1.Context()), ext.SpanKindRPCServer)
345	assert.Equal(t, sp1.(*Span).context.spanID, sp2.(*Span).context.parentID)
346	assert.NotEqual(t, sp1.(*Span).context.spanID, sp2.(*Span).context.spanID)
347	sp2.Finish()
348	sp1.Finish()
349	tc.Close()
350
351	tracer, tc = NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.ZipkinSharedRPCSpan(true))
352
353	sp1 = tracer.StartSpan("client", ext.SpanKindRPCClient)
354	sp2 = tracer.StartSpan("server", opentracing.ChildOf(sp1.Context()), ext.SpanKindRPCServer)
355	assert.Equal(t, sp1.(*Span).context.spanID, sp2.(*Span).context.spanID)
356	assert.Equal(t, sp1.(*Span).context.parentID, sp2.(*Span).context.parentID)
357	sp2.Finish()
358	sp1.Finish()
359	tc.Close()
360}
361
362type testDebugThrottler struct {
363	process Process
364}
365
366func (t *testDebugThrottler) SetProcess(process Process) {
367	t.process = process
368}
369
370func (t *testDebugThrottler) Close() error {
371	return nil
372}
373
374func (t *testDebugThrottler) IsAllowed(operation string) bool {
375	return true
376}
377
378func TestDebugThrottler(t *testing.T) {
379	throttler := &testDebugThrottler{}
380	opentracingTracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.DebugThrottler(throttler))
381	assert.NoError(t, tc.Close())
382	tracer := opentracingTracer.(*Tracer)
383	assert.Equal(t, tracer.process, throttler.process)
384}
385
386func TestThrottling_SamplingPriority(t *testing.T) {
387	tracer, closer := NewTracer("DOOP", NewConstSampler(true), NewNullReporter())
388
389	sp1 := tracer.StartSpan("s1", opentracing.Tags{string(ext.SamplingPriority): 0}).(*Span)
390	assert.False(t, sp1.context.IsDebug())
391
392	sp1 = tracer.StartSpan("s1", opentracing.Tags{string(ext.SamplingPriority): uint16(1)}).(*Span)
393	assert.True(t, sp1.context.IsDebug())
394
395	assert.NotNil(t, findDomainTag(sp1, "sampling.priority"), "sampling.priority tag should be added")
396	closer.Close()
397
398	tracer, closer = NewTracer("DOOP", NewConstSampler(true), NewNullReporter(),
399		TracerOptions.DebugThrottler(testThrottler{allowAll: false}))
400	defer closer.Close()
401
402	sp1 = tracer.StartSpan("s1", opentracing.Tags{string(ext.SamplingPriority): uint16(1)}).(*Span)
403	ext.SamplingPriority.Set(sp1, 1)
404	assert.False(t, sp1.context.IsDebug(), "debug should not be allowed by the throttler")
405}
406
407func TestThrottling_DebugHeader(t *testing.T) {
408	tracer, closer := NewTracer("DOOP", NewConstSampler(true), NewNullReporter())
409
410	h := http.Header{}
411	h.Add(JaegerDebugHeader, "x")
412	ctx, err := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(h))
413	require.NoError(t, err)
414
415	sp := tracer.StartSpan("root", opentracing.ChildOf(ctx)).(*Span)
416	assert.True(t, sp.context.IsDebug())
417	assert.Len(t, sp.References(), 0)
418	closer.Close()
419
420	tracer, closer = NewTracer("DOOP", NewConstSampler(true), NewNullReporter(),
421		TracerOptions.DebugThrottler(testThrottler{allowAll: false}))
422	defer closer.Close()
423
424	sp = tracer.StartSpan("root", opentracing.ChildOf(ctx)).(*Span)
425	assert.False(t, sp.context.IsDebug(), "debug should not be allowed by the throttler")
426}
427
428func TestSetGetTag(t *testing.T) {
429	opentracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter())
430	tracer := opentracer.(*Tracer)
431	defer tc.Close()
432	value, ok := tracer.getTag(TracerIPTagKey)
433	assert.True(t, ok)
434	_, ok = value.(string)
435	assert.True(t, ok)
436	assert.True(t, tracer.hostIPv4 != 0)
437
438	ipStr := "11.22.33.44"
439	opentracer, tc = NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.Tag(TracerIPTagKey, ipStr))
440	tracer = opentracer.(*Tracer)
441	defer tc.Close()
442	value, ok = tracer.getTag(TracerIPTagKey)
443	assert.True(t, ok)
444	assert.True(t, value == ipStr)
445	assert.True(t, tracer.hostIPv4 != 0)
446
447	ipStrInvalid := "an invalid input"
448	opentracer, tc = NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.Tag(TracerIPTagKey, ipStrInvalid))
449	tracer = opentracer.(*Tracer)
450	defer tc.Close()
451	value, ok = tracer.getTag(TracerIPTagKey)
452	assert.True(t, ok)
453	assert.True(t, value == ipStrInvalid)
454	assert.True(t, tracer.hostIPv4 == 0)
455}
456
457func TestTracerGetSampler(t *testing.T) {
458	sampler := NewRateLimitingSampler(1)
459	tracer, closer := NewTracer("service", sampler, NewNullReporter())
460	defer closer.Close()
461
462	assert.Same(t, sampler, tracer.(*Tracer).Sampler())
463}
464
465type dummyPropagator struct{}
466type dummyCarrier struct {
467	ok bool
468}
469
470func (p *dummyPropagator) Inject(ctx SpanContext, carrier interface{}) error {
471	c, ok := carrier.(*dummyCarrier)
472	if !ok {
473		return opentracing.ErrInvalidCarrier
474	}
475	c.ok = true
476	return nil
477}
478
479func (p *dummyPropagator) Extract(carrier interface{}) (SpanContext, error) {
480	c, ok := carrier.(*dummyCarrier)
481	if !ok {
482		return emptyContext, opentracing.ErrInvalidCarrier
483	}
484	if c.ok {
485		return emptyContext, nil
486	}
487	return emptyContext, opentracing.ErrSpanContextNotFound
488}
489
490func TestAPI(t *testing.T) {
491	harness.RunAPIChecks(
492		t,
493		func() (opentracing.Tracer, func()) {
494			tracer, closer := NewTracer("DOOP", // respect the classics, man!
495				NewConstSampler(true),
496				NewNullReporter(),
497			)
498
499			return tracer, func() { closer.Close() }
500		},
501		harness.CheckEverything(),
502		harness.UseProbe(&jaegerProbe{}),
503	)
504}
505
506type jaegerProbe struct{}
507
508// SameTrace helps tests assert that this tracer's spans are from the same trace.
509func (jp *jaegerProbe) SameTrace(first, second opentracing.Span) bool {
510	firstCtx := first.Context().(SpanContext)
511	secondCtx := second.Context().(SpanContext)
512	return firstCtx.traceID == secondCtx.traceID
513}
514
515// SameSpanContext helps tests assert that a span and a context are from the same trace and span.
516func (jp *jaegerProbe) SameSpanContext(first opentracing.Span, second opentracing.SpanContext) bool {
517	firstCtx := first.Context().(SpanContext)
518	secondCtx := second.(SpanContext)
519	return firstCtx.traceID == secondCtx.traceID && firstCtx.spanID == secondCtx.spanID
520}
521