-
Notifications
You must be signed in to change notification settings - Fork 59
chore(showcase): add tracing tests #1725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
quartzmo
wants to merge
23
commits into
googleapis:main
Choose a base branch
from
quartzmo:showcase-tracing-tests
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
996f614
impl(showcase): port observability fixture for mock OTLP testing
quartzmo 18edd84
impl(showcase): port tracing scenarios for integration tests
quartzmo 5e07264
impl(showcase): add tracing tests and update go.mod
quartzmo c995e56
impl(showcase): add cloud trace test, update makefile and showcase.bash
quartzmo 0c1d1af
impl(showcase): stabilize tracing tests and upgrade dependencies
quartzmo b8dbbce
docs(showcase): document how telemetry tests work
quartzmo 565a811
update showcase/README.md
quartzmo f31c1bd
fix(showcase): remove replace directive from go.mod to fix CI apidiff
quartzmo 48b9737
gofmt -w .
quartzmo 02c9e73
fix(showcase): normalize status.message in tests to handle environmen…
quartzmo 5c154c8
chore(showcase): add license headers to new test files
quartzmo 62548d9
add assertion for rpc.system.name: http
quartzmo 3e483c3
add REST assertion for status.message
quartzmo 3858618
add assertions for last span in a retry sequence
quartzmo e65ea61
test: add REST coverage to Cloud Trace integration tests
quartzmo e2de8c5
remove unused metrics and logging types from observability_fixture_te…
quartzmo 3c4ffab
test: refactor trace assertions to use lookup and assert helpers
quartzmo b6d0a91
test: eliminate DYNAMIC placeholder asserts constraints on individual…
quartzmo d41178f
Merge branch 'main' into showcase-tracing-tests
quartzmo 8a4d384
remove unneeded comments
quartzmo d727d5b
test: implement strict attribute assertions and constraint callbacks
quartzmo 3571b0b
test: make resource assertions conditional on GCP environment
quartzmo 6beb98a
test: use checkAbsent constraint to simplify assertCapturedSpan
quartzmo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| // Copyright 2026 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 | ||
| // | ||
| // https://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. | ||
|
|
||
| //go:build telemetry | ||
| // +build telemetry | ||
|
|
||
| package showcase | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/hex" | ||
| "os" | ||
| "testing" | ||
| "time" | ||
|
|
||
| trace "cloud.google.com/go/trace/apiv1" | ||
| "cloud.google.com/go/trace/apiv1/tracepb" | ||
| showcase "github.com/googleapis/gapic-showcase/client" | ||
| gax "github.com/googleapis/gax-go/v2" | ||
| "go.opentelemetry.io/contrib/detectors/gcp" | ||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" | ||
| "go.opentelemetry.io/otel/sdk/resource" | ||
| sdktrace "go.opentelemetry.io/otel/sdk/trace" | ||
| semconv "go.opentelemetry.io/otel/semconv/v1.26.0" | ||
| "golang.org/x/oauth2" | ||
| "golang.org/x/oauth2/google" | ||
| "google.golang.org/api/option" | ||
| "google.golang.org/grpc" | ||
| "google.golang.org/grpc/credentials" | ||
| "google.golang.org/grpc/credentials/insecure" | ||
| "google.golang.org/grpc/credentials/oauth" | ||
| ) | ||
|
|
||
| // setupCloudTrace configures and installs a new tracer provider with a GCP | ||
| // resource detector that points to telemetry.googleapis.com. It retrieves the | ||
| // project ID from the environment or default credentials, configures telemetry | ||
| // environment variables, and initializes the OTLP exporter to send traces to | ||
| // Cloud Trace. | ||
| func setupCloudTrace(t *testing.T) string { | ||
| ctx := context.Background() | ||
| creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/cloud-platform") | ||
| if err != nil { | ||
| t.Skipf("Skipping Cloud Trace integration test: %v", err) | ||
| } | ||
| projectID := os.Getenv("GCLOUD_TESTS_GOLANG_PROJECT_ID") | ||
| if projectID == "" { | ||
| projectID = creds.ProjectID | ||
| } | ||
| if projectID == "" { | ||
| t.Skip("Skipping Cloud Trace integration test: no project ID found in GCLOUD_TESTS_GOLANG_PROJECT_ID or default credentials") | ||
| } | ||
|
|
||
| gax.TestOnlyResetIsFeatureEnabled() | ||
| t.Cleanup(gax.TestOnlyResetIsFeatureEnabled) | ||
| os.Setenv("GOOGLE_SDK_GO_EXPERIMENTAL_TRACING", "true") | ||
| t.Cleanup(func() { os.Unsetenv("GOOGLE_SDK_GO_EXPERIMENTAL_TRACING") }) | ||
|
|
||
| // Set OTEL_RESOURCE_ATTRIBUTES with project_id for the telemetry endpoint | ||
| os.Setenv("OTEL_RESOURCE_ATTRIBUTES", "gcp.project_id="+projectID) | ||
| t.Cleanup(func() { os.Unsetenv("OTEL_RESOURCE_ATTRIBUTES") }) | ||
|
|
||
| // The telemetry endpoint requires a quota project when using ADC user credentials | ||
| os.Setenv("GOOGLE_CLOUD_QUOTA_PROJECT", projectID) | ||
| t.Cleanup(func() { os.Unsetenv("GOOGLE_CLOUD_QUOTA_PROJECT") }) | ||
|
|
||
| grpcCreds, err := oauth.NewApplicationDefault(ctx) | ||
| if err != nil { | ||
| t.Fatalf("failed to create gRPC credentials: %v", err) | ||
| } | ||
|
|
||
| // Initialize the OTLP exporter to point to telemetry.googleapis.com | ||
| exp, err := otlptracegrpc.New(ctx, | ||
| otlptracegrpc.WithEndpoint("telemetry.googleapis.com:443"), | ||
| otlptracegrpc.WithDialOption(grpc.WithPerRPCCredentials(grpcCreds)), | ||
| otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")), | ||
| otlptracegrpc.WithHeaders(map[string]string{"x-goog-user-project": projectID}), | ||
| ) | ||
| if err != nil { | ||
| t.Fatalf("failed to create OTLP exporter: %v", err) | ||
| } | ||
|
|
||
| res, err := resource.New(ctx, | ||
| resource.WithDetectors(gcp.NewDetector()), | ||
| resource.WithTelemetrySDK(), | ||
| resource.WithAttributes( | ||
| semconv.ServiceNameKey.String("test-app"), | ||
| ), | ||
| ) | ||
| if err != nil { | ||
| t.Fatalf("failed to create resource: %v", err) | ||
| } | ||
|
|
||
| tp := sdktrace.NewTracerProvider( | ||
| sdktrace.WithBatcher(exp), | ||
| sdktrace.WithResource(res), | ||
| ) | ||
| oldTP := otel.GetTracerProvider() | ||
| t.Cleanup(func() { otel.SetTracerProvider(oldTP) }) | ||
| otel.SetTracerProvider(tp) | ||
| t.Cleanup(func() { | ||
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
| defer cancel() | ||
| tp.Shutdown(ctx) | ||
| }) | ||
|
|
||
| return projectID | ||
| } | ||
|
|
||
| func verifyTraceExists(t *testing.T, ctx context.Context, traceClient *trace.Client, projectID string, traceID [16]byte) { | ||
| traceIDStr := hex.EncodeToString(traceID[:]) | ||
| t.Logf("Looking for trace %s in project %s", traceIDStr, projectID) | ||
|
|
||
| var found bool | ||
| for i := 0; i < 15; i++ { | ||
| req := &tracepb.GetTraceRequest{ | ||
| ProjectId: projectID, | ||
| TraceId: traceIDStr, | ||
| } | ||
| _, err := traceClient.GetTrace(ctx, req) | ||
| if err == nil { | ||
| found = true | ||
| break | ||
| } | ||
| t.Logf("Attempt %d: trace %s not found yet, retrying...", i+1, traceIDStr) | ||
| time.Sleep(2 * time.Second) | ||
| } | ||
|
|
||
| if !found { | ||
| t.Errorf("Trace %s was not found in Cloud Trace backend", traceIDStr) | ||
| } | ||
| } | ||
|
|
||
| func TestObservability_Tracing_CloudTrace_Integration(t *testing.T) { | ||
| projectID := setupCloudTrace(t) | ||
| ctx := context.Background() | ||
|
|
||
| transports := []string{"grpc", "rest"} | ||
| for _, transport := range transports { | ||
| t.Run(transport, func(t *testing.T) { | ||
| var clientOpts []option.ClientOption | ||
| if transport == "grpc" { | ||
| clientOpts = []option.ClientOption{ | ||
| option.WithEndpoint("127.0.0.1:7469"), | ||
| option.WithTokenSource(oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "dummy-token"})), | ||
| option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), | ||
| } | ||
| } else { | ||
| clientOpts = []option.ClientOption{ | ||
| option.WithEndpoint("http://127.0.0.1:7469"), | ||
| option.WithTokenSource(oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "dummy-token"})), | ||
| } | ||
| } | ||
|
|
||
| var seqClient *showcase.SequenceClient | ||
| var err error | ||
| if transport == "grpc" { | ||
| seqClient, err = showcase.NewSequenceClient(ctx, clientOpts...) | ||
| } else { | ||
| seqClient, err = showcase.NewSequenceRESTClient(ctx, clientOpts...) | ||
| } | ||
| if err != nil { | ||
| t.Fatalf("failed to create sequence client: %v", err) | ||
| } | ||
| t.Cleanup(func() { seqClient.Close() }) | ||
|
|
||
| var echoClient *showcase.EchoClient | ||
| if transport == "grpc" { | ||
| echoClient, err = showcase.NewEchoClient(ctx, clientOpts...) | ||
| } else { | ||
| echoClient, err = showcase.NewEchoRESTClient(ctx, clientOpts...) | ||
| } | ||
| if err != nil { | ||
| t.Fatalf("failed to create echo client: %v", err) | ||
| } | ||
| t.Cleanup(func() { echoClient.Close() }) | ||
|
|
||
| traceClient, err := trace.NewClient(ctx) | ||
| if err != nil { | ||
| t.Fatalf("failed to create trace client: %v", err) | ||
| } | ||
| t.Cleanup(func() { traceClient.Close() }) | ||
|
|
||
| t.Run("Success", func(t *testing.T) { | ||
| ctxSpan, span := otel.Tracer("test-tracer").Start(ctx, "APP-Success-"+transport) | ||
| if transport == "grpc" { | ||
| _ = runTracingSuccessScenario(ctxSpan, t, seqClient) | ||
| } else { | ||
| _ = runTracingSuccessScenarioREST(ctxSpan, t, seqClient) | ||
| } | ||
| span.End() | ||
| traceID := span.SpanContext().TraceID() | ||
| otel.GetTracerProvider().(*sdktrace.TracerProvider).ForceFlush(ctx) | ||
| verifyTraceExists(t, ctx, traceClient, projectID, traceID) | ||
| }) | ||
|
|
||
| t.Run("ServerFailure", func(t *testing.T) { | ||
| ctxSpan, span := otel.Tracer("test-tracer").Start(ctx, "APP-ServerFailure-"+transport) | ||
| if transport == "grpc" { | ||
| _ = runTracingServerFailureScenario(ctxSpan, t, seqClient) | ||
| } else { | ||
| _ = runTracingServerFailureScenarioREST(ctxSpan, t, seqClient) | ||
| } | ||
| span.End() | ||
| traceID := span.SpanContext().TraceID() | ||
| otel.GetTracerProvider().(*sdktrace.TracerProvider).ForceFlush(ctx) | ||
| verifyTraceExists(t, ctx, traceClient, projectID, traceID) | ||
| }) | ||
|
|
||
| t.Run("ClientFailure", func(t *testing.T) { | ||
| ctxSpan, span := otel.Tracer("test-tracer").Start(ctx, "APP-ClientFailure-"+transport) | ||
| if transport == "grpc" { | ||
| _ = runTracingClientFailureScenario(ctxSpan, t, seqClient) | ||
| } else { | ||
| _ = runTracingClientFailureScenarioREST(ctxSpan, t, seqClient) | ||
| } | ||
| span.End() | ||
| traceID := span.SpanContext().TraceID() | ||
| otel.GetTracerProvider().(*sdktrace.TracerProvider).ForceFlush(ctx) | ||
| verifyTraceExists(t, ctx, traceClient, projectID, traceID) | ||
| }) | ||
|
|
||
| t.Run("Retry", func(t *testing.T) { | ||
| ctxSpan, span := otel.Tracer("test-tracer").Start(ctx, "APP-Retry-"+transport) | ||
| if transport == "grpc" { | ||
| _ = runTracingRetryScenario(ctxSpan, t, seqClient) | ||
| } else { | ||
| _ = runTracingRetryScenarioREST(ctxSpan, t, seqClient) | ||
| } | ||
| span.End() | ||
| traceID := span.SpanContext().TraceID() | ||
| otel.GetTracerProvider().(*sdktrace.TracerProvider).ForceFlush(ctx) | ||
| verifyTraceExists(t, ctx, traceClient, projectID, traceID) | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.