Files
galaxy-game/gamemaster/internal/telemetry/runtime_test.go
T
2026-05-03 07:59:03 +02:00

191 lines
5.4 KiB
Go

package telemetry
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
)
func TestProcessConfigValidate(t *testing.T) {
t.Parallel()
require.NoError(t, ProcessConfig{
TracesExporter: "none",
MetricsExporter: "none",
}.Validate())
require.NoError(t, ProcessConfig{
TracesExporter: "otlp",
MetricsExporter: "otlp",
TracesProtocol: "grpc",
MetricsProtocol: "http/protobuf",
}.Validate())
require.Error(t, ProcessConfig{
TracesExporter: "stdout",
MetricsExporter: "none",
}.Validate())
require.Error(t, ProcessConfig{
TracesExporter: "none",
MetricsExporter: "kafka",
}.Validate())
require.Error(t, ProcessConfig{
TracesExporter: "otlp",
MetricsExporter: "none",
TracesProtocol: "thrift",
}.Validate())
}
func TestNewWithProvidersBuildsRuntime(t *testing.T) {
t.Parallel()
reader := metric.NewManualReader()
meterProvider := metric.NewMeterProvider(metric.WithReader(reader))
runtime, err := NewWithProviders(meterProvider, nil)
require.NoError(t, err)
require.NotNil(t, runtime)
require.NotNil(t, runtime.MeterProvider())
require.NotNil(t, runtime.TracerProvider())
}
func TestRecordHelpersEmitInstruments(t *testing.T) {
t.Parallel()
reader := metric.NewManualReader()
meterProvider := metric.NewMeterProvider(metric.WithReader(reader))
runtime, err := NewWithProviders(meterProvider, nil)
require.NoError(t, err)
ctx := context.Background()
runtime.RecordInternalHTTPRequest(ctx, []attribute.KeyValue{
attribute.String("route", "/healthz"),
attribute.String("method", "GET"),
attribute.String("status_code", "200"),
}, 10*time.Millisecond)
runtime.RecordRegisterRuntimeOutcome(ctx, "success", "")
runtime.RecordTurnGenerationOutcome(ctx, "success", "", "scheduler")
runtime.RecordCommandExecuteOutcome(ctx, "success", "")
runtime.RecordOrderPutOutcome(ctx, "success", "")
runtime.RecordReportGetOutcome(ctx, "success", "")
runtime.RecordBanishOutcome(ctx, "success", "")
runtime.RecordHealthEventConsumed(ctx)
runtime.RecordLobbyEventPublished(ctx, "runtime_snapshot_update")
runtime.RecordNotificationPublishAttempt(ctx, "game.turn.ready", "ok")
runtime.RecordMembershipCacheResult(ctx, "hit")
runtime.RecordEngineCall(ctx, "init", 25*time.Millisecond)
var rm metricdata.ResourceMetrics
require.NoError(t, reader.Collect(ctx, &rm))
names := collectInstrumentNames(rm)
expected := []string{
"gamemaster.internal_http.requests",
"gamemaster.internal_http.duration",
"gamemaster.register_runtime.outcomes",
"gamemaster.turn_generation.outcomes",
"gamemaster.command_execute.outcomes",
"gamemaster.order_put.outcomes",
"gamemaster.report_get.outcomes",
"gamemaster.banish.outcomes",
"gamemaster.health_events.consumed",
"gamemaster.lobby_events.published",
"gamemaster.notification.publish_attempts",
"gamemaster.membership_cache.hits",
"gamemaster.engine_call.latency",
}
for _, name := range expected {
require.Contains(t, names, name, "expected instrument %s to be recorded", name)
}
}
func collectInstrumentNames(rm metricdata.ResourceMetrics) map[string]struct{} {
names := make(map[string]struct{})
for _, sm := range rm.ScopeMetrics {
for _, m := range sm.Metrics {
names[m.Name] = struct{}{}
}
}
return names
}
type stubRuntimeProbe struct {
counts map[string]int
err error
}
func (probe stubRuntimeProbe) CountByStatus(_ context.Context) (map[string]int, error) {
return probe.counts, probe.err
}
type stubSchedulerProbe struct {
due int
err error
}
func (probe stubSchedulerProbe) CountDue(_ context.Context) (int, error) {
return probe.due, probe.err
}
type stubVersionsProbe struct {
count int
err error
}
func (probe stubVersionsProbe) CountVersions(_ context.Context) (int, error) {
return probe.count, probe.err
}
func TestRegisterGaugesEmitsObservations(t *testing.T) {
t.Parallel()
reader := metric.NewManualReader()
meterProvider := metric.NewMeterProvider(metric.WithReader(reader))
runtime, err := NewWithProviders(meterProvider, nil)
require.NoError(t, err)
require.NoError(t, runtime.RegisterGauges(GaugeDependencies{
RuntimeRecordsByStatus: stubRuntimeProbe{counts: map[string]int{"running": 3}},
SchedulerDueGames: stubSchedulerProbe{due: 2},
EngineVersionsTotal: stubVersionsProbe{count: 5},
}))
var rm metricdata.ResourceMetrics
require.NoError(t, reader.Collect(context.Background(), &rm))
names := collectInstrumentNames(rm)
require.Contains(t, names, "gamemaster.runtime_records_by_status")
require.Contains(t, names, "gamemaster.scheduler.due_games")
require.Contains(t, names, "gamemaster.engine_versions_total")
}
func TestRegisterGaugesRejectsNilDependencies(t *testing.T) {
t.Parallel()
reader := metric.NewManualReader()
meterProvider := metric.NewMeterProvider(metric.WithReader(reader))
runtime, err := NewWithProviders(meterProvider, nil)
require.NoError(t, err)
require.Error(t, runtime.RegisterGauges(GaugeDependencies{
SchedulerDueGames: stubSchedulerProbe{},
EngineVersionsTotal: stubVersionsProbe{},
}))
require.Error(t, runtime.RegisterGauges(GaugeDependencies{
RuntimeRecordsByStatus: stubRuntimeProbe{},
EngineVersionsTotal: stubVersionsProbe{},
}))
require.Error(t, runtime.RegisterGauges(GaugeDependencies{
RuntimeRecordsByStatus: stubRuntimeProbe{},
SchedulerDueGames: stubSchedulerProbe{},
}))
}