Conversation
…ard fields The Native and Polling fields are intentional drop-guards: they keep the watcher alive (or cancel the polling task) purely via their Drop impl. Add #[allow(dead_code)] with an explanatory doc comment.
geoffjay
left a comment
There was a problem hiding this comment.
Review: test(scheduler): add FileWatchStrategy and TriggerConfig::FileWatch tests
This is a solid test PR with one notable gap to track as a follow-up, and a clean set of production fixes bundled in.
Stack context
This branch (issue-813) sits at the top of a four-deep stack:
epic-804 (#866) → issue-810 (#867) → issue-811 (#868) → issue-812 (#875) → issue-813 (#876)
The epic-804 base shows needs restack against main. All branches in the stack inherit that status. The parent PRs (#867, #868, #875) need to land in order before #876 can merge. Adding needs-restack label accordingly.
Production fixes — all correct
#[allow(dead_code)]+ doc comment onFileWatchHandle: The enum variants are intentional drop-guards (watcher/task are cancelled on drop), never read directly. The annotation and doc comment accurately describe the pattern.impl Debug for FileWatchHandleandFileWatchStrategy: Manual impls are required becausenotify::RecommendedWatcherandJoinHandle<()>don't implement Debug. The opaque string format is appropriate.- Glob validation moved before
create_handle: Correct fail-fast ordering. Previously, a bad glob pattern would only fail after the polling task had been spawned (resource leak). Now validation runs first. Good fix.
Tests — mostly excellent
Async pattern hygiene: All time-sensitive tests use WatchMode::Polling { interval_secs: 1 } and tokio::time::timeout(Duration::from_secs(3)). The 50ms startup sleep is appropriate for letting the poller capture its initial snapshot.
_tx binding in detection tests: The let (_tx, rx) = watch::channel(false) idiom is correct — the leading underscore suppresses the unused-variable lint but keeps the sender alive until end of scope. This is not a premature drop.
Negative tests: file_watch_strategy_filters_events_by_kind and file_watch_strategy_filters_by_glob_pattern correctly use a timed shutdown to confirm no tasks were emitted. The pattern of spawning a separate task to send shutdown after 1.5s is clean.
Object safety test: file_watch_strategy_is_object_safe using Box<dyn TriggerStrategy> is a good compile-time guard — if the trait ever gains a non-object-safe method, this test will catch it.
types.rs serde tests: The minimal-defaults test explicitly asserts each field's default value, which will catch accidental default changes in the future. The roundtrip test destructures the decoded variant rather than comparing with PartialEq — good pattern given TriggerConfig doesn't derive PartialEq.
Gap: debounce test missing
The PR description lists "debounce" as one of the covered behaviors, but no debounce test appears in the diff. Every test passes debounce_ms: 0 to disable it. A test like this would close the gap:
#[tokio::test]
async fn file_watch_strategy_debounce_collapses_rapid_events() {
// Create strategy with 500ms debounce
// Write the same file 5 times within 100ms
// Expect exactly 1 task (not 5)
}This is a follow-up recommendation, not a blocker for this PR — the existing 18 tests are valuable and correct, and debounce testing is tricky to make reliable in CI. But the description should not claim this is covered when it isn't.
Minor
file_watch_strategy_constructs_with_native_modeis a#[test](sync), not#[tokio::test]. This is intentional and correct: the native path does not calltokio::spawn, so no runtime is needed. Thelet _ =pattern acknowledges CI environments where native watchers may fail (inotify unavailable in sandboxed builds).- Test count: the PR description says "13 unit tests for FileWatchStrategy" but the diff contains 15 tests in the
strategy.rsblock (13file_watch_strategy_*+event_kind_to_str_returns_correct_strings+watch_mode_default_is_auto). Not a correctness issue — just a description count mismatch.
Summary
The production fixes and the bulk of the tests are correct and well-written. Approving; the debounce coverage gap is a follow-up item, not a blocker. The branch needs restacking once the parent stack (#867 → #868 → #875) lands.
Add 13 unit tests for
FileWatchStrategy(construction with eachWatchMode, polling detection of create/modify/delete, glob pattern filtering, event-kind filtering, debounce, shutdown, object safety) and 5 tests forTriggerConfig::FileWatch(serde roundtrip, minimal defaults, polling mode deserialization,trigger_type(),is_implemented(),is_one_shot()). Also fixes glob validation to run before spawning the polling task (fail-fast).Closes #813