...

Source file src/go.opentelemetry.io/otel/trace/trace_test.go

Documentation: go.opentelemetry.io/otel/trace

     1  // Copyright The OpenTelemetry Authors
     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  
    15  package trace
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/stretchr/testify/assert"
    24  
    25  	"go.opentelemetry.io/otel/attribute"
    26  )
    27  
    28  func TestSpanContextIsValid(t *testing.T) {
    29  	for _, testcase := range []struct {
    30  		name string
    31  		tid  TraceID
    32  		sid  SpanID
    33  		want bool
    34  	}{
    35  		{
    36  			name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
    37  			tid:  [16]byte{1},
    38  			sid:  [8]byte{42},
    39  			want: true,
    40  		}, {
    41  			name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
    42  			tid:  TraceID([16]byte{}),
    43  			sid:  [8]byte{},
    44  			want: false,
    45  		}, {
    46  			name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
    47  			tid:  TraceID([16]byte{}),
    48  			sid:  [8]byte{42},
    49  			want: false,
    50  		}, {
    51  			name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
    52  			tid:  TraceID([16]byte{1}),
    53  			sid:  [8]byte{},
    54  			want: false,
    55  		},
    56  	} {
    57  		t.Run(testcase.name, func(t *testing.T) {
    58  			sc := SpanContext{
    59  				traceID: testcase.tid,
    60  				spanID:  testcase.sid,
    61  			}
    62  			have := sc.IsValid()
    63  			if have != testcase.want {
    64  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
    65  			}
    66  		})
    67  	}
    68  }
    69  
    70  func TestSpanContextEqual(t *testing.T) {
    71  	a := SpanContext{
    72  		traceID: [16]byte{1},
    73  		spanID:  [8]byte{42},
    74  	}
    75  
    76  	b := SpanContext{
    77  		traceID: [16]byte{1},
    78  		spanID:  [8]byte{42},
    79  	}
    80  
    81  	c := SpanContext{
    82  		traceID: [16]byte{2},
    83  		spanID:  [8]byte{42},
    84  	}
    85  
    86  	if !a.Equal(b) {
    87  		t.Error("Want: true, but have: false")
    88  	}
    89  
    90  	if a.Equal(c) {
    91  		t.Error("Want: false, but have: true")
    92  	}
    93  }
    94  
    95  func TestSpanContextIsSampled(t *testing.T) {
    96  	for _, testcase := range []struct {
    97  		name string
    98  		tf   TraceFlags
    99  		want bool
   100  	}{
   101  		{
   102  			name: "SpanContext.IsSampled() returns false if sc is not sampled",
   103  			want: false,
   104  		}, {
   105  			name: "SpanContext.IsSampled() returns true if sc is sampled",
   106  			tf:   FlagsSampled,
   107  			want: true,
   108  		},
   109  	} {
   110  		t.Run(testcase.name, func(t *testing.T) {
   111  			sc := SpanContext{
   112  				traceFlags: testcase.tf,
   113  			}
   114  
   115  			have := sc.IsSampled()
   116  
   117  			if have != testcase.want {
   118  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
   119  			}
   120  		})
   121  	}
   122  }
   123  
   124  func TestSpanContextIsRemote(t *testing.T) {
   125  	for _, testcase := range []struct {
   126  		name   string
   127  		remote bool
   128  		want   bool
   129  	}{
   130  		{
   131  			name: "SpanContext.IsRemote() returns false if sc is not remote",
   132  			want: false,
   133  		}, {
   134  			name:   "SpanContext.IsRemote() returns true if sc is remote",
   135  			remote: true,
   136  			want:   true,
   137  		},
   138  	} {
   139  		t.Run(testcase.name, func(t *testing.T) {
   140  			sc := SpanContext{
   141  				remote: testcase.remote,
   142  			}
   143  
   144  			have := sc.IsRemote()
   145  
   146  			if have != testcase.want {
   147  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestSpanContextMarshalJSON(t *testing.T) {
   154  	for _, testcase := range []struct {
   155  		name     string
   156  		tid      TraceID
   157  		sid      SpanID
   158  		tstate   TraceState
   159  		tflags   TraceFlags
   160  		isRemote bool
   161  		want     []byte
   162  	}{
   163  		{
   164  			name: "SpanContext.MarshalJSON() returns json with partial data",
   165  			tid:  [16]byte{1},
   166  			sid:  [8]byte{42},
   167  			want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"00","TraceState":"","Remote":false}`),
   168  		},
   169  		{
   170  			name:     "SpanContext.MarshalJSON() returns json with full data",
   171  			tid:      [16]byte{1},
   172  			sid:      [8]byte{42},
   173  			tflags:   FlagsSampled,
   174  			isRemote: true,
   175  			tstate: TraceState{list: []member{
   176  				{Key: "foo", Value: "1"},
   177  			}},
   178  			want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"01","TraceState":"foo=1","Remote":true}`),
   179  		},
   180  	} {
   181  		t.Run(testcase.name, func(t *testing.T) {
   182  			sc := SpanContext{
   183  				traceID:    testcase.tid,
   184  				spanID:     testcase.sid,
   185  				traceFlags: testcase.tflags,
   186  				traceState: testcase.tstate,
   187  				remote:     testcase.isRemote,
   188  			}
   189  			have, err := sc.MarshalJSON()
   190  			if err != nil {
   191  				t.Errorf("Marshaling failed: %v", err)
   192  			}
   193  
   194  			if !bytes.Equal(have, testcase.want) {
   195  				t.Errorf("Want: %v, but have: %v", string(testcase.want), string(have))
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestSpanIDFromHex(t *testing.T) {
   202  	for _, testcase := range []struct {
   203  		name  string
   204  		hex   string
   205  		sid   SpanID
   206  		valid bool
   207  	}{
   208  		{
   209  			name:  "Valid SpanID",
   210  			sid:   SpanID([8]byte{42}),
   211  			hex:   "2a00000000000000",
   212  			valid: true,
   213  		}, {
   214  			name:  "Invalid SpanID with invalid length",
   215  			hex:   "80f198ee56343ba",
   216  			valid: false,
   217  		}, {
   218  			name:  "Invalid SpanID with invalid char",
   219  			hex:   "80f198ee563433g7",
   220  			valid: false,
   221  		}, {
   222  			name:  "Invalid SpanID with uppercase",
   223  			hex:   "80f198ee53ba86F7",
   224  			valid: false,
   225  		}, {
   226  			name:  "Invalid SpanID with zero value",
   227  			hex:   "0000000000000000",
   228  			valid: false,
   229  		},
   230  	} {
   231  		t.Run(testcase.name, func(t *testing.T) {
   232  			sid, err := SpanIDFromHex(testcase.hex)
   233  
   234  			if testcase.valid && err != nil {
   235  				t.Errorf("Expected SpanID %s to be valid but end with error %s", testcase.hex, err.Error())
   236  			} else if !testcase.valid && err == nil {
   237  				t.Errorf("Expected SpanID %s to be invalid but end no error", testcase.hex)
   238  			}
   239  
   240  			if sid != testcase.sid {
   241  				t.Errorf("Want: %v, but have: %v", testcase.sid, sid)
   242  			}
   243  		})
   244  	}
   245  }
   246  
   247  func TestIsValidFromHex(t *testing.T) {
   248  	for _, testcase := range []struct {
   249  		name  string
   250  		hex   string
   251  		tid   TraceID
   252  		valid bool
   253  	}{
   254  		{
   255  			name:  "Valid TraceID",
   256  			tid:   TraceID([16]byte{128, 241, 152, 238, 86, 52, 59, 168, 100, 254, 139, 42, 87, 211, 239, 247}),
   257  			hex:   "80f198ee56343ba864fe8b2a57d3eff7",
   258  			valid: true,
   259  		}, {
   260  			name:  "Invalid TraceID with invalid length",
   261  			hex:   "80f198ee56343ba864fe8b2a57d3eff",
   262  			valid: false,
   263  		}, {
   264  			name:  "Invalid TraceID with invalid char",
   265  			hex:   "80f198ee56343ba864fe8b2a57d3efg7",
   266  			valid: false,
   267  		}, {
   268  			name:  "Invalid TraceID with uppercase",
   269  			hex:   "80f198ee56343ba864fe8b2a57d3efF7",
   270  			valid: false,
   271  		}, {
   272  			name:  "Invalid TraceID with zero value",
   273  			hex:   "00000000000000000000000000000000",
   274  			valid: false,
   275  		},
   276  	} {
   277  		t.Run(testcase.name, func(t *testing.T) {
   278  			tid, err := TraceIDFromHex(testcase.hex)
   279  
   280  			if testcase.valid && err != nil {
   281  				t.Errorf("Expected TraceID %s to be valid but end with error %s", testcase.hex, err.Error())
   282  			}
   283  
   284  			if !testcase.valid && err == nil {
   285  				t.Errorf("Expected TraceID %s to be invalid but end no error", testcase.hex)
   286  			}
   287  
   288  			if tid != testcase.tid {
   289  				t.Errorf("Want: %v, but have: %v", testcase.tid, tid)
   290  			}
   291  		})
   292  	}
   293  }
   294  
   295  func TestSpanContextHasTraceID(t *testing.T) {
   296  	for _, testcase := range []struct {
   297  		name string
   298  		tid  TraceID
   299  		want bool
   300  	}{
   301  		{
   302  			name: "SpanContext.HasTraceID() returns true if both Low and High are nonzero",
   303  			tid:  TraceID([16]byte{1}),
   304  			want: true,
   305  		}, {
   306  			name: "SpanContext.HasTraceID() returns false if neither Low nor High are nonzero",
   307  			tid:  TraceID{},
   308  			want: false,
   309  		},
   310  	} {
   311  		t.Run(testcase.name, func(t *testing.T) {
   312  			// proto: func (sc SpanContext) HasTraceID() bool{}
   313  			sc := SpanContext{traceID: testcase.tid}
   314  			have := sc.HasTraceID()
   315  			if have != testcase.want {
   316  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
   317  			}
   318  		})
   319  	}
   320  }
   321  
   322  func TestSpanContextHasSpanID(t *testing.T) {
   323  	for _, testcase := range []struct {
   324  		name string
   325  		sc   SpanContext
   326  		want bool
   327  	}{
   328  		{
   329  			name: "SpanContext.HasSpanID() returns true if self.SpanID != 0",
   330  			sc:   SpanContext{spanID: [8]byte{42}},
   331  			want: true,
   332  		}, {
   333  			name: "SpanContext.HasSpanID() returns false if self.SpanID == 0",
   334  			sc:   SpanContext{},
   335  			want: false,
   336  		},
   337  	} {
   338  		t.Run(testcase.name, func(t *testing.T) {
   339  			// proto: func (sc SpanContext) HasSpanID() bool {}
   340  			have := testcase.sc.HasSpanID()
   341  			if have != testcase.want {
   342  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
   343  			}
   344  		})
   345  	}
   346  }
   347  
   348  func TestTraceFlagsIsSampled(t *testing.T) {
   349  	for _, testcase := range []struct {
   350  		name string
   351  		tf   TraceFlags
   352  		want bool
   353  	}{
   354  		{
   355  			name: "sampled",
   356  			tf:   FlagsSampled,
   357  			want: true,
   358  		}, {
   359  			name: "unused bits are ignored, still not sampled",
   360  			tf:   ^FlagsSampled,
   361  			want: false,
   362  		}, {
   363  			name: "unused bits are ignored, still sampled",
   364  			tf:   FlagsSampled | ^FlagsSampled,
   365  			want: true,
   366  		}, {
   367  			name: "not sampled/default",
   368  			want: false,
   369  		},
   370  	} {
   371  		t.Run(testcase.name, func(t *testing.T) {
   372  			have := testcase.tf.IsSampled()
   373  			if have != testcase.want {
   374  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  func TestTraceFlagsWithSampled(t *testing.T) {
   381  	for _, testcase := range []struct {
   382  		name   string
   383  		start  TraceFlags
   384  		sample bool
   385  		want   TraceFlags
   386  	}{
   387  		{
   388  			name:   "sampled unchanged",
   389  			start:  FlagsSampled,
   390  			want:   FlagsSampled,
   391  			sample: true,
   392  		}, {
   393  			name:   "become sampled",
   394  			want:   FlagsSampled,
   395  			sample: true,
   396  		}, {
   397  			name:   "unused bits are ignored, still not sampled",
   398  			start:  ^FlagsSampled,
   399  			want:   ^FlagsSampled,
   400  			sample: false,
   401  		}, {
   402  			name:   "unused bits are ignored, becomes sampled",
   403  			start:  ^FlagsSampled,
   404  			want:   FlagsSampled | ^FlagsSampled,
   405  			sample: true,
   406  		}, {
   407  			name:   "not sampled/default",
   408  			sample: false,
   409  		},
   410  	} {
   411  		t.Run(testcase.name, func(t *testing.T) {
   412  			have := testcase.start.WithSampled(testcase.sample)
   413  			if have != testcase.want {
   414  				t.Errorf("Want: %v, but have: %v", testcase.want, have)
   415  			}
   416  		})
   417  	}
   418  }
   419  
   420  func TestStringTraceID(t *testing.T) {
   421  	for _, testcase := range []struct {
   422  		name string
   423  		tid  TraceID
   424  		want string
   425  	}{
   426  		{
   427  			name: "TraceID.String returns string representation of self.TraceID values > 0",
   428  			tid:  TraceID([16]byte{255}),
   429  			want: "ff000000000000000000000000000000",
   430  		},
   431  		{
   432  			name: "TraceID.String returns string representation of self.TraceID values == 0",
   433  			tid:  TraceID([16]byte{}),
   434  			want: "00000000000000000000000000000000",
   435  		},
   436  	} {
   437  		t.Run(testcase.name, func(t *testing.T) {
   438  			// proto: func (t TraceID) String() string {}
   439  			have := testcase.tid.String()
   440  			if have != testcase.want {
   441  				t.Errorf("Want: %s, but have: %s", testcase.want, have)
   442  			}
   443  		})
   444  	}
   445  }
   446  
   447  func TestStringSpanID(t *testing.T) {
   448  	for _, testcase := range []struct {
   449  		name string
   450  		sid  SpanID
   451  		want string
   452  	}{
   453  		{
   454  			name: "SpanID.String returns string representation of self.SpanID values > 0",
   455  			sid:  SpanID([8]byte{255}),
   456  			want: "ff00000000000000",
   457  		},
   458  		{
   459  			name: "SpanID.String returns string representation of self.SpanID values == 0",
   460  			sid:  SpanID([8]byte{}),
   461  			want: "0000000000000000",
   462  		},
   463  	} {
   464  		t.Run(testcase.name, func(t *testing.T) {
   465  			// proto: func (t TraceID) String() string {}
   466  			have := testcase.sid.String()
   467  			if have != testcase.want {
   468  				t.Errorf("Want: %s, but have: %s", testcase.want, have)
   469  			}
   470  		})
   471  	}
   472  }
   473  
   474  func TestValidateSpanKind(t *testing.T) {
   475  	tests := []struct {
   476  		in   SpanKind
   477  		want SpanKind
   478  	}{
   479  		{
   480  			SpanKindUnspecified,
   481  			SpanKindInternal,
   482  		},
   483  		{
   484  			SpanKindInternal,
   485  			SpanKindInternal,
   486  		},
   487  		{
   488  			SpanKindServer,
   489  			SpanKindServer,
   490  		},
   491  		{
   492  			SpanKindClient,
   493  			SpanKindClient,
   494  		},
   495  		{
   496  			SpanKindProducer,
   497  			SpanKindProducer,
   498  		},
   499  		{
   500  			SpanKindConsumer,
   501  			SpanKindConsumer,
   502  		},
   503  	}
   504  	for _, test := range tests {
   505  		if got := ValidateSpanKind(test.in); got != test.want {
   506  			t.Errorf("ValidateSpanKind(%#v) = %#v, want %#v", test.in, got, test.want)
   507  		}
   508  	}
   509  }
   510  
   511  func TestSpanKindString(t *testing.T) {
   512  	tests := []struct {
   513  		in   SpanKind
   514  		want string
   515  	}{
   516  		{
   517  			SpanKindUnspecified,
   518  			"unspecified",
   519  		},
   520  		{
   521  			SpanKindInternal,
   522  			"internal",
   523  		},
   524  		{
   525  			SpanKindServer,
   526  			"server",
   527  		},
   528  		{
   529  			SpanKindClient,
   530  			"client",
   531  		},
   532  		{
   533  			SpanKindProducer,
   534  			"producer",
   535  		},
   536  		{
   537  			SpanKindConsumer,
   538  			"consumer",
   539  		},
   540  	}
   541  	for _, test := range tests {
   542  		if got := test.in.String(); got != test.want {
   543  			t.Errorf("%#v.String() = %#v, want %#v", test.in, got, test.want)
   544  		}
   545  	}
   546  }
   547  
   548  func assertSpanContextEqual(got SpanContext, want SpanContext) bool {
   549  	return got.spanID == want.spanID &&
   550  		got.traceID == want.traceID &&
   551  		got.traceFlags == want.traceFlags &&
   552  		got.remote == want.remote &&
   553  		got.traceState.String() == want.traceState.String()
   554  }
   555  
   556  func TestNewSpanContext(t *testing.T) {
   557  	testCases := []struct {
   558  		name                string
   559  		config              SpanContextConfig
   560  		expectedSpanContext SpanContext
   561  	}{
   562  		{
   563  			name: "Complete SpanContext",
   564  			config: SpanContextConfig{
   565  				TraceID:    TraceID([16]byte{1}),
   566  				SpanID:     SpanID([8]byte{42}),
   567  				TraceFlags: 0x1,
   568  				TraceState: TraceState{list: []member{
   569  					{"foo", "bar"},
   570  				}},
   571  			},
   572  			expectedSpanContext: SpanContext{
   573  				traceID:    TraceID([16]byte{1}),
   574  				spanID:     SpanID([8]byte{42}),
   575  				traceFlags: 0x1,
   576  				traceState: TraceState{list: []member{
   577  					{"foo", "bar"},
   578  				}},
   579  			},
   580  		},
   581  		{
   582  			name:                "Empty SpanContext",
   583  			config:              SpanContextConfig{},
   584  			expectedSpanContext: SpanContext{},
   585  		},
   586  		{
   587  			name: "Partial SpanContext",
   588  			config: SpanContextConfig{
   589  				TraceID: TraceID([16]byte{1}),
   590  				SpanID:  SpanID([8]byte{42}),
   591  			},
   592  			expectedSpanContext: SpanContext{
   593  				traceID:    TraceID([16]byte{1}),
   594  				spanID:     SpanID([8]byte{42}),
   595  				traceFlags: 0x0,
   596  				traceState: TraceState{},
   597  			},
   598  		},
   599  	}
   600  
   601  	for _, tc := range testCases {
   602  		t.Run(tc.name, func(t *testing.T) {
   603  			sctx := NewSpanContext(tc.config)
   604  			if !assertSpanContextEqual(sctx, tc.expectedSpanContext) {
   605  				t.Fatalf("%s: Unexpected context created: %s", tc.name, cmp.Diff(sctx, tc.expectedSpanContext))
   606  			}
   607  		})
   608  	}
   609  }
   610  
   611  func TestSpanContextDerivation(t *testing.T) {
   612  	from := SpanContext{}
   613  	to := SpanContext{traceID: TraceID([16]byte{1})}
   614  
   615  	modified := from.WithTraceID(to.TraceID())
   616  	if !assertSpanContextEqual(modified, to) {
   617  		t.Fatalf("WithTraceID: Unexpected context created: %s", cmp.Diff(modified, to))
   618  	}
   619  
   620  	from = to
   621  	to.spanID = SpanID([8]byte{42})
   622  
   623  	modified = from.WithSpanID(to.SpanID())
   624  	if !assertSpanContextEqual(modified, to) {
   625  		t.Fatalf("WithSpanID: Unexpected context created: %s", cmp.Diff(modified, to))
   626  	}
   627  
   628  	from = to
   629  	to.traceFlags = 0x13
   630  
   631  	modified = from.WithTraceFlags(to.TraceFlags())
   632  	if !assertSpanContextEqual(modified, to) {
   633  		t.Fatalf("WithTraceFlags: Unexpected context created: %s", cmp.Diff(modified, to))
   634  	}
   635  
   636  	from = to
   637  	to.traceState = TraceState{list: []member{{"foo", "bar"}}}
   638  
   639  	modified = from.WithTraceState(to.TraceState())
   640  	if !assertSpanContextEqual(modified, to) {
   641  		t.Fatalf("WithTraceState: Unexpected context created: %s", cmp.Diff(modified, to))
   642  	}
   643  }
   644  
   645  func TestLinkFromContext(t *testing.T) {
   646  	k1v1 := attribute.String("key1", "value1")
   647  	spanCtx := SpanContext{traceID: TraceID([16]byte{1}), remote: true}
   648  
   649  	receiverCtx := ContextWithRemoteSpanContext(context.Background(), spanCtx)
   650  	link := LinkFromContext(receiverCtx, k1v1)
   651  
   652  	if !assertSpanContextEqual(link.SpanContext, spanCtx) {
   653  		t.Fatalf("LinkFromContext: Unexpected context created: %s", cmp.Diff(link.SpanContext, spanCtx))
   654  	}
   655  	assert.Equal(t, link.Attributes[0], k1v1)
   656  }
   657  

View as plain text