blob: 0b816a99727ce676667963cb094a5fdb8cd0b88e [file] [edit]
// 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
//
// 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 datastore
import (
"context"
"testing"
pb "cloud.google.com/go/datastore/apiv1/datastorepb"
"cloud.google.com/go/internal/testutil"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
func TestTransactionTracingContextPropagation(t *testing.T) {
ctx := context.Background()
te := testutil.NewOpenTelemetryTestExporter()
t.Cleanup(func() {
te.Unregister(ctx)
})
client, srv, cleanup := newMock(t)
defer cleanup()
mockTxnID := []byte("tid")
mockKey := NameKey("Kind", "name", nil)
mockEntity := &pb.Entity{
Key: keyToProto(mockKey),
}
srv.addRPC(&pb.BeginTransactionRequest{ProjectId: mockProjectID}, &pb.BeginTransactionResponse{Transaction: mockTxnID})
srv.addRPC(&pb.LookupRequest{
ProjectId: mockProjectID,
Keys: []*pb.Key{keyToProto(mockKey)},
ReadOptions: &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{Transaction: mockTxnID},
},
}, &pb.LookupResponse{Found: []*pb.EntityResult{{Entity: mockEntity}}})
srv.addRPC(&pb.LookupRequest{
ProjectId: mockProjectID,
Keys: []*pb.Key{keyToProto(mockKey)},
ReadOptions: &pb.ReadOptions{
ConsistencyType: &pb.ReadOptions_Transaction{Transaction: mockTxnID},
},
}, &pb.LookupResponse{Found: []*pb.EntityResult{{Entity: mockEntity}}})
srv.addRPC(&pb.CommitRequest{
ProjectId: mockProjectID,
TransactionSelector: &pb.CommitRequest_Transaction{Transaction: mockTxnID},
Mode: pb.CommitRequest_TRANSACTIONAL,
}, &pb.CommitResponse{})
tx, err := client.NewTransaction(ctx)
if err != nil {
t.Fatal(err)
}
var dst struct{}
if err := tx.Get(mockKey, &dst); err != nil {
t.Fatal(err)
}
if err := tx.Get(mockKey, &dst); err != nil {
t.Fatal(err)
}
if _, err := tx.Commit(); err != nil {
t.Fatal(err)
}
spans := te.Spans()
var newTxnSpan, beginTxnSpan tracetest.SpanStub
var getSpans []tracetest.SpanStub
var commitSpan tracetest.SpanStub
var hasNewTxn, hasBeginTxn, hasCommit bool
for _, s := range spans {
switch s.Name {
case "cloud.google.com/go/datastore.NewTransaction":
newTxnSpan = s
hasNewTxn = true
case "cloud.google.com/go/datastore.Transaction.BeginTransaction":
beginTxnSpan = s
hasBeginTxn = true
case "cloud.google.com/go/datastore.Transaction.Get":
getSpans = append(getSpans, s)
case "cloud.google.com/go/datastore.Transaction.Commit":
commitSpan = s
hasCommit = true
}
}
if !hasNewTxn {
t.Fatal("missing NewTransaction span")
}
newTxnSpanID := newTxnSpan.SpanContext.SpanID()
if !hasBeginTxn {
t.Fatal("missing BeginTransaction span")
}
if beginTxnSpan.Parent.SpanID() != newTxnSpanID {
t.Errorf("BeginTransaction span parent = %v, want %v", beginTxnSpan.Parent.SpanID(), newTxnSpanID)
}
if len(getSpans) != 2 {
t.Fatalf("got %d Get spans, want 2", len(getSpans))
}
for i, s := range getSpans {
if s.Parent.SpanID() != newTxnSpanID {
t.Errorf("Get span %d parent = %v, want %v (parent span: %v)", i, s.Parent.SpanID(), newTxnSpanID, s.Parent)
}
}
if !hasCommit {
t.Fatal("missing Commit span")
}
if commitSpan.Parent.SpanID() != newTxnSpanID {
t.Errorf("Commit span parent = %v, want %v", commitSpan.Parent.SpanID(), newTxnSpanID)
}
}
func TestIteratorTracingContextPropagation(t *testing.T) {
ctx := context.Background()
te := testutil.NewOpenTelemetryTestExporter()
t.Cleanup(func() {
te.Unregister(ctx)
})
client, srv, cleanup := newMock(t)
defer cleanup()
mockKind := "Kind"
srv.addRPC(&pb.RunQueryRequest{
ProjectId: mockProjectID,
QueryType: &pb.RunQueryRequest_Query{Query: &pb.Query{Kind: []*pb.KindExpression{{Name: mockKind}}}},
}, &pb.RunQueryResponse{})
q := NewQuery(mockKind)
it := client.Run(ctx, q)
_, _ = it.Cursor()
_, _ = it.Cursor()
spans := te.Spans()
var runSpan tracetest.SpanStub
var cursorSpans []tracetest.SpanStub
var hasRun bool
for _, s := range spans {
switch s.Name {
case "cloud.google.com/go/datastore.Query.Run":
runSpan = s
hasRun = true
case "cloud.google.com/go/datastore.Query.Cursor":
cursorSpans = append(cursorSpans, s)
}
}
if !hasRun {
t.Fatal("missing Query.Run span")
}
runSpanID := runSpan.SpanContext.SpanID()
if len(cursorSpans) != 2 {
t.Fatalf("got %d Cursor spans, want 2", len(cursorSpans))
}
for i, s := range cursorSpans {
if s.Parent.SpanID() != runSpanID {
t.Errorf("Cursor span %d parent = %v, want %v", i, s.Parent.SpanID(), runSpanID)
}
}
}