// Unit tests for eventList coalescing and drain semantics. package fswatch import ( "errors" "testing" ) // clear is only used by tests; live code drains via drain() so the // snapshot and the reset happen atomically. func (el *eventList) clear() { el.mu.Lock() defer el.mu.Unlock() el.entries = nil el.err = nil } func TestEventListCreateThenDelete(t *testing.T) { t.Parallel() var el eventList el.create("a") el.remove("a") if el.size() != 1 { t.Fatalf("size after create+remove want 1, got %d", el.size()) } if got := el.getEvents(); len(got) != 0 { t.Fatalf("getEvents should drop create+delete, got %v", got) } } func TestEventListDeleteThenCreate(t *testing.T) { t.Parallel() var el eventList el.remove("a") el.create("a") got := el.getEvents() if len(got) != 1 { t.Fatalf("expected 1 event, got %d", len(got)) } // "Assume update event when rapidly removed and created". if got[0].Kind != EventUpdate { t.Fatalf("expected update, got %v", got[0].Kind) } } func TestEventListCreateDeleteCreate(t *testing.T) { t.Parallel() var el eventList el.create("a") el.remove("a") el.create("a") got := el.getEvents() if len(got) != 1 { t.Fatalf("expected 1 event, got %d", len(got)) } if got[0].Kind != EventUpdate { t.Fatalf("create+delete+create should coalesce to update, got %v", got[0].Kind) } } func TestEventListErrorIsLatchedAndCleared(t *testing.T) { t.Parallel() var el eventList if el.hasError() { t.Fatal("fresh eventList should have no error") } if got := el.getError(); got != nil { t.Fatalf("fresh getError want nil, got %v", got) } el.setError(errors.New("first")) el.setError(errors.New("second")) // only first wins if !el.hasError() { t.Fatal("hasError should be true after setError") } if got := el.getError(); got == nil || got.Error() != "first" { t.Fatalf("getError want first, got %v", got) } el.clear() if el.hasError() { t.Fatal("clear should drop the error") } if got := el.getError(); got != nil { t.Fatalf("post-clear getError want nil, got %v", got) } } func TestEventListDrainIsAtomic(t *testing.T) { t.Parallel() var el eventList el.create("a") el.update("b") el.setError(errors.New("oops")) events, err := el.drain() if err == nil { t.Fatal("drain should return the error") } if len(events) != 2 { t.Fatalf("drain should return 2 events, got %d", len(events)) } events2, err2 := el.drain() if err2 != nil { t.Fatalf("second drain should have no error, got %v", err2) } if len(events2) != 0 { t.Fatalf("second drain should be empty, got %d", len(events2)) } } func TestEventListDrainReturnsErrorWithEvents(t *testing.T) { t.Parallel() var el eventList el.create("file.txt") el.setError(errors.New("overflow")) events, err := el.drain() if err == nil { t.Fatal("expected error from drain") } if len(events) != 1 { t.Fatalf("expected 1 event alongside error, got %d", len(events)) } } func TestEventListDrainForSequences(t *testing.T) { t.Parallel() var el eventList el.create("file.txt") startAfterCreate := el.sequence() el.remove("file.txt") eventsByCallback, err := el.drainForSequences([]uint64{0, startAfterCreate}) if err != nil { t.Fatal(err) } if len(eventsByCallback[0]) != 0 { t.Fatalf("create+delete should cancel for original callback, got %v", eventsByCallback[0]) } if len(eventsByCallback[1]) != 1 { t.Fatalf("expected delete for later callback, got %v", eventsByCallback[1]) } if got := eventsByCallback[1][0]; got.Kind != EventDelete || got.Path != "file.txt" { t.Fatalf("expected delete for file.txt, got %v", got) } }