mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
174 lines
3.9 KiB
Go
174 lines
3.9 KiB
Go
package replay
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestWatcher_RunCoalescesCreateBurstIntoOneBroadcast(t *testing.T) {
|
|
directory := t.TempDir()
|
|
w := NewWatcher(directory)
|
|
w.debounce = 30 * time.Millisecond
|
|
events := w.Subscribe()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() { _ = w.Run(ctx) }()
|
|
defer cancel()
|
|
waitForWatch(t, w.debounce)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
mustCreate(t, filepath.Join(directory, "run-"+string(rune('a'+i)), "meta.json"))
|
|
}
|
|
|
|
select {
|
|
case <-events:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("burst of creates produced no broadcast")
|
|
}
|
|
if extra := drainWithin(events, 4*w.debounce); extra != 0 {
|
|
t.Errorf("burst yielded %d extra broadcasts, want a single coalesced one", extra)
|
|
}
|
|
}
|
|
|
|
func TestWatcher_RunIgnoresWriteAndChmod(t *testing.T) {
|
|
directory := t.TempDir()
|
|
existing := filepath.Join(directory, "marker")
|
|
if err := os.WriteFile(existing, []byte("{}"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
w := NewWatcher(directory)
|
|
w.debounce = 30 * time.Millisecond
|
|
events := w.Subscribe()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() { _ = w.Run(ctx) }()
|
|
defer cancel()
|
|
waitForWatch(t, w.debounce)
|
|
|
|
if err := os.WriteFile(existing, []byte("touched"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chmod(existing, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got := drainWithin(events, 6*w.debounce); got != 0 {
|
|
t.Errorf("write/chmod produced %d broadcasts, want 0", got)
|
|
}
|
|
}
|
|
|
|
func TestWatcher_RunCancelClosesSubscribers(t *testing.T) {
|
|
w := NewWatcher(t.TempDir())
|
|
w.debounce = 30 * time.Millisecond
|
|
events := w.Subscribe()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
go func() { _ = w.Run(ctx) }()
|
|
waitForWatch(t, w.debounce)
|
|
cancel()
|
|
|
|
select {
|
|
case _, ok := <-events:
|
|
if ok {
|
|
// drain any pending broadcast, then expect close
|
|
_, ok = <-events
|
|
}
|
|
if ok {
|
|
t.Fatal("subscriber channel should be closed after ctx cancel")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("subscriber channel was not closed after ctx cancel")
|
|
}
|
|
|
|
post := w.Subscribe()
|
|
select {
|
|
case _, ok := <-post:
|
|
if ok {
|
|
t.Error("Subscribe after shutdown should return a pre-closed channel")
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Error("Subscribe after shutdown blocked instead of returning a closed channel")
|
|
}
|
|
}
|
|
|
|
func mustCreate(t *testing.T, path string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte("{}"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func waitForWatch(t *testing.T, debounce time.Duration) {
|
|
t.Helper()
|
|
time.Sleep(10 * debounce)
|
|
}
|
|
|
|
func drainWithin(events <-chan struct{}, window time.Duration) int {
|
|
count := 0
|
|
deadline := time.After(window)
|
|
for {
|
|
select {
|
|
case <-events:
|
|
count++
|
|
case <-deadline:
|
|
return count
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWatcher_UnsubscribeRemovesChannel(t *testing.T) {
|
|
w := NewWatcher(t.TempDir())
|
|
first := w.Subscribe()
|
|
second := w.Subscribe()
|
|
third := w.Subscribe()
|
|
|
|
if count := len(w.subscribers); count != 3 {
|
|
t.Fatalf("expected 3 subscribers, got %d", count)
|
|
}
|
|
|
|
w.Unsubscribe(second)
|
|
|
|
if count := len(w.subscribers); count != 2 {
|
|
t.Fatalf("expected 2 subscribers after Unsubscribe, got %d", count)
|
|
}
|
|
|
|
// broadcast should still notify remaining subscribers
|
|
w.broadcast()
|
|
select {
|
|
case <-first:
|
|
default:
|
|
t.Error("first subscriber did not receive broadcast")
|
|
}
|
|
select {
|
|
case <-third:
|
|
default:
|
|
t.Error("third subscriber did not receive broadcast")
|
|
}
|
|
}
|
|
|
|
func TestWatcher_UnsubscribeUnknownChannelIsNoop(t *testing.T) {
|
|
w := NewWatcher(t.TempDir())
|
|
existing := w.Subscribe()
|
|
|
|
stranger := make(chan struct{})
|
|
w.Unsubscribe(stranger)
|
|
|
|
if count := len(w.subscribers); count != 1 {
|
|
t.Fatalf("expected 1 subscriber after no-op Unsubscribe, got %d", count)
|
|
}
|
|
|
|
w.broadcast()
|
|
select {
|
|
case <-existing:
|
|
default:
|
|
t.Error("existing subscriber did not receive broadcast")
|
|
}
|
|
}
|