Files
coder/coderd/prometheusmetrics/aggregator_internal_test.go
T
Ethan 645da33767 test: fix TestDescCacheTimestampUpdate flake (#20975)
## Problem

`TestDescCacheTimestampUpdate` was flaky on Windows CI because
`time.Now()` has ~15.6ms resolution, causing consecutive calls to return
identical timestamps.

## Solution

Inject `quartz.Clock` into `MetricsAggregator` using an options pattern,
making the test deterministic by using a mock clock with explicit time
advancement.

### Changes
- Add `clock quartz.Clock` field to `MetricsAggregator` struct
- Add `WithClock()` option for dependency injection
- Replace all `time.Now()` calls with `ma.clock.Now()`
- Update test to use mock clock with `mClock.Advance(time.Second)`

---

This PR was fully generated by [`mux`](https://github.com/coder/mux)
using Claude Opus 4.5, and reviewed by me.

Closes https://github.com/coder/internal/issues/1146
2025-12-02 10:53:36 +11:00

95 lines
2.6 KiB
Go

package prometheusmetrics
import (
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"cdr.dev/slog/sloggers/slogtest"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/agentmetrics"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
)
func TestDescCache_DescExpire(t *testing.T) {
const (
testWorkspaceName = "yogi-workspace"
testUsername = "yogi-bear"
testAgentName = "main-agent"
testTemplateName = "main-template"
)
testLabels := AgentMetricLabels{
Username: testUsername,
WorkspaceName: testWorkspaceName,
AgentName: testAgentName,
TemplateName: testTemplateName,
}
t.Parallel()
// given
registry := prometheus.NewRegistry()
ma, err := NewMetricsAggregator(slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}), registry, time.Millisecond, agentmetrics.LabelAll)
require.NoError(t, err)
given := []*agentproto.Stats_Metric{
{Name: "a_counter_one", Type: agentproto.Stats_Metric_COUNTER, Value: 1},
}
_, err = ma.asPrometheus(&annotatedMetric{
given[0],
testLabels.Username,
testLabels.WorkspaceName,
testLabels.AgentName,
testLabels.TemplateName,
// the rest doesn't matter for this test
time.Now(),
[]string{},
})
require.NoError(t, err)
require.Eventually(t, func() bool {
ma.cleanupDescCache()
return len(ma.descCache) == 0
}, testutil.WaitShort, testutil.IntervalFast)
}
// TestDescCacheTimestampUpdate ensures that the timestamp update in getOrCreateDesc
// updates the map entry because d is a copy, not a pointer.
func TestDescCacheTimestampUpdate(t *testing.T) {
t.Parallel()
mClock := quartz.NewMock(t)
registry := prometheus.NewRegistry()
ma, err := NewMetricsAggregator(slogtest.Make(t, nil), registry, time.Hour, nil, WithClock(mClock))
require.NoError(t, err)
baseLabelNames := []string{"label1", "label2"}
extraLabels := []*agentproto.Stats_Metric_Label{
{Name: "extra1", Value: "value1"},
}
desc1 := ma.getOrCreateDesc("test_metric", "help text", baseLabelNames, extraLabels)
require.NotNil(t, desc1)
key := cacheKeyForDesc("test_metric", baseLabelNames, extraLabels)
initialEntry := ma.descCache[key]
initialTime := initialEntry.lastUsed
// Advance the mock clock to ensure a different timestamp
mClock.Advance(time.Second)
desc2 := ma.getOrCreateDesc("test_metric", "help text", baseLabelNames, extraLabels)
require.NotNil(t, desc2)
updatedEntry := ma.descCache[key]
updatedTime := updatedEntry.lastUsed
require.NotEqual(t, initialTime, updatedTime,
"Timestamp was NOT updated in map when accessing a metric description that should be cached")
}