...

Source file src/cloud.google.com/go/internal/trace/trace_test.go

Documentation: cloud.google.com/go/internal/trace

     1  // Copyright 2018 Google LLC
     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  	"context"
    19  	"errors"
    20  	"net/http"
    21  	"sort"
    22  	"testing"
    23  
    24  	"cloud.google.com/go/internal/testutil"
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/go-cmp/cmp/cmpopts"
    27  	"github.com/googleapis/gax-go/v2/apierror"
    28  	octrace "go.opencensus.io/trace"
    29  	"go.opentelemetry.io/otel/attribute"
    30  	otcodes "go.opentelemetry.io/otel/codes"
    31  	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    32  	"google.golang.org/api/googleapi"
    33  	"google.golang.org/genproto/googleapis/rpc/code"
    34  	"google.golang.org/grpc/codes"
    35  	"google.golang.org/grpc/status"
    36  )
    37  
    38  var (
    39  	ignoreEventFields = cmpopts.IgnoreFields(sdktrace.Event{}, "Time")
    40  	ignoreValueFields = cmpopts.IgnoreFields(attribute.Value{}, "vtype", "numeric", "stringly", "slice")
    41  )
    42  
    43  func TestStartSpan_OpenCensus(t *testing.T) {
    44  	old := IsOpenTelemetryTracingEnabled()
    45  	SetOpenTelemetryTracingEnabledField(false)
    46  	te := testutil.NewTestExporter()
    47  	t.Cleanup(func() {
    48  		SetOpenTelemetryTracingEnabledField(old)
    49  		te.Unregister()
    50  	})
    51  
    52  	ctx := context.Background()
    53  	ctx = StartSpan(ctx, "test-span")
    54  
    55  	TracePrintf(ctx, annotationData(), "Add my annotations")
    56  
    57  	err := &googleapi.Error{Code: http.StatusBadRequest, Message: "INVALID ARGUMENT"}
    58  	EndSpan(ctx, err)
    59  
    60  	if !IsOpenCensusTracingEnabled() {
    61  		t.Errorf("got false, want true")
    62  	}
    63  	if IsOpenTelemetryTracingEnabled() {
    64  		t.Errorf("got true, want false")
    65  	}
    66  	spans := te.Spans
    67  	if len(spans) != 1 {
    68  		t.Fatalf("got %d, want 1", len(spans))
    69  	}
    70  	if got, want := spans[0].Name, "test-span"; got != want {
    71  		t.Fatalf("got %s, want %s", got, want)
    72  	}
    73  	if want := int32(3); spans[0].Status.Code != want {
    74  		t.Errorf("got %v, want %v", spans[0].Status.Code, want)
    75  	}
    76  	if want := "INVALID ARGUMENT"; spans[0].Status.Message != want {
    77  		t.Errorf("got %v, want %v", spans[0].Status.Message, want)
    78  	}
    79  	if len(spans[0].Annotations) != 1 {
    80  		t.Fatalf("got %d, want 1", len(spans[0].Annotations))
    81  	}
    82  	got := spans[0].Annotations[0].Attributes
    83  	want := make(map[string]interface{})
    84  	want["my_bool"] = true
    85  	want["my_float"] = "0.9"
    86  	want["my_int"] = int64(123)
    87  	want["my_int64"] = int64(456)
    88  	want["my_string"] = "my string"
    89  	opt := cmpopts.SortMaps(func(a, b int) bool {
    90  		return a < b
    91  	})
    92  	if !cmp.Equal(got, want, opt) {
    93  		t.Errorf("got(-), want(+),: \n%s", cmp.Diff(got, want, opt))
    94  	}
    95  }
    96  
    97  func TestStartSpan_OpenTelemetry(t *testing.T) {
    98  	old := IsOpenTelemetryTracingEnabled()
    99  	SetOpenTelemetryTracingEnabledField(true)
   100  	ctx := context.Background()
   101  	te := testutil.NewOpenTelemetryTestExporter()
   102  	t.Cleanup(func() {
   103  		SetOpenTelemetryTracingEnabledField(old)
   104  		te.Unregister(ctx)
   105  	})
   106  
   107  	ctx = StartSpan(ctx, "test-span")
   108  
   109  	TracePrintf(ctx, annotationData(), "Add my annotations")
   110  
   111  	err := &googleapi.Error{Code: http.StatusBadRequest, Message: "INVALID ARGUMENT"}
   112  	EndSpan(ctx, err)
   113  
   114  	if IsOpenCensusTracingEnabled() {
   115  		t.Errorf("got true, want false")
   116  	}
   117  	if !IsOpenTelemetryTracingEnabled() {
   118  		t.Errorf("got false, want true")
   119  	}
   120  	spans := te.Spans()
   121  	if len(spans) != 1 {
   122  		t.Fatalf("got %d, want 1", len(spans))
   123  	}
   124  	if got, want := spans[0].Name, "test-span"; got != want {
   125  		t.Fatalf("got %s, want %s", got, want)
   126  	}
   127  	if want := otcodes.Error; spans[0].Status.Code != want {
   128  		t.Errorf("got %v, want %v", spans[0].Status.Code, want)
   129  	}
   130  	if want := "INVALID ARGUMENT"; spans[0].Status.Description != want {
   131  		t.Errorf("got %v, want %v", spans[0].Status.Description, want)
   132  	}
   133  
   134  	want := []attribute.KeyValue{
   135  		attribute.Key("my_bool").Bool(true),
   136  		attribute.Key("my_float").String("0.9"),
   137  		attribute.Key("my_int").Int(123),
   138  		attribute.Key("my_int64").Int64(int64(456)),
   139  		attribute.Key("my_string").String("my string"),
   140  	}
   141  	got := spans[0].Events[0].Attributes
   142  	// Sorting is required since the TracePrintf parameter is a map.
   143  	sort.Slice(got, func(i, j int) bool {
   144  		return got[i].Key < got[j].Key
   145  	})
   146  	if !cmp.Equal(got, want, ignoreEventFields, ignoreValueFields) {
   147  		t.Errorf("got %v, want %v", got, want)
   148  	}
   149  	wantEvent := sdktrace.Event{
   150  		Name: "exception",
   151  		Attributes: []attribute.KeyValue{
   152  			// KeyValues are NOT sorted by key, but the sort is deterministic,
   153  			// since this Event was created by Span.RecordError.
   154  			attribute.Key("exception.type").String("*googleapi.Error"),
   155  			attribute.Key("exception.message").String("googleapi: Error 400: INVALID ARGUMENT"),
   156  		},
   157  	}
   158  	if !cmp.Equal(spans[0].Events[1], wantEvent, ignoreEventFields, ignoreValueFields) {
   159  		t.Errorf("got %v, want %v", spans[0].Events[1], want)
   160  	}
   161  }
   162  
   163  func TestToStatus(t *testing.T) {
   164  	for _, testcase := range []struct {
   165  		input error
   166  		want  octrace.Status
   167  	}{
   168  		{
   169  			errors.New("some random error"),
   170  			octrace.Status{Code: int32(code.Code_UNKNOWN), Message: "some random error"},
   171  		},
   172  		{
   173  			&googleapi.Error{Code: http.StatusConflict, Message: "some specific googleapi http error"},
   174  			octrace.Status{Code: int32(code.Code_ALREADY_EXISTS), Message: "some specific googleapi http error"},
   175  		},
   176  		{
   177  			status.Error(codes.DataLoss, "some specific grpc error"),
   178  			octrace.Status{Code: int32(code.Code_DATA_LOSS), Message: "some specific grpc error"},
   179  		},
   180  	} {
   181  		got := toStatus(testcase.input)
   182  		if r := testutil.Diff(got, testcase.want); r != "" {
   183  			t.Errorf("got -, want +:\n%s", r)
   184  		}
   185  	}
   186  }
   187  
   188  func TestToOpenTelemetryStatusDescription(t *testing.T) {
   189  	for _, testcase := range []struct {
   190  		input error
   191  		want  string
   192  	}{
   193  		{
   194  			errors.New("some random error"),
   195  			"some random error",
   196  		},
   197  		{
   198  			&googleapi.Error{Code: http.StatusConflict, Message: "some specific googleapi http error"},
   199  			"some specific googleapi http error",
   200  		},
   201  		{
   202  			status.Error(codes.DataLoss, "some specific grpc error"),
   203  			"some specific grpc error",
   204  		},
   205  	} {
   206  		// Wrap supported types in apierror.APIError as GAPIC clients
   207  		// do, but fall back to the unwrapped error if not supported.
   208  		// https://github.com/googleapis/gax-go/blob/v2.12.0/v2/invoke.go#L95
   209  		var err error
   210  		err, ok := apierror.FromError(testcase.input)
   211  		if !ok {
   212  			err = testcase.input
   213  		}
   214  
   215  		got := toOpenTelemetryStatusDescription(err)
   216  		if got != testcase.want {
   217  			t.Errorf("got %s, want %s", got, testcase.want)
   218  		}
   219  	}
   220  }
   221  
   222  func TestToStatus_APIError(t *testing.T) {
   223  	for _, testcase := range []struct {
   224  		input error
   225  		want  octrace.Status
   226  	}{
   227  		{
   228  			// Apparently nonsensical error, but this is supported by the implementation.
   229  			&googleapi.Error{Code: 200, Message: "OK"},
   230  			octrace.Status{Code: int32(code.Code_OK), Message: "OK"},
   231  		},
   232  		{
   233  			&googleapi.Error{Code: 499, Message: "error 499"},
   234  			octrace.Status{Code: int32(code.Code_CANCELLED), Message: "error 499"},
   235  		},
   236  		{
   237  			&googleapi.Error{Code: http.StatusInternalServerError, Message: "error 500"},
   238  			octrace.Status{Code: int32(code.Code_UNKNOWN), Message: "error 500"},
   239  		},
   240  		{
   241  			&googleapi.Error{Code: http.StatusBadRequest, Message: "error 400"},
   242  			octrace.Status{Code: int32(code.Code_INVALID_ARGUMENT), Message: "error 400"},
   243  		},
   244  		{
   245  			&googleapi.Error{Code: http.StatusGatewayTimeout, Message: "error 504"},
   246  			octrace.Status{Code: int32(code.Code_DEADLINE_EXCEEDED), Message: "error 504"},
   247  		},
   248  		{
   249  			&googleapi.Error{Code: http.StatusNotFound, Message: "error 404"},
   250  			octrace.Status{Code: int32(code.Code_NOT_FOUND), Message: "error 404"},
   251  		},
   252  		{
   253  			&googleapi.Error{Code: http.StatusConflict, Message: "error 409"},
   254  			octrace.Status{Code: int32(code.Code_ALREADY_EXISTS), Message: "error 409"},
   255  		},
   256  		{
   257  			&googleapi.Error{Code: http.StatusForbidden, Message: "error 403"},
   258  			octrace.Status{Code: int32(code.Code_PERMISSION_DENIED), Message: "error 403"},
   259  		},
   260  		{
   261  			&googleapi.Error{Code: http.StatusUnauthorized, Message: "error 401"},
   262  			octrace.Status{Code: int32(code.Code_UNAUTHENTICATED), Message: "error 401"},
   263  		},
   264  		{
   265  			&googleapi.Error{Code: http.StatusTooManyRequests, Message: "error 429"},
   266  			octrace.Status{Code: int32(code.Code_RESOURCE_EXHAUSTED), Message: "error 429"},
   267  		},
   268  		{
   269  			&googleapi.Error{Code: http.StatusNotImplemented, Message: "error 501"},
   270  			octrace.Status{Code: int32(code.Code_UNIMPLEMENTED), Message: "error 501"},
   271  		},
   272  		{
   273  			&googleapi.Error{Code: http.StatusServiceUnavailable, Message: "error 503"},
   274  			octrace.Status{Code: int32(code.Code_UNAVAILABLE), Message: "error 503"},
   275  		},
   276  		{
   277  			&googleapi.Error{Code: http.StatusMovedPermanently, Message: "error 301"},
   278  			octrace.Status{Code: int32(code.Code_UNKNOWN), Message: "error 301"},
   279  		},
   280  	} {
   281  		// Wrap googleapi.Error in apierror.APIError as GAPIC clients do.
   282  		// https://github.com/googleapis/gax-go/blob/v2.12.0/v2/invoke.go#L95
   283  		err, ok := apierror.FromError(testcase.input)
   284  		if !ok {
   285  			t.Fatalf("apierror.FromError failed to parse %v", testcase.input)
   286  		}
   287  		got := toStatus(err)
   288  		if r := testutil.Diff(got, testcase.want); r != "" {
   289  			t.Errorf("got -, want +:\n%s", r)
   290  		}
   291  	}
   292  }
   293  
   294  func annotationData() map[string]interface{} {
   295  	attrMap := make(map[string]interface{})
   296  	attrMap["my_string"] = "my string"
   297  	attrMap["my_bool"] = true
   298  	attrMap["my_int"] = 123
   299  	attrMap["my_int64"] = int64(456)
   300  	attrMap["my_float"] = 0.9
   301  	return attrMap
   302  }
   303  

View as plain text