1
22 package harness
23
24 import (
25 "bytes"
26 "testing"
27 "time"
28
29 "github.com/opentracing/opentracing-go"
30 "github.com/opentracing/opentracing-go/log"
31 "github.com/stretchr/testify/assert"
32 "github.com/stretchr/testify/suite"
33 )
34
35
36 type APICheckCapabilities struct {
37 CheckBaggageValues bool
38 CheckExtract bool
39 CheckInject bool
40 Probe APICheckProbe
41 }
42
43
44 type APICheckProbe interface {
45
46 SameTrace(first, second opentracing.Span) bool
47
48 SameSpanContext(opentracing.Span, opentracing.SpanContext) bool
49 }
50
51
52 type APICheckSuite struct {
53 suite.Suite
54 opts APICheckCapabilities
55 newTracer func() (tracer opentracing.Tracer, closer func())
56 tracer opentracing.Tracer
57 closer func()
58 }
59
60
61
62
63 func RunAPIChecks(
64 t *testing.T,
65 newTracer func() (tracer opentracing.Tracer, closer func()),
66 opts ...APICheckOption,
67 ) {
68 s := &APICheckSuite{newTracer: newTracer}
69 for _, opt := range opts {
70 opt(s)
71 }
72 suite.Run(t, s)
73 }
74
75
76 type APICheckOption func(*APICheckSuite)
77
78
79 func CheckBaggageValues(val bool) APICheckOption {
80 return func(s *APICheckSuite) {
81 s.opts.CheckBaggageValues = val
82 }
83 }
84
85
86 func CheckExtract(val bool) APICheckOption {
87 return func(s *APICheckSuite) {
88 s.opts.CheckExtract = val
89 }
90 }
91
92
93 func CheckInject(val bool) APICheckOption {
94 return func(s *APICheckSuite) {
95 s.opts.CheckInject = val
96 }
97 }
98
99
100 func CheckEverything() APICheckOption {
101 return func(s *APICheckSuite) {
102 s.opts.CheckBaggageValues = true
103 s.opts.CheckExtract = true
104 s.opts.CheckInject = true
105 }
106 }
107
108
109 func UseProbe(probe APICheckProbe) APICheckOption {
110 return func(s *APICheckSuite) {
111 s.opts.Probe = probe
112 }
113 }
114
115
116 func (s *APICheckSuite) SetupTest() {
117 s.tracer, s.closer = s.newTracer()
118 if s.tracer == nil {
119 s.T().Fatalf("newTracer returned nil Tracer")
120 }
121 }
122
123
124 func (s *APICheckSuite) TearDownTest() {
125 if s.closer != nil {
126 s.closer()
127 }
128 s.tracer, s.closer = nil, nil
129 }
130
131
132 func (s *APICheckSuite) TestStartSpan() {
133 span := s.tracer.StartSpan(
134 "Fry",
135 opentracing.Tag{Key: "birthday", Value: "August 14 1974"})
136 span.LogFields(
137 log.String("hospital", "Brooklyn Pre-Med Hospital"),
138 log.String("city", "Old New York"))
139 span.Finish()
140 }
141
142
143 func (s *APICheckSuite) TestStartSpanWithParent() {
144 parentSpan := s.tracer.StartSpan("Turanga Munda")
145 s.NotNil(parentSpan)
146
147 childFns := []func(opentracing.SpanContext) opentracing.SpanReference{
148 opentracing.ChildOf,
149 opentracing.FollowsFrom,
150 }
151 for _, childFn := range childFns {
152 span := s.tracer.StartSpan(
153 "Leela",
154 childFn(parentSpan.Context()),
155 opentracing.Tag{Key: "birthplace", Value: "sewers"})
156 span.Finish()
157 if s.opts.Probe != nil {
158 s.True(s.opts.Probe.SameTrace(parentSpan, span))
159 } else {
160 s.T().Log("harness.Probe not specified, skipping")
161 }
162 }
163
164 parentSpan.Finish()
165 }
166
167
168 func (s *APICheckSuite) TestSetOperationName() {
169 span := s.tracer.StartSpan("").SetOperationName("Farnsworth")
170 span.Finish()
171 }
172
173
174 func (s *APICheckSuite) TestSpanTagValueTypes() {
175 span := s.tracer.StartSpan("ManyTypes")
176 span.
177 SetTag("an_int", 9).
178 SetTag("a_bool", true).
179 SetTag("a_string", "aoeuidhtns")
180 }
181
182
183 func (s *APICheckSuite) TestSpanTagsWithChaining() {
184 span := s.tracer.StartSpan("Farnsworth")
185 span.
186 SetTag("birthday", "9 April, 2841").
187 SetTag("loves", "different lengths of wires")
188 span.
189 SetTag("unicode_val", "non-ascii: \u200b").
190 SetTag("unicode_key_\u200b", "ascii val")
191 span.Finish()
192 }
193
194
195 func (s *APICheckSuite) TestSpanLogs() {
196 span := s.tracer.StartSpan("Fry")
197 span.LogKV(
198 "event", "frozen",
199 "year", 1999,
200 "place", "Cryogenics Labs")
201 span.LogKV(
202 "event", "defrosted",
203 "year", 2999,
204 "place", "Cryogenics Labs")
205
206 ts := time.Now()
207 span.FinishWithOptions(opentracing.FinishOptions{
208 LogRecords: []opentracing.LogRecord{
209 {
210 Timestamp: ts,
211 Fields: []log.Field{
212 log.String("event", "job-assignment"),
213 log.String("type", "delivery boy"),
214 },
215 },
216 }})
217
218
219 span.LogEvent("an arbitrary event")
220 span.LogEventWithPayload("y", "z")
221 span.Log(opentracing.LogData{Event: "y", Payload: "z"})
222 }
223
224 func assertEmptyBaggage(t *testing.T, spanContext opentracing.SpanContext) {
225 if !assert.NotNil(t, spanContext, "assertEmptyBaggage got empty context") {
226 return
227 }
228 spanContext.ForeachBaggageItem(func(k, v string) bool {
229 assert.Fail(t, "new span shouldn't have baggage")
230 return false
231 })
232 }
233
234
235
236 func (s *APICheckSuite) TestSpanBaggage() {
237 span := s.tracer.StartSpan("Fry")
238 assertEmptyBaggage(s.T(), span.Context())
239
240 spanRef := span.SetBaggageItem("Kiff-loves", "Amy")
241 s.Exactly(spanRef, span)
242
243 val := span.BaggageItem("Kiff-loves")
244 if s.opts.CheckBaggageValues {
245 s.Equal("Amy", val)
246 } else {
247 s.T().Log("CheckBaggageValues capability not set, skipping")
248 }
249 span.Finish()
250 }
251
252
253
254 func (s *APICheckSuite) TestContextBaggage() {
255 span := s.tracer.StartSpan("Fry")
256 assertEmptyBaggage(s.T(), span.Context())
257
258 span.SetBaggageItem("Kiff-loves", "Amy")
259 if s.opts.CheckBaggageValues {
260 called := false
261 span.Context().ForeachBaggageItem(func(k, v string) bool {
262 s.False(called)
263 called = true
264 s.Equal("Kiff-loves", k)
265 s.Equal("Amy", v)
266 return true
267 })
268 } else {
269 s.T().Log("CheckBaggageValues capability not set, skipping")
270 }
271 span.Finish()
272 }
273
274
275
276
277 func (s *APICheckSuite) TestTextPropagation() {
278 span := s.tracer.StartSpan("Bender")
279 textCarrier := opentracing.TextMapCarrier{}
280 err := span.Tracer().Inject(span.Context(), opentracing.TextMap, textCarrier)
281 assert.NoError(s.T(), err)
282
283 extractedContext, err := s.tracer.Extract(opentracing.TextMap, textCarrier)
284 if s.opts.CheckExtract {
285 s.NoError(err)
286 assertEmptyBaggage(s.T(), extractedContext)
287 } else {
288 s.T().Log("CheckExtract capability not set, skipping")
289 }
290 if s.opts.Probe != nil {
291 s.True(s.opts.Probe.SameSpanContext(span, extractedContext))
292 } else {
293 s.T().Log("harness.Probe not specified, skipping")
294 }
295 span.Finish()
296 }
297
298
299
300
301 func (s *APICheckSuite) TestHTTPPropagation() {
302 span := s.tracer.StartSpan("Bender")
303 textCarrier := opentracing.HTTPHeadersCarrier{}
304 err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, textCarrier)
305 s.NoError(err)
306
307 extractedContext, err := s.tracer.Extract(opentracing.HTTPHeaders, textCarrier)
308 if s.opts.CheckExtract {
309 s.NoError(err)
310 assertEmptyBaggage(s.T(), extractedContext)
311 } else {
312 s.T().Log("CheckExtract capability not set, skipping")
313 }
314 if s.opts.Probe != nil {
315 s.True(s.opts.Probe.SameSpanContext(span, extractedContext))
316 } else {
317 s.T().Log("harness.Probe not specified, skipping")
318 }
319 span.Finish()
320 }
321
322
323
324
325 func (s *APICheckSuite) TestBinaryPropagation() {
326 span := s.tracer.StartSpan("Bender")
327 buf := new(bytes.Buffer)
328 err := span.Tracer().Inject(span.Context(), opentracing.Binary, buf)
329 s.NoError(err)
330
331 extractedContext, err := s.tracer.Extract(opentracing.Binary, buf)
332 if s.opts.CheckExtract {
333 s.NoError(err)
334 assertEmptyBaggage(s.T(), extractedContext)
335 } else {
336 s.T().Log("CheckExtract capability not set, skipping")
337 }
338 if s.opts.Probe != nil {
339 s.True(s.opts.Probe.SameSpanContext(span, extractedContext))
340 } else {
341 s.T().Log("harness.Probe not specified, skipping")
342 }
343 span.Finish()
344 }
345
346
347
348 func (s *APICheckSuite) TestMandatoryFormats() {
349 formats := []struct{ Format, Carrier interface{} }{
350 {opentracing.TextMap, opentracing.TextMapCarrier{}},
351 {opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier{}},
352 {opentracing.Binary, new(bytes.Buffer)},
353 }
354 span := s.tracer.StartSpan("Bender")
355 for _, fmtCarrier := range formats {
356 err := span.Tracer().Inject(span.Context(), fmtCarrier.Format, fmtCarrier.Carrier)
357 s.NoError(err)
358 spanCtx, err := s.tracer.Extract(fmtCarrier.Format, fmtCarrier.Carrier)
359 if s.opts.CheckExtract {
360 s.NoError(err)
361 assertEmptyBaggage(s.T(), spanCtx)
362 } else {
363 s.T().Log("CheckExtract capability not set, skipping")
364 }
365 }
366 }
367
368
369
370 func (s *APICheckSuite) TestUnknownFormat() {
371 customFormat := "kiss my shiny metal ..."
372 span := s.tracer.StartSpan("Bender")
373
374 err := span.Tracer().Inject(span.Context(), customFormat, nil)
375 if s.opts.CheckInject {
376 s.Equal(opentracing.ErrUnsupportedFormat, err)
377 } else {
378 s.T().Log("CheckInject capability not set, skipping")
379 }
380 ctx, err := s.tracer.Extract(customFormat, nil)
381 s.Nil(ctx)
382 if s.opts.CheckExtract {
383 s.Equal(opentracing.ErrUnsupportedFormat, err)
384 } else {
385 s.T().Log("CheckExtract capability not set, skipping")
386 }
387 }
388
389
390 type ForeignSpanContext struct{}
391
392
393 func (f ForeignSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
394
395
396 type NotACarrier struct{}
397
398
399 func (s *APICheckSuite) TestInvalidInject() {
400 if !s.opts.CheckInject {
401 s.T().Skip("CheckInject capability not set, skipping")
402 }
403 span := s.tracer.StartSpan("op")
404
405
406 err := span.Tracer().Inject(ForeignSpanContext{}, opentracing.Binary, new(bytes.Buffer))
407 s.Equal(opentracing.ErrInvalidSpanContext, err, "Foreign SpanContext should return invalid error")
408 err = span.Tracer().Inject(span.Context(), opentracing.Binary, NotACarrier{})
409 s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not io.Writer should return error")
410
411
412 err = span.Tracer().Inject(ForeignSpanContext{}, opentracing.TextMap, opentracing.TextMapCarrier{})
413 s.Equal(opentracing.ErrInvalidSpanContext, err, "Foreign SpanContext should return invalid error")
414 err = span.Tracer().Inject(span.Context(), opentracing.TextMap, NotACarrier{})
415 s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapWriter should return error")
416
417
418 err = span.Tracer().Inject(ForeignSpanContext{}, opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier{})
419 s.Equal(opentracing.ErrInvalidSpanContext, err, "Foreign SpanContext should return invalid error")
420 err = span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, NotACarrier{})
421 s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapWriter should return error")
422 }
423
424
425 func (s *APICheckSuite) TestInvalidExtract() {
426 if !s.opts.CheckExtract {
427 s.T().Skip("CheckExtract capability not set, skipping")
428 }
429 span := s.tracer.StartSpan("op")
430
431
432 ctx, err := span.Tracer().Extract(opentracing.Binary, NotACarrier{})
433 s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not io.Reader should return error")
434 s.Nil(ctx)
435
436
437 ctx, err = span.Tracer().Extract(opentracing.TextMap, NotACarrier{})
438 s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapReader should return error")
439 s.Nil(ctx)
440
441
442 ctx, err = span.Tracer().Extract(opentracing.HTTPHeaders, NotACarrier{})
443 s.Equal(opentracing.ErrInvalidCarrier, err, "Carrier that's not TextMapReader should return error")
444 s.Nil(ctx)
445
446 span.Finish()
447 }
448
449
450
451
452 func (s *APICheckSuite) TestMultiBaggage() {
453 span := s.tracer.StartSpan("op")
454 assertEmptyBaggage(s.T(), span.Context())
455
456 span.SetBaggageItem("Bag1", "BaggageVal1")
457 span.SetBaggageItem("Bag2", "BaggageVal2")
458 if s.opts.CheckBaggageValues {
459 s.Equal("BaggageVal1", span.BaggageItem("Bag1"))
460 s.Equal("BaggageVal2", span.BaggageItem("Bag2"))
461 called := false
462 span.Context().ForeachBaggageItem(func(k, v string) bool {
463 s.False(called)
464 called = true
465 return false
466 })
467 s.True(called)
468 } else {
469 s.T().Log("CheckBaggageValues capability not set, skipping")
470 }
471 span.Finish()
472 }
473
View as plain text