Brad Fitzpatrick 265c76dbc5 all: unify some redundant testing.TB interface copies
I added yet another one in 6d117d64a256234 but that new one is at the
best place int he dependency graph and has the best name, so let's use
that one for everything possible.

types/lazy can't use it for circular dependency reasons, so unexport
that copy at least.

Updates #cleanup

Change-Id: I25db6b6a0d81dbb8e89a0a9080c7f15cbf7aa770
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-04-08 08:19:29 -07:00

90 lines
2.4 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package metrics
import (
"strings"
"tailscale.com/util/clientmetric"
"tailscale.com/util/set"
"tailscale.com/util/syspolicy/internal"
"tailscale.com/util/testenv"
)
// TestState represents a metric name and its expected value.
type TestState struct {
Name string // `$os` in the name will be replaced by the actual operating system name.
Value int64
}
// TestHandler facilitates testing of the code that uses metrics.
type TestHandler struct {
t testenv.TB
m map[string]int64
}
// NewTestHandler returns a new TestHandler.
func NewTestHandler(t testenv.TB) *TestHandler {
return &TestHandler{t, make(map[string]int64)}
}
// AddMetric increments the metric with the specified name and type by delta d.
func (h *TestHandler) AddMetric(name string, typ clientmetric.Type, d int64) {
h.t.Helper()
if typ == clientmetric.TypeCounter && d < 0 {
h.t.Fatalf("an attempt was made to decrement a counter metric %q", name)
}
if v, ok := h.m[name]; ok || d != 0 {
h.m[name] = v + d
}
}
// SetMetric sets the metric with the specified name and type to the value v.
func (h *TestHandler) SetMetric(name string, typ clientmetric.Type, v int64) {
h.t.Helper()
if typ == clientmetric.TypeCounter {
h.t.Fatalf("an attempt was made to set a counter metric %q", name)
}
if _, ok := h.m[name]; ok || v != 0 {
h.m[name] = v
}
}
// MustEqual fails the test if the actual metric state differs from the specified state.
func (h *TestHandler) MustEqual(metrics ...TestState) {
h.t.Helper()
h.MustContain(metrics...)
h.mustNoExtra(metrics...)
}
// MustContain fails the test if the specified metrics are not set or have
// different values than specified. It permits other metrics to be set in
// addition to the ones being tested.
func (h *TestHandler) MustContain(metrics ...TestState) {
h.t.Helper()
for _, m := range metrics {
name := strings.ReplaceAll(m.Name, "$os", internal.OS())
v, ok := h.m[name]
if !ok {
h.t.Errorf("%q: got (none), want %v", name, m.Value)
} else if v != m.Value {
h.t.Fatalf("%q: got %v, want %v", name, v, m.Value)
}
}
}
func (h *TestHandler) mustNoExtra(metrics ...TestState) {
h.t.Helper()
s := make(set.Set[string])
for i := range metrics {
s.Add(strings.ReplaceAll(metrics[i].Name, "$os", internal.OS()))
}
for n, v := range h.m {
if !s.Contains(n) {
h.t.Errorf("%q: got %v, want (none)", n, v)
}
}
}