package metrics import ( "sync" "testing" "time" ) // TestChurnPerMinuteCountsLastFullMinute announces and withdraws prefixes on // an injected clock and expects each minute's whole count once that minute // has ended, and 0 for a minute with none. func TestChurnPerMinuteCountsLastFullMinute(t *testing.T) { t.Parallel() now := time.Date(2026, 10, 26, 12, 0, 30, 0, time.UTC) tracker := New() tracker.churnPerMinute.now = func() time.Time { return now } expectChurn := func(want int, why string) { t.Helper() got := tracker.GetChurnPerMinute() if got != want { t.Fatalf("at %s churn = %d, want %d: %s", now.Format(time.TimeOnly), got, want, why) } } const firstMinuteChurn = 2 tracker.RecordAnnouncement() tracker.RecordWithdrawal() expectChurn(0, "12:00 has not ended") now = time.Date(2026, 10, 26, 12, 1, 59, 0, time.UTC) tracker.RecordAnnouncement() expectChurn(firstMinuteChurn, "12:00 had one announcement and one withdrawal") now = time.Date(2026, 10, 26, 12, 2, 0, 0, time.UTC) expectChurn(1, "12:01 had one announcement") now = time.Date(2026, 10, 26, 12, 4, 0, 0, time.UTC) expectChurn(0, "12:03 had none") tracker.RecordWithdrawal() now = time.Date(2026, 10, 26, 12, 6, 0, 0, time.UTC) expectChurn(0, "12:05 had none, though 12:04 had one withdrawal") } // TestChurnPerMinuteCountsConcurrentRecords announces prefixes from several // goroutines at once and expects every one counted. func TestChurnPerMinuteCountsConcurrentRecords(t *testing.T) { t.Parallel() const ( goroutines = 8 recordsPerGoroutine = 1000 ) now := time.Date(2026, 10, 26, 12, 0, 0, 0, time.UTC) tracker := New() tracker.churnPerMinute.now = func() time.Time { return now } var wg sync.WaitGroup for range goroutines { wg.Go(func() { for range recordsPerGoroutine { tracker.RecordAnnouncement() } }) } wg.Wait() now = now.Add(time.Minute) want := goroutines * recordsPerGoroutine if got := tracker.GetChurnPerMinute(); got != want { t.Fatalf("churn = %d, want %d", got, want) } }