Files
gerbil/internal/observability/otel/backend_test.go

176 lines
5.1 KiB
Go

package otel_test
import (
"context"
"testing"
"time"
obsotel "github.com/fosrl/gerbil/internal/observability/otel"
)
const (
defaultGRPCEndpoint = "localhost:4317"
defaultServiceName = "gerbil-test"
)
func newInMemoryBackend(t *testing.T) *obsotel.Backend {
t.Helper()
// Use a very short export interval; an in-process collector (noop exporter)
// is used by pointing to a non-existent endpoint with insecure mode.
// The backend itself should initialise without error since connection is lazy.
b, err := obsotel.New(obsotel.Config{
Protocol: "grpc",
Endpoint: defaultGRPCEndpoint,
Insecure: true,
ExportInterval: 100 * time.Millisecond,
ServiceName: defaultServiceName,
ServiceVersion: "0.0.1",
})
if err != nil {
t.Fatalf("failed to create otel backend: %v", err)
}
return b
}
func TestOtelBackendHTTPHandlerIsNil(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
if b.HTTPHandler() != nil {
t.Error("OTel backend HTTPHandler should return nil")
}
}
func TestOtelBackendShutdown(t *testing.T) {
b := newInMemoryBackend(t)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := b.Shutdown(ctx); err != nil {
// Shutdown with unreachable collector may fail to flush; that's acceptable.
// What matters is that Shutdown does not panic.
t.Logf("Shutdown returned (expected with no collector): %v", err)
}
}
func TestOtelBackendCounter(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
c, err := b.NewCounter("gerbil_test_counter_total", "test counter", "result")
if err != nil {
t.Fatalf("NewCounter returned error: %v", err)
}
// Should not panic
c.Add(context.Background(), 1, map[string]string{"result": "ok"})
c.Add(context.Background(), 5, nil)
}
func TestOtelBackendUpDownCounter(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
u, err := b.NewUpDownCounter("gerbil_test_updown", "test updown", "state")
if err != nil {
t.Fatalf("NewUpDownCounter returned error: %v", err)
}
u.Add(context.Background(), 3, map[string]string{"state": "active"})
u.Add(context.Background(), -1, map[string]string{"state": "active"})
}
func TestOtelBackendInt64Gauge(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
g, err := b.NewInt64Gauge("gerbil_test_int_gauge", "test gauge")
if err != nil {
t.Fatalf("NewInt64Gauge returned error: %v", err)
}
g.Record(context.Background(), 42, nil)
}
func TestOtelBackendFloat64Gauge(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
g, err := b.NewFloat64Gauge("gerbil_test_float_gauge", "test float gauge")
if err != nil {
t.Fatalf("NewFloat64Gauge returned error: %v", err)
}
g.Record(context.Background(), 3.14, nil)
}
func TestOtelBackendHistogram(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
h, err := b.NewHistogram("gerbil_test_duration_seconds", "test histogram",
[]float64{0.1, 0.5, 1.0}, "method")
if err != nil {
t.Fatalf("NewHistogram returned error: %v", err)
}
h.Record(context.Background(), 0.3, map[string]string{"method": "GET"})
}
func TestOtelBackendHTTPProtocol(t *testing.T) {
b, err := obsotel.New(obsotel.Config{
Protocol: "http",
Endpoint: "localhost:4318",
Insecure: true,
ExportInterval: 100 * time.Millisecond,
ServiceName: defaultServiceName,
})
if err != nil {
t.Fatalf("failed to create otel http backend: %v", err)
}
defer b.Shutdown(context.Background()) //nolint:errcheck
if b.HTTPHandler() != nil {
t.Error("OTel HTTP backend should not expose a /metrics endpoint")
}
}
func TestOtelBackendInvalidProtocol(t *testing.T) {
_, err := obsotel.New(obsotel.Config{
Protocol: "tcp",
Endpoint: defaultGRPCEndpoint,
ExportInterval: 10 * time.Second,
})
if err == nil {
t.Error("expected error for invalid protocol")
}
}
func TestOtelBackendDeploymentEnvironment(t *testing.T) {
b, err := obsotel.New(obsotel.Config{
Protocol: "grpc",
Endpoint: defaultGRPCEndpoint,
Insecure: true,
ExportInterval: 100 * time.Millisecond,
ServiceName: defaultServiceName,
ServiceVersion: "1.2.3",
DeploymentEnvironment: "staging",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer b.Shutdown(context.Background()) //nolint:errcheck
}
func TestOtelBackendRejectsInvalidLabelNames(t *testing.T) {
b := newInMemoryBackend(t)
defer b.Shutdown(context.Background()) //nolint:errcheck
t.Run("duplicate labels", func(t *testing.T) {
_, err := b.NewCounter("gerbil_test_invalid_labels_total", "test counter", "result", "result")
if err == nil {
t.Fatal("expected error for duplicate label names")
}
})
t.Run("invalid label name", func(t *testing.T) {
_, err := b.NewHistogram("gerbil_test_invalid_histogram", "test histogram", []float64{0.1, 1.0}, "status-code")
if err == nil {
t.Fatal("expected error for invalid label name")
}
})
}