1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
153
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
207
208
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
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
282
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