test(telemetry): cover context helpers and tracer initialization

Add tests for every context helper and for Init across disabled, http, grpc
and invalid protocol configurations, raising coverage from 21.6% to 98.6%.

This also surfaced a real bug: Init failed when tracing was enabled because
resource.Default() (schema 1.43.0) conflicted with semconv.SchemaURL
(1.26.0). Build the service name resource schemaless so Init succeeds.
This commit is contained in:
Tosone committed 2026-09-27 12:37:00 +08:00
1 parent fb7f20e8c4
commit 9a7e6c8655
3 files changed
+218 -2

No files matched your search

+119 -1
View File
@@ -15,18 +15,136 @@
package telemetry
import (
"context"
"encoding/json/v2"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestInjectCarrierClearsStalePropagationHeaders(t *testing.T) {
// setPropagator installs the composite propagator the package expects and
// restores the previous one when the test finishes.
func setPropagator(t *testing.T) {
t.Helper()
prev := otel.GetTextMapPropagator()
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
t.Cleanup(func() { otel.SetTextMapPropagator(prev) })
}
// recordingContext returns a context carrying a sampled span and the trace id
// of that span.
func recordingContext(t *testing.T) (context.Context, string) {
t.Helper()
tp := sdktrace.NewTracerProvider()
t.Cleanup(func() { _ = tp.Shutdown(context.Background()) })
ctx, span := tp.Tracer("telemetry-test").Start(context.Background(), "op")
t.Cleanup(func() { span.End() })
return ctx, span.SpanContext().TraceID().String()
}
func TestTraceIDFromContext(t *testing.T) {
require.Empty(t, TraceIDFromContext(context.Background()))
ctx, want := recordingContext(t)
require.Equal(t, want, TraceIDFromContext(ctx))
}
func TestCarrierFromContext(t *testing.T) {
setPropagator(t)
require.Nil(t, CarrierFromContext(context.Background()))
ctx, want := recordingContext(t)
carrier := CarrierFromContext(ctx)
require.NotNil(t, carrier)
require.Contains(t, carrier, "traceparent")
require.Equal(t, want, TraceIDFromCarrier(carrier))
}
func TestCarrierFromContextWithoutPropagator(t *testing.T) {
prev := otel.GetTextMapPropagator()
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator())
t.Cleanup(func() { otel.SetTextMapPropagator(prev) })
ctx, _ := recordingContext(t)
require.Nil(t, CarrierFromContext(ctx))
}
func TestContextWithCarrier(t *testing.T) {
setPropagator(t)
base := context.Background()
require.Equal(t, base, ContextWithCarrier(base, nil))
require.Equal(t, base, ContextWithCarrier(base, map[string]string{}))
ctx, want := recordingContext(t)
got := TraceIDFromContext(ContextWithCarrier(base, CarrierFromContext(ctx)))
require.Equal(t, want, got)
}
func TestTraceIDFromCarrier(t *testing.T) {
setPropagator(t)
require.Empty(t, TraceIDFromCarrier(nil))
ctx, want := recordingContext(t)
require.Equal(t, want, TraceIDFromCarrier(CarrierFromContext(ctx)))
}
func TestTraceIDFromCarrierBytes(t *testing.T) {
setPropagator(t)
require.Empty(t, TraceIDFromCarrierBytes([]byte("not-json")))
ctx, want := recordingContext(t)
data, err := json.Marshal(CarrierFromContext(ctx))
require.NoError(t, err)
require.Equal(t, want, TraceIDFromCarrierBytes(data))
}
func TestClearCarrier(t *testing.T) {
setPropagator(t)
empty := map[string]string{}
require.Equal(t, empty, ClearCarrier(empty))
carrier := map[string]string{
"Traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"BAGGAGE": "hello=world",
"tracestate": "vendor=value",
"user-agent": "sigma",
}
require.Equal(t, map[string]string{"user-agent": "sigma"}, ClearCarrier(carrier))
}
func TestInjectCarrier(t *testing.T) {
setPropagator(t)
// Without an active span, stale propagation headers are dropped.
got := InjectCarrier(context.Background(), map[string]string{
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"user-agent": "sigma",
})
require.Equal(t, map[string]string{"user-agent": "sigma"}, got)
// Without an active span and a nil carrier, nothing is created.
require.Nil(t, InjectCarrier(context.Background(), nil))
// With an active span and a nil carrier, a new carrier is created.
ctx, want := recordingContext(t)
got = InjectCarrier(ctx, nil)
require.Equal(t, want, TraceIDFromCarrier(got))
}
func TestInjectCarrierClearsStalePropagationHeaders(t *testing.T) {
setPropagator(t)
carrier := map[string]string{
"Traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
+1 -1
View File
@@ -68,7 +68,7 @@ func Init(cfg *config.Configuration) (func(context.Context) error, error) {
// 2. Resource (service.name, etc.)
res, err := resource.Merge(
resource.Default(),
resource.NewWithAttributes(semconv.SchemaURL,
resource.NewSchemaless(
semconv.ServiceName(serviceName),
),
)
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2026 sigma
//
// 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 telemetry
import (
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"github.com/go-sigma/sigma/pkg/config"
)
func TestInit(t *testing.T) {
prevTP := otel.GetTracerProvider()
prevPropagator := otel.GetTextMapPropagator()
t.Cleanup(func() {
otel.SetTracerProvider(prevTP)
otel.SetTextMapPropagator(prevPropagator)
})
tests := []struct {
name string
cfg config.ConfigurationTrace
wantErr bool
wantNil bool
}{
{
name: "disabled",
cfg: config.ConfigurationTrace{Enabled: false},
wantNil: true,
},
{
name: "http",
cfg: config.ConfigurationTrace{Enabled: true, Protocol: "http", Endpoint: "localhost:4318"},
},
{
name: "http default protocol",
cfg: config.ConfigurationTrace{Enabled: true, Endpoint: "localhost:4318"},
},
{
name: "http insecure with custom service name and ratio",
cfg: config.ConfigurationTrace{
Enabled: true,
Protocol: "http",
Endpoint: "localhost:4318",
Insecure: true,
SampleRatio: 0.5,
ServiceName: "custom",
},
},
{
name: "grpc",
cfg: config.ConfigurationTrace{Enabled: true, Protocol: "grpc", Endpoint: "localhost:4317", Insecure: true},
},
{
name: "grpc exporter error",
cfg: config.ConfigurationTrace{Enabled: true, Protocol: "grpc", Endpoint: "\x00", Insecure: true},
wantErr: true,
wantNil: true,
},
{
name: "unknown protocol",
cfg: config.ConfigurationTrace{Enabled: true, Protocol: "bogus", Endpoint: "localhost:4317"},
wantErr: true,
wantNil: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
shutdown, err := Init(&config.Configuration{Trace: tt.cfg})
if tt.wantErr {
require.Error(t, err)
require.Nil(t, shutdown)
return
}
require.NoError(t, err)
if tt.wantNil {
require.Nil(t, shutdown)
return
}
require.NotNil(t, shutdown)
require.NoError(t, shutdown(t.Context()))
})
}
}