google.golang.org/grpc@v1.72.2/stats/opentelemetry/grpc_trace_bin_propagator_test.go (about)

     1  /*
     2   *
     3   * Copyright 2024 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package opentelemetry
    20  
    21  import (
    22  	"context"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	oteltrace "go.opentelemetry.io/otel/trace"
    27  	"google.golang.org/grpc/metadata"
    28  	itracing "google.golang.org/grpc/stats/opentelemetry/internal/tracing"
    29  )
    30  
    31  var validSpanContext = oteltrace.SpanContext{}.WithTraceID(
    32  	oteltrace.TraceID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}).WithSpanID(
    33  	oteltrace.SpanID{17, 18, 19, 20, 21, 22, 23, 24}).WithTraceFlags(
    34  	oteltrace.TraceFlags(1))
    35  
    36  // TestInject_ValidSpanContext verifies that the GRPCTraceBinPropagator
    37  // correctly injects a valid OpenTelemetry span context as `grpc-trace-bin`
    38  // header in the provided carrier's context metadata.
    39  //
    40  // It verifies that if a valid span context is injected, same span context can
    41  // can be retreived from the carrier's context metadata.
    42  func (s) TestInject_ValidSpanContext(t *testing.T) {
    43  	p := GRPCTraceBinPropagator{}
    44  	ctx, cancel := context.WithCancel(context.Background())
    45  	defer cancel()
    46  	c := itracing.NewOutgoingCarrier(ctx)
    47  	ctx = oteltrace.ContextWithSpanContext(ctx, validSpanContext)
    48  
    49  	p.Inject(ctx, c)
    50  
    51  	md, _ := metadata.FromOutgoingContext(c.Context())
    52  	gotH := md.Get(grpcTraceBinHeaderKey)
    53  	if gotH[len(gotH)-1] == "" {
    54  		t.Fatalf("got empty value from Carrier's context metadata grpc-trace-bin header, want valid span context: %v", validSpanContext)
    55  	}
    56  	gotSC, ok := fromBinary([]byte(gotH[len(gotH)-1]))
    57  	if !ok {
    58  		t.Fatalf("got invalid span context %v from Carrier's context metadata grpc-trace-bin header, want valid span context: %v", gotSC, validSpanContext)
    59  	}
    60  	if cmp.Equal(validSpanContext, gotSC) {
    61  		t.Fatalf("got span context = %v, want span contexts %v", gotSC, validSpanContext)
    62  	}
    63  }
    64  
    65  // TestInject_InvalidSpanContext verifies that the GRPCTraceBinPropagator does
    66  // not inject an invalid OpenTelemetry span context as `grpc-trace-bin` header
    67  // in the provided carrier's context metadata.
    68  //
    69  // If an invalid span context is injected, it verifies that `grpc-trace-bin`
    70  // header is not set in the carrier's context metadata.
    71  func (s) TestInject_InvalidSpanContext(t *testing.T) {
    72  	p := GRPCTraceBinPropagator{}
    73  	ctx, cancel := context.WithCancel(context.Background())
    74  	defer cancel()
    75  	c := itracing.NewOutgoingCarrier(ctx)
    76  	ctx = oteltrace.ContextWithSpanContext(ctx, oteltrace.SpanContext{})
    77  
    78  	p.Inject(ctx, c)
    79  
    80  	md, _ := metadata.FromOutgoingContext(c.Context())
    81  	if gotH := md.Get(grpcTraceBinHeaderKey); len(gotH) > 0 {
    82  		t.Fatalf("got %v value from Carrier's context metadata grpc-trace-bin header, want empty", gotH)
    83  	}
    84  }
    85  
    86  // TestExtract verifies that the GRPCTraceBinPropagator correctly extracts
    87  // OpenTelemetry span context data from the provided context using carrier.
    88  //
    89  // If a valid span context was injected, it verifies same trace span context
    90  // is extracted from carrier's metadata for `grpc-trace-bin` header key.
    91  //
    92  // If invalid span context was injected, it verifies that valid trace span
    93  // context is not extracted.
    94  func (s) TestExtract(t *testing.T) {
    95  	tests := []struct {
    96  		name   string
    97  		wantSC oteltrace.SpanContext // expected span context from carrier
    98  	}{
    99  		{
   100  			name:   "valid OpenTelemetry span context",
   101  			wantSC: validSpanContext.WithRemote(true),
   102  		},
   103  		{
   104  			name:   "invalid OpenTelemetry span context",
   105  			wantSC: oteltrace.SpanContext{},
   106  		},
   107  	}
   108  
   109  	for _, test := range tests {
   110  		t.Run(test.name, func(t *testing.T) {
   111  			p := GRPCTraceBinPropagator{}
   112  			ctx, cancel := context.WithCancel(context.Background())
   113  			defer cancel()
   114  			ctx = metadata.NewIncomingContext(ctx, metadata.MD{grpcTraceBinHeaderKey: []string{string(toBinary(test.wantSC))}})
   115  
   116  			c := itracing.NewIncomingCarrier(ctx)
   117  
   118  			tCtx := p.Extract(ctx, c)
   119  			got := oteltrace.SpanContextFromContext(tCtx)
   120  			if !got.Equal(test.wantSC) {
   121  				t.Fatalf("got span context: %v, want span context: %v", got, test.wantSC)
   122  			}
   123  		})
   124  	}
   125  }
   126  
   127  // TestBinary verifies that the toBinary() function correctly serializes a valid
   128  // OpenTelemetry span context into its binary format representation. If span
   129  // context is invalid, it verifies that serialization is nil.
   130  func (s) TestToBinary(t *testing.T) {
   131  	tests := []struct {
   132  		name string
   133  		sc   oteltrace.SpanContext
   134  		want []byte
   135  	}{
   136  		{
   137  			name: "valid context",
   138  			sc:   validSpanContext,
   139  			want: toBinary(validSpanContext),
   140  		},
   141  		{
   142  			name: "zero value context",
   143  			sc:   oteltrace.SpanContext{},
   144  			want: nil,
   145  		},
   146  	}
   147  
   148  	for _, test := range tests {
   149  		t.Run(test.name, func(t *testing.T) {
   150  			if got := toBinary(test.sc); !cmp.Equal(got, test.want) {
   151  				t.Fatalf("binary() = %v, want %v", got, test.want)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  // TestFromBinary verifies that the fromBinary() function correctly
   158  // deserializes a binary format representation of a valid OpenTelemetry span
   159  // context into its corresponding span context format. If span context's binary
   160  // representation is invalid, it verifies that deserialization is zero value
   161  // span context.
   162  func (s) TestFromBinary(t *testing.T) {
   163  	tests := []struct {
   164  		name string
   165  		b    []byte
   166  		want oteltrace.SpanContext
   167  		ok   bool
   168  	}{
   169  		{
   170  			name: "valid",
   171  			b:    []byte{0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 17, 18, 19, 20, 21, 22, 23, 24, 2, 1},
   172  			want: validSpanContext.WithRemote(true),
   173  			ok:   true,
   174  		},
   175  		{
   176  			name: "invalid length",
   177  			b:    []byte{0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 17, 18, 19, 20, 21, 22, 23, 24, 2},
   178  			want: oteltrace.SpanContext{},
   179  			ok:   false,
   180  		},
   181  		{
   182  			name: "invalid version",
   183  			b:    []byte{1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 17, 18, 19, 20, 21, 22, 23, 24, 2, 1},
   184  			want: oteltrace.SpanContext{},
   185  			ok:   false,
   186  		},
   187  		{
   188  			name: "invalid traceID field ID",
   189  			b:    []byte{0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 17, 18, 19, 20, 21, 22, 23, 24, 2, 1},
   190  			want: oteltrace.SpanContext{},
   191  			ok:   false,
   192  		},
   193  		{
   194  			name: "invalid spanID field ID",
   195  			b:    []byte{0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 17, 18, 19, 20, 21, 22, 23, 24, 2, 1},
   196  			want: oteltrace.SpanContext{},
   197  			ok:   false,
   198  		},
   199  		{
   200  			name: "invalid traceFlags field ID",
   201  			b:    []byte{0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 17, 18, 19, 20, 21, 22, 23, 24, 1, 1},
   202  			want: oteltrace.SpanContext{},
   203  			ok:   false,
   204  		},
   205  	}
   206  
   207  	for _, test := range tests {
   208  		t.Run(test.name, func(t *testing.T) {
   209  			got, ok := fromBinary(test.b)
   210  			if ok != test.ok {
   211  				t.Fatalf("fromBinary() ok = %v, want %v", ok, test.ok)
   212  				return
   213  			}
   214  			if !got.Equal(test.want) {
   215  				t.Fatalf("fromBinary() got = %v, want %v", got, test.want)
   216  			}
   217  		})
   218  	}
   219  }