...

Source file src/github.com/opentracing/opentracing-go/harness/api_checkers.go

Documentation: github.com/opentracing/opentracing-go/harness

     1  /*
     2  
     3  Package harness provides a suite of API compatibility checks. They were originally ported from the
     4  OpenTracing Python library's "harness" module.
     5  
     6  To run this test suite against your tracer, call harness.RunAPIChecks and provide it a function
     7  that returns a Tracer implementation and a function to call to close it. The function will be
     8  called to create a new tracer before each test in the suite is run, and the returned closer function
     9  will be called after each test is finished.
    10  
    11  Several options provide additional checks for your Tracer's behavior: CheckBaggageValues(true)
    12  indicates your tracer supports baggage propagation, CheckExtract(true) tells the suite to test if
    13  the Tracer can extract a trace context from text and binary carriers, and CheckInject(true) tests
    14  if the Tracer can inject the trace context into a carrier.
    15  
    16  The UseProbe option provides an APICheckProbe implementation that allows the test suite to
    17  additionally check if two Spans are part of the same trace, and if a Span and a SpanContext
    18  are part of the same trace. Implementing an APICheckProbe provides additional assertions that
    19  your tracer is working properly.
    20  
    21  */
    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  // APICheckCapabilities describes capabilities of a Tracer that should be checked by APICheckSuite.
    36  type APICheckCapabilities struct {
    37  	CheckBaggageValues bool          // whether to check for propagation of baggage values
    38  	CheckExtract       bool          // whether to check if extracting contexts from carriers works
    39  	CheckInject        bool          // whether to check if injecting contexts works
    40  	Probe              APICheckProbe // optional interface providing methods to check recorded data
    41  }
    42  
    43  // APICheckProbe exposes methods for testing data recorded by a Tracer.
    44  type APICheckProbe interface {
    45  	// SameTrace helps tests assert that this tracer's spans are from the same trace.
    46  	SameTrace(first, second opentracing.Span) bool
    47  	// SameSpanContext helps tests assert that a span and a context are from the same trace and span.
    48  	SameSpanContext(opentracing.Span, opentracing.SpanContext) bool
    49  }
    50  
    51  // APICheckSuite is a testify suite for checking a Tracer against the OpenTracing API.
    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  // RunAPIChecks runs a test suite to check a Tracer against the OpenTracing API.
    61  // It is provided a function that will be executed to create and destroy a tracer for each test
    62  // in the suite, and the given APICheckOption functional options `opts`.
    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  // APICheckOption instances may be passed to NewAPICheckSuite.
    76  type APICheckOption func(*APICheckSuite)
    77  
    78  // CheckBaggageValues returns an option that sets whether to check for propagation of baggage values.
    79  func CheckBaggageValues(val bool) APICheckOption {
    80  	return func(s *APICheckSuite) {
    81  		s.opts.CheckBaggageValues = val
    82  	}
    83  }
    84  
    85  // CheckExtract returns an option that sets whether to check if extracting contexts from carriers works.
    86  func CheckExtract(val bool) APICheckOption {
    87  	return func(s *APICheckSuite) {
    88  		s.opts.CheckExtract = val
    89  	}
    90  }
    91  
    92  // CheckInject returns an option that sets whether to check if injecting contexts works.
    93  func CheckInject(val bool) APICheckOption {
    94  	return func(s *APICheckSuite) {
    95  		s.opts.CheckInject = val
    96  	}
    97  }
    98  
    99  // CheckEverything returns an option that enables all API checks.
   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  // UseProbe returns an option that specifies an APICheckProbe implementation to use.
   109  func UseProbe(probe APICheckProbe) APICheckOption {
   110  	return func(s *APICheckSuite) {
   111  		s.opts.Probe = probe
   112  	}
   113  }
   114  
   115  // SetupTest creates a tracer for this specific test invocation.
   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  // TearDownTest closes the tracer, and clears the test-specific tracer.
   124  func (s *APICheckSuite) TearDownTest() {
   125  	if s.closer != nil {
   126  		s.closer()
   127  	}
   128  	s.tracer, s.closer = nil, nil
   129  }
   130  
   131  // TestStartSpan checks if a Tracer can start a span and calls some span API methods.
   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  // TestStartSpanWithParent checks if a Tracer can start a span with a specified parent.
   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  // TestSetOperationName attempts to set the operation name on a span after it has been created.
   168  func (s *APICheckSuite) TestSetOperationName() {
   169  	span := s.tracer.StartSpan("").SetOperationName("Farnsworth")
   170  	span.Finish()
   171  }
   172  
   173  // TestSpanTagValueTypes sets tags using values of different types.
   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  // TestSpanTagsWithChaining tests chaining of calls to SetTag.
   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  // TestSpanLogs tests calls to log keys and values with spans.
   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  	// Test deprecated log methods
   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  // TestSpanBaggage tests calls to set and get span baggage, and if the CheckBaggageValues option
   235  // is set, asserts that baggage values were successfully retrieved.
   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  // TestContextBaggage tests calls to set and get span baggage, and if the CheckBaggageValues option
   253  // is set, asserts that baggage values were successfully retrieved from the span's SpanContext.
   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  // TestTextPropagation tests if the Tracer can Inject a span into a TextMapCarrier, and later Extract it.
   275  // If CheckExtract is set, it will check if Extract was successful (returned no error). If a Probe is set,
   276  // it will check if the extracted context is in the same trace as the original span.
   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  // TestHTTPPropagation tests if the Tracer can Inject a span into HTTP headers, and later Extract it.
   299  // If CheckExtract is set, it will check if Extract was successful (returned no error). If a Probe is set,
   300  // it will check if the extracted context is in the same trace as the original span.
   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  // TestBinaryPropagation tests if the Tracer can Inject a span into a binary buffer, and later Extract it.
   323  // If CheckExtract is set, it will check if Extract was successful (returned no error). If a Probe is set,
   324  // it will check if the extracted context is in the same trace as the original span.
   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  // TestMandatoryFormats tests if all mandatory carrier formats are supported. If CheckExtract is set, it
   347  // will check if the call to Extract was successful (returned no error such as ErrUnsupportedFormat).
   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  // TestUnknownFormat checks if attempting to Inject or Extract using an unsupported format
   369  // returns ErrUnsupportedFormat, if CheckInject and CheckExtract are set.
   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  // ForeignSpanContext satisfies the opentracing.SpanContext interface, but otherwise does nothing.
   390  type ForeignSpanContext struct{}
   391  
   392  // ForeachBaggageItem could call handler for each baggage KV, but does nothing.
   393  func (f ForeignSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
   394  
   395  // NotACarrier does not satisfy any of the opentracing carrier interfaces.
   396  type NotACarrier struct{}
   397  
   398  // TestInvalidInject checks if errors are returned when Inject is called with invalid inputs.
   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  	// binary inject
   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  	// text inject
   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  	// HTTP inject
   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  // TestInvalidExtract checks if errors are returned when Extract is called with invalid inputs.
   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  	// binary extract
   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  	// text extract
   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  	// HTTP extract
   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  // TestMultiBaggage tests calls to set multiple baggage items, and if the CheckBaggageValues option
   450  // is set, asserts that a baggage value was successfully retrieved from the span's SpanContext.
   451  // It also ensures that returning false from the ForeachBaggageItem handler aborts iteration.
   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) // should only be called once
   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