package metrics import ( "strings" "testing" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" ) // register wires a fresh registry — the reason Register exists instead of // init()-time self-registration. func register(t *testing.T, m *Metrics, phases map[string]int, active int) *prometheus.Registry { t.Helper() reg := prometheus.NewRegistry() err := m.Register(reg, func() map[string]int { return phases }, func() int { return active }, ) if err != nil { t.Fatalf("Register: %v", err) } return reg } func TestRegister_scrapeTimeCollectors(t *testing.T) { t.Parallel() m := New() reg := register(t, m, map[string]int{"Ready": 3, "Provisioning": 1}, 7) expected := ` # HELP proxy_operator_leases_active Currently active leases. # TYPE proxy_operator_leases_active gauge proxy_operator_leases_active 7 # HELP proxy_operator_proxies Proxy objects by phase. # TYPE proxy_operator_proxies gauge proxy_operator_proxies{phase="Provisioning"} 1 proxy_operator_proxies{phase="Ready"} 3 ` if err := testutil.GatherAndCompare(reg, strings.NewReader(expected), "proxy_operator_proxies", "proxy_operator_leases_active"); err != nil { t.Error(err) } } func TestObserveProbe_andForget(t *testing.T) { t.Parallel() m := New() reg := register(t, m, nil, 0) m.ObserveProbe("default/p1", 30*time.Millisecond, true) m.ObserveProbe("default/p1", 40*time.Millisecond, false) m.ObserveProbe("default/p2", 10*time.Millisecond, true) if got := testutil.CollectAndCount(m.healthcheckDuration); got != 2 { t.Errorf("duration series = %d, want 2 (one per proxy)", got) } if got := testutil.ToFloat64(m.healthcheckFailures.WithLabelValues("default/p1")); got != 1 { t.Errorf("p1 failures = %v, want 1 (only the failed probe)", got) } m.ForgetProxy("default/p1") if got := testutil.CollectAndCount(m.healthcheckDuration); got != 1 { t.Errorf("duration series after ForgetProxy = %d, want 1 — series must not leak", got) } if got := testutil.CollectAndCount(m.healthcheckFailures); got != 0 { t.Errorf("failure series after ForgetProxy = %d, want 0", got) } _ = reg } func TestLeaseRequest(t *testing.T) { t.Parallel() m := New() register(t, m, nil, 0) m.LeaseRequest("granted") m.LeaseRequest("granted") m.LeaseRequest("no_match") if got := testutil.ToFloat64(m.leaseRequests.WithLabelValues("granted")); got != 2 { t.Errorf("granted = %v, want 2", got) } if got := testutil.ToFloat64(m.leaseRequests.WithLabelValues("no_match")); got != 1 { t.Errorf("no_match = %v, want 1", got) } } func TestProviderRequest(t *testing.T) { t.Parallel() m := New() register(t, m, nil, 0) m.ProviderRequest("gcp-eu", "create", "ok") m.ProviderRequest("gcp-eu", "create", "quota_exceeded") if got := testutil.ToFloat64(m.providerRequests.WithLabelValues("gcp-eu", "create", "ok")); got != 1 { t.Errorf("ok = %v, want 1", got) } if got := testutil.ToFloat64(m.providerRequests.WithLabelValues("gcp-eu", "create", "quota_exceeded")); got != 1 { t.Errorf("quota_exceeded = %v, want 1", got) } } func TestRegister_freshRegistryPerTest(t *testing.T) { t.Parallel() // Registering the same metric set on two registries must both succeed — // the property init()-style global registration would break. m1, m2 := New(), New() register(t, m1, nil, 0) register(t, m2, nil, 0) } func TestRegisterProviderTargets_scrape(t *testing.T) { t.Parallel() checked := time.Unix(1757602800, 0) targets := []ProviderTarget{ {Provider: "kvm-home", Target: "nas1", Up: true, Duration: 84 * time.Millisecond, CheckedAt: checked}, {Provider: "kvm-home", Target: "nas2", Up: false, Duration: 15 * time.Second, CheckedAt: checked}, } reg := prometheus.NewRegistry() if err := RegisterProviderTargets(reg, func() []ProviderTarget { return targets }); err != nil { t.Fatalf("RegisterProviderTargets: %v", err) } expected := ` # HELP proxy_operator_provider_check_duration_seconds Duration of the target's latest provider health check. # TYPE proxy_operator_provider_check_duration_seconds gauge proxy_operator_provider_check_duration_seconds{provider="kvm-home",target="nas1"} 0.084 proxy_operator_provider_check_duration_seconds{provider="kvm-home",target="nas2"} 15 # HELP proxy_operator_provider_check_timestamp_seconds Unix time of the target's latest provider health check. # TYPE proxy_operator_provider_check_timestamp_seconds gauge proxy_operator_provider_check_timestamp_seconds{provider="kvm-home",target="nas1"} 1.7576028e+09 proxy_operator_provider_check_timestamp_seconds{provider="kvm-home",target="nas2"} 1.7576028e+09 # HELP proxy_operator_provider_target_up Whether the target passed its latest provider health check (1) or not (0). # TYPE proxy_operator_provider_target_up gauge proxy_operator_provider_target_up{provider="kvm-home",target="nas1"} 1 proxy_operator_provider_target_up{provider="kvm-home",target="nas2"} 0 ` if err := testutil.GatherAndCompare(reg, strings.NewReader(expected)); err != nil { t.Error(err) } } func TestRegisterProviderTargets_emptySnapshotHasNoSeries(t *testing.T) { t.Parallel() reg := prometheus.NewRegistry() if err := RegisterProviderTargets(reg, func() []ProviderTarget { return nil }); err != nil { t.Fatalf("RegisterProviderTargets: %v", err) } if n, err := testutil.GatherAndCount(reg); err != nil || n != 0 { t.Errorf("series before the first check = %d (%v), want 0", n, err) } }