Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

164 lines
3.6 KiB
Go

package main
import (
"context"
"errors"
"testing"
"time"
)
// newTestLoop wires a pushLoop with caller-controlled timers.
func newTestLoop(push func(context.Context, pushReason) error) (
*pushLoop, chan time.Time, chan time.Time,
) {
fire := make(chan time.Time, 1)
floor := make(chan time.Time, 1)
l := &pushLoop{
debounce: time.Minute, // irrelevant; after is stubbed
dirty: make(chan struct{}, 1),
floor: floor,
after: func(time.Duration) <-chan time.Time { return fire },
push: push,
}
return l, fire, floor
}
func TestPushLoop_DirtyTriggersOnePush(t *testing.T) {
pushed := make(chan pushReason, 4)
l, fire, _ := newTestLoop(func(_ context.Context, r pushReason) error {
pushed <- r
return nil
})
ctx := t.Context()
go l.Run(ctx)
l.NotifyDirty()
fire <- time.Now()
select {
case r := <-pushed:
if r != reasonChange {
t.Fatalf("reason = %q, want %q", r, reasonChange)
}
case <-time.After(time.Second):
t.Fatal("expected a push")
}
}
func TestPushLoop_BurstCoalesces(t *testing.T) {
pushed := make(chan pushReason, 8)
l, fire, _ := newTestLoop(func(_ context.Context, r pushReason) error {
pushed <- r
return nil
})
ctx := t.Context()
go l.Run(ctx)
// Many dirty signals before the timer fires -> one push.
for range 5 {
l.NotifyDirty()
}
fire <- time.Now()
select {
case <-pushed:
case <-time.After(time.Second):
t.Fatal("expected a push")
}
select {
case <-pushed:
t.Fatal("expected exactly one push for a burst")
case <-time.After(100 * time.Millisecond):
}
}
func TestPushLoop_FloorPushesWithoutDirty(t *testing.T) {
pushed := make(chan pushReason, 4)
l, _, floor := newTestLoop(func(_ context.Context, r pushReason) error {
pushed <- r
return nil
})
ctx := t.Context()
go l.Run(ctx)
floor <- time.Now()
select {
case r := <-pushed:
if r != reasonInterval {
t.Fatalf("reason = %q, want %q", r, reasonInterval)
}
case <-time.After(time.Second):
t.Fatal("expected an interval push")
}
}
func TestPushLoop_ErrorDoesNotStopLoop(t *testing.T) {
pushed := make(chan pushReason, 4)
calls := 0
l, fire, _ := newTestLoop(func(_ context.Context, r pushReason) error {
calls++
pushed <- r
if calls == 1 {
return errors.New("pg down")
}
return nil
})
ctx := t.Context()
go l.Run(ctx)
l.NotifyDirty()
fire <- time.Now()
<-pushed // first (errored); also synchronizes the loop draining fire before the next send
l.NotifyDirty()
fire <- time.Now()
select {
case <-pushed: // second succeeds -> loop survived the error
case <-time.After(time.Second):
t.Fatal("loop did not survive a push error")
}
}
func TestPushLoop_ShutdownFlushes(t *testing.T) {
pushed := make(chan pushReason, 4)
l, _, _ := newTestLoop(func(_ context.Context, r pushReason) error {
pushed <- r
return nil
})
ctx, cancel := context.WithCancel(context.Background())
go l.Run(ctx)
cancel()
select {
case r := <-pushed:
if r != reasonShutdown {
t.Fatalf("reason = %q, want %q", r, reasonShutdown)
}
case <-time.After(time.Second):
t.Fatal("expected a shutdown flush push")
}
}
func TestPushLoop_ShutdownFlushHonorsTimeout(t *testing.T) {
gotDeadline := make(chan bool, 1)
l, _, _ := newTestLoop(func(ctx context.Context, _ pushReason) error {
_, ok := ctx.Deadline()
gotDeadline <- ok
return nil
})
l.flushTimeout = 50 * time.Millisecond
ctx, cancel := context.WithCancel(context.Background())
go l.Run(ctx)
cancel()
select {
case ok := <-gotDeadline:
if !ok {
t.Fatal("shutdown flush ctx should carry a deadline when flushTimeout > 0")
}
case <-time.After(time.Second):
t.Fatal("expected a shutdown flush push")
}
}