mirror of
https://github.com/marcusDenslow/terminalShop.git
synced 2026-10-25 02:52:20 +02:00
* test(api): cover everyTick helper * test(api): make everyTick cancel test deterministic
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestEveryTick_CallsFnOnTick verifies the helper invokes fn repeatedly on its
|
|
// interval. A non-blocking send keeps the spawned goroutine from ever blocking
|
|
// on a full channel, and the 1s timeout per tick is generous vs the 1ms
|
|
// interval so the test is not timing-flaky.
|
|
func TestEveryTick_CallsFnOnTick(t *testing.T) {
|
|
ctx := t.Context()
|
|
|
|
fired := make(chan struct{}, 1)
|
|
everyTick(ctx, time.Millisecond, func(context.Context) {
|
|
select {
|
|
case fired <- struct{}{}:
|
|
default:
|
|
}
|
|
})
|
|
|
|
for i := range 3 {
|
|
select {
|
|
case <-fired:
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("tick %d did not fire within 1s", i+1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEveryTick_StopsOnContextCancel verifies cancelling ctx stops the loop.
|
|
// After the first tick we cancel inside the inter-tick gap, so everyTick's
|
|
// select sees only ctx.Done ready (ticker.C is cold) and returns
|
|
// deterministically — there is no random pick between two ready cases. We then
|
|
// confirm no further tick arrives within a full interval, which a live loop
|
|
// could not satisfy. This is the shutdown guarantee the reconcilers depend on.
|
|
func TestEveryTick_StopsOnContextCancel(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
const interval = 50 * time.Millisecond
|
|
ticks := make(chan struct{}) // unbuffered: fn blocks until the test receives
|
|
everyTick(ctx, interval, func(context.Context) {
|
|
ticks <- struct{}{}
|
|
})
|
|
|
|
// First tick proves the loop is running and leaves the goroutine back in
|
|
// select with ticker.C cold for the next ~interval.
|
|
select {
|
|
case <-ticks:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("everyTick never called fn")
|
|
}
|
|
|
|
cancel()
|
|
|
|
// Cancel landed in the cold gap, so the loop must have taken ctx.Done and
|
|
// returned. A live loop would tick again at ~interval; a stopped one never
|
|
// will.
|
|
select {
|
|
case <-ticks:
|
|
t.Fatal("everyTick ticked again after cancel")
|
|
case <-time.After(interval + 50*time.Millisecond):
|
|
// good: silent past a full interval — the loop returned
|
|
}
|
|
}
|
|
|
|
func TestEveryTick_PassesContextToFn(t *testing.T) {
|
|
type ctxKey string
|
|
const marker ctxKey = "marker"
|
|
ctx, cancel := context.WithCancel(context.WithValue(context.Background(), marker, "present"))
|
|
defer cancel()
|
|
|
|
got := make(chan string, 1)
|
|
everyTick(ctx, time.Millisecond, func(c context.Context) {
|
|
v, _ := c.Value(marker).(string)
|
|
select {
|
|
case got <- v:
|
|
default:
|
|
}
|
|
})
|
|
|
|
select {
|
|
case v := <-got:
|
|
if v != "present" {
|
|
t.Fatalf("fn received wrong context: value = %q, want %q", v, "present")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("fn never ran")
|
|
}
|
|
}
|