blob: 3d0d118cf7a4efc9696e225c7a48ca1985eee26b [file] [edit]
// 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
}