fix(graph): stabilize debouncer tests with synctest fake clock #741
+40
-28
@@ -3,6 +3,7 @@ package graph
|
|||||||
import (
|
import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/synctest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -10,48 +11,59 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDebouncer_Coalesces(t *testing.T) {
|
func TestDebouncer_Coalesces(t *testing.T) {
|
||||||
d := NewDebouncer(50 * time.Millisecond)
|
synctest.Test(t, func(t *testing.T) {
|
||||||
var calls atomic.Int32
|
d := NewDebouncer(50 * time.Millisecond)
|
||||||
|
var calls atomic.Int32
|
||||||
|
|
||||||
// Fire 10 rapid calls for the same key — only the last should execute.
|
// Fire 10 rapid calls for the same key — only the last should execute.
|
||||||
for range 10 {
|
for range 10 {
|
||||||
d.Debounce("key1", func() {
|
d.Debounce("key1", func() {
|
||||||
calls.Add(1)
|
calls.Add(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the debounce delay plus some margin.
|
// Advance fake clock past the debounce delay and let goroutines settle.
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
synctest.Wait()
|
||||||
|
|
||||||
assert.Equal(t, int32(1), calls.Load(), "rapid calls should coalesce into a single execution")
|
assert.Equal(t, int32(1), calls.Load(), "rapid calls should coalesce into a single execution")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebouncer_DifferentKeys(t *testing.T) {
|
func TestDebouncer_DifferentKeys(t *testing.T) {
|
||||||
d := NewDebouncer(50 * time.Millisecond)
|
synctest.Test(t, func(t *testing.T) {
|
||||||
var calls atomic.Int32
|
d := NewDebouncer(50 * time.Millisecond)
|
||||||
|
var calls atomic.Int32
|
||||||
|
|
||||||
d.Debounce("key-a", func() { calls.Add(1) })
|
d.Debounce("key-a", func() { calls.Add(1) })
|
||||||
d.Debounce("key-b", func() { calls.Add(1) })
|
d.Debounce("key-b", func() { calls.Add(1) })
|
||||||
d.Debounce("key-c", func() { calls.Add(1) })
|
d.Debounce("key-c", func() { calls.Add(1) })
|
||||||
|
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
synctest.Wait()
|
||||||
|
|
||||||
assert.Equal(t, int32(3), calls.Load(), "different keys should fire independently")
|
assert.Equal(t, int32(3), calls.Load(), "different keys should fire independently")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebouncer_TimerReset(t *testing.T) {
|
func TestDebouncer_TimerReset(t *testing.T) {
|
||||||
d := NewDebouncer(100 * time.Millisecond)
|
synctest.Test(t, func(t *testing.T) {
|
||||||
var value atomic.Int32
|
d := NewDebouncer(100 * time.Millisecond)
|
||||||
|
var value atomic.Int32
|
||||||
|
|
||||||
// First call sets value to 1.
|
// First call sets value to 1.
|
||||||
d.Debounce("key", func() { value.Store(1) })
|
d.Debounce("key", func() { value.Store(1) })
|
||||||
|
|
||||||
// Wait 60ms (less than the 100ms delay), then replace with value 2.
|
// Advance 60ms (less than the 100ms delay) — first timer hasn't fired.
|
||||||
time.Sleep(60 * time.Millisecond)
|
time.Sleep(60 * time.Millisecond)
|
||||||
d.Debounce("key", func() { value.Store(2) })
|
|
||||||
|
|
||||||
// At 60ms the first timer hasn't fired yet. Wait for the second timer.
|
// Replace with value 2 — resets the timer to fire at 60+100 = 160ms.
|
||||||
time.Sleep(150 * time.Millisecond)
|
d.Debounce("key", func() { value.Store(2) })
|
||||||
|
|
||||||
require.Equal(t, int32(2), value.Load(), "later call should replace the earlier one")
|
// Advance another 100ms (total 160ms) to fire the reset timer.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
synctest.Wait()
|
||||||
|
|
||||||
|
require.Equal(t, int32(2), value.Load(), "later call should replace the earlier one")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user