Ejemplo
go.opentelemetry.io/otel/trace v1.45.0: SpanFromContext returns non-nil no-op spans for uninstrumented contexts, SpanContextConfig requires explicit FlagsSampled to prevent silent child span suppression, WithNewRoot severs parent trace IDs while invalid links are dropped, and TraceState strictly rejects uppercase keys while enforcing head-promotion
Muestra verificada para golang go.opentelemetry.io/otel/trace v1.45.0: SpanFromContext returns non-nil no-op spans for uninstrumented contexts…
sha256:2293c213fb83eb5777e19f685b3c1c4ba11e5acaeeb6995eb093958dc0b307b5
Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido.
Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas.
MIT-0
Evidencia de ejecución
El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.
- Base de evidencia
- Contrato firmado aprobado
- Recibos de verificación
- 3
- Claves de firma que lo compilaron
- 2
Entorno declarado
go linux x64 go go gomod
Entornos de las ejecuciones de verificación
| Entorno | Contrato | Etapas | Ejecución |
|---|---|---|---|
| go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-16 |
| go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · golang@1 |
2026-08-18 |
| go 1.26 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | FAIL | compile:SKIPPED · contract:FAIL · load:SKIPPED · resolve:PASS CONTAINER_RUN · golang@1golang:1.26-alpine@sha256:28d89ee9cc0f… |
2026-09-07 |
Caso
HOW- Objetivo
- SpanFromContext returns non-nil no-op spans for uninstrumented contexts, SpanContextConfig requires explicit FlagsSampled to prevent silent child span suppression, WithNewRoot severs parent trace IDs while invalid links are dropped, and TraceState strictly rejects uppercase keys while enforcing head-promotion
- Paquetes
- Símbolos
-
- go.opentelemetry.io/otel/trace.SpanFromContext
- go.opentelemetry.io/otel/trace.SpanContextFromContext
- go.opentelemetry.io/otel/trace.ContextWithRemoteSpanContext
- go.opentelemetry.io/otel/trace.NewSpanContext
- go.opentelemetry.io/otel/trace.WithNewRoot
- go.opentelemetry.io/otel/trace.WithLinks
- go.opentelemetry.io/otel/trace.ParseTraceState
- go.opentelemetry.io/otel/trace.TraceState
- go.opentelemetry.io/otel/trace.TraceFlags
- Entorno
- go
- Creado
- 2026-08-16T07:51:53Z
Contrato
- assert SpanFromContext returns non-nil noopSpan with invalid SpanContext on uninstrumented context
- assert ContextWithRemoteSpanContext wraps remote SpanContext in a non-recording span while tracer starts recording child
- assert valid SpanContext with TraceFlags 0 suppresses child recording whereas invalid TraceID falls back to sampled root
- assert WithNewRoot creates detached trace and WithLinks drops invalid SpanContext links
- assert ParseTraceState and Insert reject uppercase keys while Insert promotes updated keys to head and evicts tail at 32 members
- assert SpanContext equality is determined by Equal method across traceID spanID flags traceState and remote status
Archivos
- NOTES.md
- csx.json
- go.mod
- go.sum
- trace_config_test.go
Código fuente
# CSX Sample Notes: OpenTelemetry Go Trace Lifecycle, Configuration & Context Traps
## Search Known Solution Result
`search_known_solution` returned existing OpenTelemetry Go samples:
- `sha256:6dc41c32b8472be7a4589a2a43c284208b012f6e51c2e4bc20a8fb60d0ec1c33` (*"Default global TextMapPropagator silently no-ops cross-boundary trace context injection, while Extract yields remote non-recording span contexts without activating spans and malformed baggage is silently dropped"*)
- `sha256:1bf17bde93ae26bd833bf9e707662cf033b05798d2ab0c1289d2d691da1b1942` (*"RecordError records an exception event leaving span status as Unset, while SetStatus Ok irrevocably locks the span status against subsequent Error updates"*)
- `sha256:22889097515d91070760080551a1b9e99943837dd4e491fb6ab40c14b0256923` (*"Raw SDK samplers passed to WithSampler strip the ParentBased decorator, dropping incoming remote sampled spans or sampling unsampled fragments unless explicitly wrapped in ParentBased"*)
**Why this sample is distinct**:
This sample proves core `go.opentelemetry.io/otel/trace` API lifecycle and configuration behaviors:
1. `trace.SpanFromContext(ctx)` never returns `nil` on uninstrumented contexts, returning a non-nil `noopSpan` with invalid `SpanContext` (`!sc.IsValid()`) where standard `if span != nil` checks produce false positives.
2. In `trace.SpanContextConfig`, omitting `trace.FlagsSampled` creates a valid `SpanContext` whose child spans are completely suppressed from recording by SDK samplers, whereas providing an invalid all-zero `TraceID` bypasses parentage and triggers a newly sampled root span.
3. `trace.WithNewRoot()` creates a new trace tree ignoring parent `TraceID`, and `trace.WithLinks()` silently drops links containing invalid `SpanContext` instances without error.
4. `trace.TraceState` enforces strict lowercase ASCII W3C keys (rejecting uppercase keys with `errInvalidKey`), rejects duplicate keys during parse with `errDuplicate`, promotes updated keys to the head (index 0), and evicts the tail member when the 32-member capacity limit is reached.
## What a Model Would Have Written Instead
A model would write `if span := trace.SpanFromContext(ctx); span != nil` to check for active tracing, assume `SpanContextConfig` defaults to sampled or that `TraceFlags(0)` triggers sampling like uninitialized contexts, assume `WithLinks` records all provided link structs, and treat `TraceState` as a case-insensitive key-value map with last-value-wins duplicate parsing and preserved key insertion order.
## How the Wrong Version Fails
It fails silently with a green build: uninstrumented code paths report fake active spans via passing nil checks, distributed child spans are silently discarded downstream due to missing `FlagsSampled` on manually constructed contexts, broken trace lineage goes unnoticed when invalid links are dropped silently, and invalid `TraceState` keys cause silent header drops during context propagation.
{"case":{"caseId":"case:sha256:564eac0ff50aebd4473a4e2c17b7ccc27b6d867e4c08e8d4de8ced08073c629d","contract":["assert SpanFromContext returns non-nil noopSpan with invalid SpanContext on uninstrumented context","assert ContextWithRemoteSpanContext wraps remote SpanContext in a non-recording span while tracer starts recording child","assert valid SpanContext with TraceFlags 0 suppresses child recording whereas invalid TraceID falls back to sampled root","assert WithNewRoot creates detached trace and WithLinks drops invalid SpanContext links","assert ParseTraceState and Insert reject uppercase keys while Insert promotes updated keys to head and evicts tail at 32 members","assert SpanContext equality is determined by Equal method across traceID spanID flags traceState and remote status"],"goal":"SpanFromContext returns non-nil no-op spans for uninstrumented contexts, SpanContextConfig requires explicit FlagsSampled to prevent silent child span suppression, WithNewRoot severs parent trace IDs while invalid links are dropped, and TraceState strictly rejects uppercase keys while enforcing head-promotion","kind":"HOW","packages":["pkg:golang/go.opentelemetry.io/otel/trace@v1.45.0","pkg:golang/go.opentelemetry.io/otel/sdk@v1.45.0","pkg:golang/go.opentelemetry.io/otel@v1.45.0"],"schemaVersion":1,"symbols":["go.opentelemetry.io/otel/trace.SpanFromContext","go.opentelemetry.io/otel/trace.SpanContextFromContext","go.opentelemetry.io/otel/trace.ContextWithRemoteSpanContext","go.opentelemetry.io/otel/trace.NewSpanContext","go.opentelemetry.io/otel/trace.WithNewRoot","go.opentelemetry.io/otel/trace.WithLinks","go.opentelemetry.io/otel/trace.ParseTraceState","go.opentelemetry.io/otel/trace.TraceState","go.opentelemetry.io/otel/trace.TraceFlags"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"gomod","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/go.opentelemetry.io/otel/trace@v1.45.0","pkg:golang/go.opentelemetry.io/otel/sdk@v1.45.0","pkg:golang/go.opentelemetry.io/otel@v1.45.0"],"schemaVersion":1,"symbols":["go.opentelemetry.io/otel/trace.SpanFromContext","go.opentelemetry.io/otel/trace.SpanContextFromContext","go.opentelemetry.io/otel/trace.ContextWithRemoteSpanContext","go.opentelemetry.io/otel/trace.NewSpanContext","go.opentelemetry.io/otel/trace.WithNewRoot","go.opentelemetry.io/otel/trace.WithLinks","go.opentelemetry.io/otel/trace.ParseTraceState","go.opentelemetry.io/otel/trace.TraceState","go.opentelemetry.io/otel/trace.TraceFlags"],"verifierAdapter":"golang@1"}
module sample
go 1.26.5
require (
go.opentelemetry.io/otel/sdk v1.45.0
go.opentelemetry.io/otel/trace v1.45.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.45.0 // indirect
go.opentelemetry.io/otel/metric v1.45.0 // indirect
golang.org/x/sys v0.47.0 // indirect
)
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
package sample_test
import (
"context"
"fmt"
"strings"
"testing"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
)
func TestSpanFromContext_UninstrumentedReturnsNonNilNoop(t *testing.T) {
ctx := context.Background()
// TRAP: SpanFromContext NEVER returns nil even on an empty/uninstrumented context.
// Naive check `if span != nil` evaluates to true, misleading callers into believing a span is active.
span := trace.SpanFromContext(ctx)
if span == nil {
t.Fatal("expected non-nil Span from uninstrumented context, got nil")
}
// Must verify validity or recording status instead of nil check.
if span.IsRecording() {
t.Fatalf("expected uninstrumented span IsRecording() to be false, got true")
}
sc := span.SpanContext()
if sc.IsValid() {
t.Fatalf("expected uninstrumented SpanContext IsValid() to be false, got true")
}
if sc.HasTraceID() || sc.HasSpanID() {
t.Fatalf("expected uninstrumented SpanContext to have no TraceID or SpanID, got %s / %s", sc.TraceID(), sc.SpanID())
}
// SpanContextFromContext directly extracts the empty SpanContext without error.
directSC := trace.SpanContextFromContext(ctx)
if directSC.IsValid() {
t.Fatalf("expected direct SpanContextFromContext IsValid() to be false, got true")
}
// Mutating calls on noopSpan silently no-op without panicking.
span.SetAttributes()
span.AddEvent("noop_event")
span.End()
}
func TestContextWithRemoteSpanContext_WrapsInNonRecordingSpan(t *testing.T) {
tid, err := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
if err != nil {
t.Fatalf("unexpected error parsing trace id: %v", err)
}
sid, err := trace.SpanIDFromHex("00f067aa0ba902b7")
if err != nil {
t.Fatalf("unexpected error parsing span id: %v", err)
}
rsc := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: tid,
SpanID: sid,
TraceFlags: trace.FlagsSampled,
Remote: false, // Will be set to true by ContextWithRemoteSpanContext
})
ctx := trace.ContextWithRemoteSpanContext(context.Background(), rsc)
extractedSpan := trace.SpanFromContext(ctx)
// Extracted remote span is a nonRecordingSpan wrapper.
if !extractedSpan.SpanContext().IsRemote() {
t.Fatalf("expected remote span context to have IsRemote() true, got false")
}
if extractedSpan.IsRecording() {
t.Fatalf("expected extracted remote span to be non-recording, got recording")
}
// SDK Tracer starting a child from this remote context creates a local recording span inheriting TraceID.
sr := tracetest.NewSpanRecorder()
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
tracer := tp.Tracer("sample/tracer")
childCtx, childSpan := tracer.Start(ctx, "child_operation")
childSC := childSpan.SpanContext()
if childSC.TraceID() != tid {
t.Fatalf("expected child span to inherit remote TraceID %s, got %s", tid, childSC.TraceID())
}
if childSC.SpanID() == sid {
t.Fatalf("expected child span to allocate a new local SpanID, got same as remote parent %s", sid)
}
if childSC.IsRemote() {
t.Fatalf("expected child span IsRemote() to be false, got true")
}
if !childSpan.IsRecording() {
t.Fatalf("expected child span to be recording, got non-recording")
}
childSpan.End()
_ = childCtx
}
func TestTraceFlags_UnsampledSuppressesRecordingWhereasInvalidTraceIDTriggersNewRoot(t *testing.T) {
tid, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
sid, _ := trace.SpanIDFromHex("00f067aa0ba902b7")
sr := tracetest.NewSpanRecorder()
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
tracer := tp.Tracer("sample/flags")
// CASE A: Valid TraceID + SpanID, but TraceFlags is 0 (Unsampled).
// Under default ParentBased sampler, child span inherits unsampled decision and is NOT recorded.
unsampledRSC := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: tid,
SpanID: sid,
TraceFlags: 0, // Unsampled
Remote: true,
})
unsampledCtx := trace.ContextWithRemoteSpanContext(context.Background(), unsampledRSC)
_, childSpanA := tracer.Start(unsampledCtx, "child_unsampled")
if childSpanA.SpanContext().IsSampled() {
t.Fatalf("expected child span of unsampled parent to have IsSampled() false, got true")
}
if childSpanA.IsRecording() {
t.Fatalf("expected child span of unsampled parent to be non-recording, got recording")
}
childSpanA.End()
// Recorded spans count should still be 0 because non-recording spans are not captured.
if len(sr.Ended()) != 0 {
t.Fatalf("expected 0 recorded spans for unsampled child, got %d", len(sr.Ended()))
}
// CASE B: Invalid TraceID (all zeros).
// Because parent SpanContext is invalid (!sc.IsValid()), SDK ignores it and treats span as a new root.
// The default root sampler samples it, resulting in a RECORDING span!
invalidRSC := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID{},
SpanID: sid,
TraceFlags: 0,
Remote: true,
})
if invalidRSC.IsValid() {
t.Fatalf("expected invalid SpanContext to return IsValid() false")
}
invalidCtx := trace.ContextWithRemoteSpanContext(context.Background(), invalidRSC)
_, childSpanB := tracer.Start(invalidCtx, "child_from_invalid_parent")
if !childSpanB.SpanContext().IsSampled() {
t.Fatalf("expected child span of invalid parent to be sampled as a new root, got unsampled")
}
if !childSpanB.IsRecording() {
t.Fatalf("expected child span of invalid parent to be recording, got non-recording")
}
if childSpanB.SpanContext().TraceID() == tid {
t.Fatalf("expected child span to generate fresh TraceID, got %s", childSpanB.SpanContext().TraceID())
}
childSpanB.End()
if len(sr.Ended()) != 1 {
t.Fatalf("expected 1 recorded span for fallback root, got %d", len(sr.Ended()))
}
}
func TestWithNewRoot_DetachesLineageAndDropsInvalidLinks(t *testing.T) {
sr := tracetest.NewSpanRecorder()
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))
tracer := tp.Tracer("sample/roots")
parentCtx, parentSpan := tracer.Start(context.Background(), "parent_span")
parentTID := parentSpan.SpanContext().TraceID()
parentSID := parentSpan.SpanContext().SpanID()
// WithNewRoot explicitly ignores parent context's TraceID and starts a new trace.
// Adding links: invalid SpanContext link is silently dropped by SDK, while valid parent link is preserved.
invalidLink := trace.Link{SpanContext: trace.SpanContext{}}
validLink := trace.Link{SpanContext: parentSpan.SpanContext()}
_, newRootSpan := tracer.Start(
parentCtx,
"detached_root",
trace.WithNewRoot(),
trace.WithLinks(invalidLink, validLink),
)
newRootTID := newRootSpan.SpanContext().TraceID()
if newRootTID == parentTID {
t.Fatalf("expected WithNewRoot to generate a new TraceID, got parent TraceID %s", parentTID)
}
newRootSpan.End()
parentSpan.End()
ended := sr.Ended()
if len(ended) != 2 {
t.Fatalf("expected 2 ended spans, got %d", len(ended))
}
// Find the ended root span in recorder.
var recordedRoot sdktrace.ReadOnlySpan
for _, s := range ended {
if s.Name() == "detached_root" {
recordedRoot = s
break
}
}
if recordedRoot == nil {
t.Fatalf("could not find detached_root span in ended spans")
}
// Invalid link was dropped; only the 1 valid link to parent is recorded.
links := recordedRoot.Links()
if len(links) != 1 {
t.Fatalf("expected exactly 1 recorded link (invalid link dropped), got %d", len(links))
}
if links[0].SpanContext.TraceID() != parentTID || links[0].SpanContext.SpanID() != parentSID {
t.Fatalf("expected link to point to parent (%s / %s), got (%s / %s)",
parentTID, parentSID, links[0].SpanContext.TraceID(), links[0].SpanContext.SpanID())
}
}
func TestTraceState_StrictW3CValidationAndHeadPromotion(t *testing.T) {
// 1. Uppercase key rejection: W3C TraceState keys must be lowercase.
_, err := trace.ParseTraceState("Vendor=alpha")
if err == nil {
t.Fatalf("expected ParseTraceState to reject uppercase key, got nil error")
}
_, err = trace.TraceState{}.Insert("Vendor", "alpha")
if err == nil {
t.Fatalf("expected TraceState.Insert to reject uppercase key, got nil error")
}
// 2. Duplicate key rejection in ParseTraceState.
_, err = trace.ParseTraceState("vendor1=1,vendor1=2")
if err == nil {
t.Fatalf("expected ParseTraceState to reject duplicate keys, got nil error")
}
// 3. Head promotion: inserting an existing key updates value and moves key to index 0 (HEAD).
ts, err := trace.ParseTraceState("v1=first,v2=second")
if err != nil {
t.Fatalf("unexpected error parsing tracestate: %v", err)
}
tsUpdated, err := ts.Insert("v2", "updated_second")
if err != nil {
t.Fatalf("unexpected error inserting into tracestate: %v", err)
}
expectedStr := "v2=updated_second,v1=first"
if tsUpdated.String() != expectedStr {
t.Fatalf("expected updated key promoted to head (%q), got %q", expectedStr, tsUpdated.String())
}
// Immutability: original ts is unchanged.
if ts.String() != "v1=first,v2=second" {
t.Fatalf("expected original TraceState to be immutable, got %q", ts.String())
}
// 4. Max 32 members capacity: 33rd member evicts the rightmost (tail) member.
var members []string
for i := 1; i <= 32; i++ {
members = append(members, fmt.Sprintf("k%02d=val", i))
}
ts32, err := trace.ParseTraceState(strings.Join(members, ","))
if err != nil {
t.Fatalf("unexpected error parsing 32 members: %v", err)
}
if ts32.Len() != 32 {
t.Fatalf("expected 32 members, got %d", ts32.Len())
}
ts33, err := ts32.Insert("k99", "new_head")
if err != nil {
t.Fatalf("unexpected error inserting 33rd member: %v", err)
}
if ts33.Len() != 32 {
t.Fatalf("expected TraceState to cap at 32 members, got %d", ts33.Len())
}
if ts33.Get("k99") != "new_head" {
t.Fatalf("expected k99 to be inserted at head with value 'new_head', got %q", ts33.Get("k99"))
}
// The oldest tail member (k32) was evicted.
if ts33.Get("k32") != "" {
t.Fatalf("expected tail member k32 to be evicted, but got %q", ts33.Get("k32"))
}
// k01 is still present.
if ts33.Get("k01") != "val" {
t.Fatalf("expected k01 to remain, got %q", ts33.Get("k01"))
}
}
func TestSpanContext_EqualityRequiresEqualMethod(t *testing.T) {
tid, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
sid, _ := trace.SpanIDFromHex("00f067aa0ba902b7")
sc1 := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: tid,
SpanID: sid,
TraceFlags: trace.FlagsSampled,
})
sc2 := trace.NewSpanContext(trace.SpanContextConfig{
TraceID: tid,
SpanID: sid,
TraceFlags: trace.FlagsSampled,
})
if !sc1.Equal(sc2) {
t.Fatalf("expected sc1.Equal(sc2) to be true")
}
// Remote flag changes equality.
if sc1.WithRemote(true).Equal(sc1) {
t.Fatalf("expected remote SpanContext to not equal local SpanContext")
}
}