| // Copyright 2025 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package storage |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "testing" |
| |
| "cloud.google.com/go/internal/testutil" |
| "cloud.google.com/go/storage/internal" |
| "github.com/google/go-cmp/cmp" |
| "go.opentelemetry.io/otel/attribute" |
| otcodes "go.opentelemetry.io/otel/codes" |
| "go.opentelemetry.io/otel/sdk/instrumentation" |
| "go.opentelemetry.io/otel/sdk/trace/tracetest" |
| "go.opentelemetry.io/otel/trace" |
| "google.golang.org/api/googleapi" |
| ) |
| |
| func TestStorageTraceStartEndSpan(t *testing.T) { |
| ctx := context.Background() |
| te := testutil.NewOpenTelemetryTestExporter() |
| t.Cleanup(func() { |
| te.Unregister(ctx) |
| }) |
| |
| // TODO: Remove setting development env var upon launch. |
| t.Setenv("GO_STORAGE_DEV_OTEL_TRACING", "true") |
| |
| spanName := "storage.TestTrace.TestStartEndSpan" |
| ctx, span := startSpan(ctx, spanName) |
| newAttrs := attribute.Int("fakeKey", 800) |
| span.SetAttributes(newAttrs) |
| endSpan(ctx, nil) |
| |
| spans := te.Spans() |
| gotSpan := spans[0] |
| if len(spans) != 1 { |
| t.Errorf("expected one span, got %d", len(spans)) |
| } |
| if got, want := gotSpan.Name, appendPackageName(spanName); got != want { |
| t.Fatalf("got %s, want %s", got, want) |
| } |
| |
| wantSpan := createWantSpanStub(spanName, getCommonAttributes()) |
| wantSpan.Attributes = append(wantSpan.Attributes, newAttrs) |
| opts := []cmp.Option{ |
| cmp.Comparer(spanAttributesComparer), |
| } |
| if diff := testutil.Diff(gotSpan, wantSpan, opts...); diff != "" { |
| t.Errorf("diff: -got, +want:\n%s\n", diff) |
| } |
| } |
| func TestStorageTraceStartSpanOption(t *testing.T) { |
| ctx := context.Background() |
| te := testutil.NewOpenTelemetryTestExporter() |
| t.Cleanup(func() { |
| te.Unregister(ctx) |
| }) |
| |
| // TODO: Remove setting development env var upon launch. |
| t.Setenv("GO_STORAGE_DEV_OTEL_TRACING", "true") |
| |
| spanName := "storage.TestTrace.TestStartSpanOption" |
| attrMap := make(map[string]interface{}) |
| attrMap["my_string"] = "my string" |
| attrMap["my_bool"] = true |
| attrMap["my_int"] = 123 |
| attrMap["my_int64"] = int64(456) |
| attrMap["my_float"] = 0.9 |
| spanStartOpts := makeSpanStartOptAttrs(attrMap) |
| |
| ctx, _ = startSpan(ctx, spanName, spanStartOpts...) |
| endSpan(ctx, nil) |
| |
| spans := te.Spans() |
| gotSpan := spans[0] |
| if len(spans) != 1 { |
| t.Errorf("expected one span, got %d", len(spans)) |
| } |
| if got, want := gotSpan.Name, appendPackageName(spanName); got != want { |
| t.Fatalf("got %s, want %s", got, want) |
| } |
| |
| wantSpan := createWantSpanStub(spanName, getCommonAttributes()) |
| wantSpan.Attributes = append(wantSpan.Attributes, otAttrs(attrMap)...) |
| opts := []cmp.Option{ |
| cmp.Comparer(spanAttributesComparer), |
| } |
| if diff := testutil.Diff(gotSpan, wantSpan, opts...); diff != "" { |
| t.Errorf("diff: -got, +want:\n%s\n", diff) |
| } |
| } |
| |
| func TestStorageTraceEndSpanRecordError(t *testing.T) { |
| ctx := context.Background() |
| te := testutil.NewOpenTelemetryTestExporter() |
| t.Cleanup(func() { |
| te.Unregister(ctx) |
| }) |
| |
| // TODO: Remove setting development env var upon launch. |
| t.Setenv("GO_STORAGE_DEV_OTEL_TRACING", "true") |
| |
| spanName := "storage.TestTrace.TestRecordError" |
| ctx, _ = startSpan(ctx, spanName) |
| err := &googleapi.Error{Code: http.StatusBadRequest, Message: "INVALID ARGUMENT"} |
| endSpan(ctx, err) |
| |
| spans := te.Spans() |
| gotSpan := spans[0] |
| if len(spans) != 1 { |
| t.Errorf("expected one span, got %d", len(spans)) |
| } |
| if got, want := gotSpan.Name, appendPackageName(spanName); got != want { |
| t.Fatalf("got %s, want %s", got, want) |
| } |
| if want := otcodes.Error; gotSpan.Status.Code != want { |
| t.Errorf("got %v, want %v", gotSpan.Status.Code, want) |
| } |
| } |
| |
| func createWantSpanStub(spanName string, attrs []attribute.KeyValue) tracetest.SpanStub { |
| return tracetest.SpanStub{ |
| Name: appendPackageName(spanName), |
| Attributes: attrs, |
| InstrumentationScope: instrumentation.Scope{ |
| Name: "cloud.google.com/go/storage", |
| Version: internal.Version, |
| }, |
| } |
| } |
| |
| func spanAttributesComparer(a, b tracetest.SpanStub) bool { |
| if a.Name != b.Name { |
| return false |
| } |
| if len(a.Attributes) != len(b.Attributes) { |
| return false |
| } |
| if a.InstrumentationScope != b.InstrumentationScope { |
| return false |
| } |
| return true |
| } |
| |
| // makeSpanStartOptAttrs makes a SpanStartOption and converts a generic map to OpenTelemetry attributes. |
| func makeSpanStartOptAttrs(attrMap map[string]interface{}) []trace.SpanStartOption { |
| attrs := otAttrs(attrMap) |
| return []trace.SpanStartOption{ |
| trace.WithAttributes(attrs...), |
| } |
| } |
| |
| // otAttrs converts a generic map to OpenTelemetry attributes. |
| func otAttrs(attrMap map[string]interface{}) []attribute.KeyValue { |
| var attrs []attribute.KeyValue |
| for k, v := range attrMap { |
| var a attribute.KeyValue |
| switch v := v.(type) { |
| case string: |
| a = attribute.Key(k).String(v) |
| case bool: |
| a = attribute.Key(k).Bool(v) |
| case int: |
| a = attribute.Key(k).Int(v) |
| case int64: |
| a = attribute.Key(k).Int64(v) |
| default: |
| a = attribute.Key(k).String(fmt.Sprintf("%#v", v)) |
| } |
| attrs = append(attrs, a) |
| } |
| return attrs |
| } |