notify/
lib.rs

1//! Cross-platform file system notification library
2//!
3//! # Installation
4//!
5//! ```toml
6//! [dependencies]
7//! notify = "8.1.0"
8//! ```
9//!
10//! If you want debounced events (or don't need them in-order), see [notify-debouncer-mini](https://docs.rs/notify-debouncer-mini/latest/notify_debouncer_mini/)
11//! or [notify-debouncer-full](https://docs.rs/notify-debouncer-full/latest/notify_debouncer_full/).
12//!
13//! ## Features
14//!
15//! List of compilation features, see below for details
16//!
17//! - `serde` for serialization of events
18//! - `macos_fsevent` enabled by default, for fsevent backend on macos
19//! - `macos_kqueue` for kqueue backend on macos
20//! - `serialization-compat-6` restores the serialization behavior of notify 6, off by default
21//!
22//! ### Serde
23//!
24//! Events are serializable via [serde](https://serde.rs) if the `serde` feature is enabled:
25//!
26//! ```toml
27//! notify = { version = "8.1.0", features = ["serde"] }
28//! ```
29//!
30//! # Known Problems
31//!
32//! ### Network filesystems
33//!
34//! Network mounted filesystems like NFS may not emit any events for notify to listen to.
35//! This applies especially to WSL programs watching windows paths ([issue #254](https://github.com/notify-rs/notify/issues/254)).
36//!
37//! A workaround is the [`PollWatcher`] backend.
38//!
39//! ### Docker with Linux on macOS M1
40//!
41//! Docker on macOS M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`.
42//! You have to manually use the [`PollWatcher`], as the native backend isn't available inside the emulation.
43//!
44//! ### macOS, FSEvents and unowned files
45//!
46//! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)),
47//! some events cannot be observed easily when trying to follow files that do not
48//! belong to you. In this case, reverting to the pollwatcher can fix the issue,
49//! with a slight performance cost.
50//!
51//! ### Editor Behaviour
52//!
53//! If you rely on precise events (Write/Delete/Create..), you will notice that the actual events
54//! can differ a lot between file editors. Some truncate the file on save, some create a new one and replace the old one.
55//! See also [this](https://github.com/notify-rs/notify/issues/247) and [this](https://github.com/notify-rs/notify/issues/113#issuecomment-281836995) issues for example.
56//!
57//! ### Parent folder deletion
58//!
59//! If you want to receive an event for a deletion of folder `b` for the path `/a/b/..`, you will have to watch its parent `/a`.
60//! See [here](https://github.com/notify-rs/notify/issues/403) for more details.
61//!
62//! ### Pseudo Filesystems like /proc, /sys
63//!
64//! Some filesystems like `/proc` and `/sys` on *nix do not emit change events or use correct file change dates.
65//! To circumvent that problem you can use the [`PollWatcher`] with the `compare_contents` option.
66//!
67//! ### Linux: Bad File Descriptor / No space left on device
68//!
69//! This may be the case of running into the max-files watched limits of your user or system.
70//! (Files also includes folders.) Note that for recursive watched folders each file and folder inside counts towards the limit.
71//!
72//! You may increase this limit in linux via
73//! ```sh
74//! sudo sysctl fs.inotify.max_user_instances=8192 # example number
75//! sudo sysctl fs.inotify.max_user_watches=524288 # example number
76//! sudo sysctl -p
77//! ```
78//!
79//! Note that the [`PollWatcher`] is not restricted by this limitation, so it may be an alternative if your users can't increase the limit.
80//!
81//! ### Watching large directories
82//!
83//! When watching a very large amount of files, notify may fail to receive all events.
84//! For example the linux backend is documented to not be a 100% reliable source. See also issue [#412](https://github.com/notify-rs/notify/issues/412).
85//!
86//! # Examples
87//!
88//! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository.
89//!
90//! ```rust
91//! use notify::{Event, RecursiveMode, Result, Watcher};
92//! use std::{path::Path, sync::mpsc};
93//!
94//! fn main() -> Result<()> {
95//!     let (tx, rx) = mpsc::channel::<Result<Event>>();
96//!
97//!     // Use recommended_watcher() to automatically select the best implementation
98//!     // for your platform. The `EventHandler` passed to this constructor can be a
99//!     // closure, a `std::sync::mpsc::Sender`, a `crossbeam_channel::Sender`, or
100//!     // another type the trait is implemented for.
101//!     let mut watcher = notify::recommended_watcher(tx)?;
102//!
103//!     // Add a path to be watched. All files and directories at that path and
104//!     // below will be monitored for changes.
105//! #     #[cfg(not(any(
106//! #     target_os = "freebsd",
107//! #     target_os = "openbsd",
108//! #     target_os = "dragonfly",
109//! #     target_os = "netbsd")))]
110//! #     { // "." doesn't exist on BSD for some reason in CI
111//!     watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
112//! #     }
113//! #     #[cfg(any())]
114//! #     { // don't run this in doctests, it blocks forever
115//!     // Block forever, printing out events as they come in
116//!     for res in rx {
117//!         match res {
118//!             Ok(event) => println!("event: {:?}", event),
119//!             Err(e) => println!("watch error: {:?}", e),
120//!         }
121//!     }
122//! #     }
123//!
124//!     Ok(())
125//! }
126//! ```
127//!
128//! ## With different configurations
129//!
130//! It is possible to create several watchers with different configurations or implementations that
131//! all call the same event function. This can accommodate advanced behaviour or work around limits.
132//!
133//! ```rust
134//! # use notify::{RecursiveMode, Result, Watcher};
135//! # use std::path::Path;
136//! #
137//! # fn main() -> Result<()> {
138//!       fn event_fn(res: Result<notify::Event>) {
139//!           match res {
140//!              Ok(event) => println!("event: {:?}", event),
141//!              Err(e) => println!("watch error: {:?}", e),
142//!           }
143//!       }
144//!
145//!       let mut watcher1 = notify::recommended_watcher(event_fn)?;
146//!       // we will just use the same watcher kind again here
147//!       let mut watcher2 = notify::recommended_watcher(event_fn)?;
148//! #     #[cfg(not(any(
149//! #     target_os = "freebsd",
150//! #     target_os = "openbsd",
151//! #     target_os = "dragonfly",
152//! #     target_os = "netbsd")))]
153//! #     { // "." doesn't exist on BSD for some reason in CI
154//! #     watcher1.watch(Path::new("."), RecursiveMode::Recursive)?;
155//! #     watcher2.watch(Path::new("."), RecursiveMode::Recursive)?;
156//! #     }
157//!       // dropping the watcher1/2 here (no loop etc) will end the program
158//! #
159//! #     Ok(())
160//! # }
161//! ```
162
163#![deny(missing_docs)]
164
165pub use config::{Config, RecursiveMode};
166pub use error::{Error, ErrorKind, Result};
167pub use notify_types::event::{self, Event, EventKind};
168#[cfg(test)]
169use std::collections::HashSet;
170use std::path::Path;
171
172pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
173pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
174#[cfg(any(
175    target_os = "linux",
176    target_os = "android",
177    target_os = "windows",
178    all(target_os = "macos", feature = "macos_kqueue", test)
179))]
180pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
181
182#[inline]
183pub(crate) fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
184    std::sync::mpsc::channel()
185}
186
187#[cfg(any(
188    target_os = "linux",
189    target_os = "android",
190    target_os = "windows",
191    all(target_os = "macos", feature = "macos_kqueue", test)
192))]
193#[inline]
194pub(crate) fn bounded<T>(cap: usize) -> (BoundSender<T>, Receiver<T>) {
195    std::sync::mpsc::sync_channel(cap)
196}
197
198#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
199pub use crate::fsevent::FsEventWatcher;
200#[cfg(any(target_os = "linux", target_os = "android"))]
201pub use crate::inotify::INotifyWatcher;
202#[cfg(any(
203    target_os = "freebsd",
204    target_os = "openbsd",
205    target_os = "netbsd",
206    target_os = "dragonfly",
207    target_os = "ios",
208    all(target_os = "macos", feature = "macos_kqueue")
209))]
210pub use crate::kqueue::KqueueWatcher;
211pub use null::NullWatcher;
212pub use poll::PollWatcher;
213#[cfg(target_os = "windows")]
214pub use windows::ReadDirectoryChangesWatcher;
215
216#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
217pub mod fsevent;
218#[cfg(any(target_os = "linux", target_os = "android"))]
219pub mod inotify;
220#[cfg(any(
221    target_os = "freebsd",
222    target_os = "openbsd",
223    target_os = "dragonfly",
224    target_os = "netbsd",
225    target_os = "ios",
226    all(target_os = "macos", feature = "macos_kqueue")
227))]
228pub mod kqueue;
229#[cfg(target_os = "windows")]
230pub mod windows;
231
232pub mod null;
233pub mod poll;
234
235mod bimap;
236mod config;
237mod error;
238
239#[cfg(test)]
240pub(crate) mod test;
241
242/// The set of requirements for watcher event handling functions.
243///
244/// # Example implementation
245///
246/// ```no_run
247/// use notify::{Event, Result, EventHandler};
248///
249/// /// Prints received events
250/// struct EventPrinter;
251///
252/// impl EventHandler for EventPrinter {
253///     fn handle_event(&mut self, event: Result<Event>) {
254///         if let Ok(event) = event {
255///             println!("Event: {:?}", event);
256///         }
257///     }
258/// }
259/// ```
260pub trait EventHandler: Send + 'static {
261    /// Handles an event.
262    fn handle_event(&mut self, event: Result<Event>);
263}
264
265impl<F> EventHandler for F
266where
267    F: FnMut(Result<Event>) + Send + 'static,
268{
269    fn handle_event(&mut self, event: Result<Event>) {
270        (self)(event);
271    }
272}
273
274#[cfg(feature = "crossbeam-channel")]
275impl EventHandler for crossbeam_channel::Sender<Result<Event>> {
276    fn handle_event(&mut self, event: Result<Event>) {
277        let _ = self.send(event);
278    }
279}
280
281#[cfg(feature = "flume")]
282impl EventHandler for flume::Sender<Result<Event>> {
283    fn handle_event(&mut self, event: Result<Event>) {
284        let _ = self.send(event);
285    }
286}
287
288impl EventHandler for std::sync::mpsc::Sender<Result<Event>> {
289    fn handle_event(&mut self, event: Result<Event>) {
290        let _ = self.send(event);
291    }
292}
293
294/// Watcher kind enumeration
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
296#[non_exhaustive]
297pub enum WatcherKind {
298    /// inotify backend (linux)
299    Inotify,
300    /// FS-Event backend (mac)
301    Fsevent,
302    /// KQueue backend (bsd,optionally mac)
303    Kqueue,
304    /// Polling based backend (fallback)
305    PollWatcher,
306    /// Windows backend
307    ReadDirectoryChangesWatcher,
308    /// Fake watcher for testing
309    NullWatcher,
310}
311
312/// Providing methods for adding and removing paths to watch.
313///
314/// `Box<dyn PathsMut>` is created by [`Watcher::paths_mut`]. See its documentation for more.
315pub trait PathsMut {
316    /// Add a new path to watch. See [`Watcher::watch`] for more.
317    fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
318
319    /// Remove a path from watching. See [`Watcher::unwatch`] for more.
320    fn remove(&mut self, path: &Path) -> Result<()>;
321
322    /// Ensure added/removed paths are applied.
323    ///
324    /// The behaviour of dropping a [`PathsMut`] without calling [`commit`] is unspecified.
325    /// The implementation is free to ignore the changes or not, and may leave the watcher in a started or stopped state.
326    fn commit(self: Box<Self>) -> Result<()>;
327}
328
329/// Type that can deliver file activity notifications
330///
331/// `Watcher` is implemented per platform using the best implementation available on that platform.
332/// In addition to such event driven implementations, a polling implementation is also provided
333/// that should work on any platform.
334pub trait Watcher {
335    /// Create a new watcher with an initial Config.
336    fn new<F: EventHandler>(event_handler: F, config: config::Config) -> Result<Self>
337    where
338        Self: Sized;
339    /// Begin watching a new path.
340    ///
341    /// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is
342    /// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise
343    /// only the directory and its immediate children will be watched.
344    ///
345    /// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only
346    /// for the file.
347    ///
348    /// On some platforms, if the `path` is renamed or removed while being watched, behaviour may
349    /// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted
350    /// one may non-recursively watch the _parent_ directory as well and manage related events.
351    ///
352    /// [#165]: https://github.com/notify-rs/notify/issues/165
353    /// [#166]: https://github.com/notify-rs/notify/issues/166
354    fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
355
356    /// Stop watching a path.
357    ///
358    /// # Errors
359    ///
360    /// Returns an error in the case that `path` has not been watched or if removing the watch
361    /// fails.
362    fn unwatch(&mut self, path: &Path) -> Result<()>;
363
364    /// Add/remove paths to watch.
365    ///
366    /// For some watcher implementations this method provides better performance than multiple calls to [`Watcher::watch`] and [`Watcher::unwatch`] if you want to add/remove many paths at once.
367    ///
368    /// # Examples
369    ///
370    /// ```
371    /// # use notify::{Watcher, RecursiveMode, Result};
372    /// # use std::path::Path;
373    /// # fn main() -> Result<()> {
374    /// # let many_paths_to_add = vec![];
375    /// let mut watcher = notify::recommended_watcher(|_event| { /* event handler */ })?;
376    /// let mut watcher_paths = watcher.paths_mut();
377    /// for path in many_paths_to_add {
378    ///     watcher_paths.add(path, RecursiveMode::Recursive)?;
379    /// }
380    /// watcher_paths.commit()?;
381    /// # Ok(())
382    /// # }
383    /// ```
384    fn paths_mut<'me>(&'me mut self) -> Box<dyn PathsMut + 'me> {
385        struct DefaultPathsMut<'a, T: ?Sized>(&'a mut T);
386        impl<'a, T: Watcher + ?Sized> PathsMut for DefaultPathsMut<'a, T> {
387            fn add(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
388                self.0.watch(path, recursive_mode)
389            }
390            fn remove(&mut self, path: &Path) -> Result<()> {
391                self.0.unwatch(path)
392            }
393            fn commit(self: Box<Self>) -> Result<()> {
394                Ok(())
395            }
396        }
397        Box::new(DefaultPathsMut(self))
398    }
399
400    /// Configure the watcher at runtime.
401    ///
402    /// See the [`Config`](config/struct.Config.html) struct for all configuration options.
403    ///
404    /// # Returns
405    ///
406    /// - `Ok(true)` on success.
407    /// - `Ok(false)` if the watcher does not support or implement the option.
408    /// - `Err(notify::Error)` on failure.
409    fn configure(&mut self, _option: Config) -> Result<bool> {
410        Ok(false)
411    }
412
413    /// Returns the watcher kind, allowing to perform backend-specific tasks
414    fn kind() -> WatcherKind
415    where
416        Self: Sized;
417
418    /// Get the list of watch handles that are currently being watched.
419    #[cfg(test)]
420    fn get_watch_handles(&self) -> HashSet<std::path::PathBuf> {
421        HashSet::default()
422    }
423}
424
425/// The recommended [`Watcher`] implementation for the current platform
426#[cfg(any(target_os = "linux", target_os = "android"))]
427pub type RecommendedWatcher = INotifyWatcher;
428/// The recommended [`Watcher`] implementation for the current platform
429#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
430pub type RecommendedWatcher = FsEventWatcher;
431/// The recommended [`Watcher`] implementation for the current platform
432#[cfg(target_os = "windows")]
433pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
434/// The recommended [`Watcher`] implementation for the current platform
435#[cfg(any(
436    target_os = "freebsd",
437    target_os = "openbsd",
438    target_os = "netbsd",
439    target_os = "dragonfly",
440    target_os = "ios",
441    all(target_os = "macos", feature = "macos_kqueue")
442))]
443pub type RecommendedWatcher = KqueueWatcher;
444/// The recommended [`Watcher`] implementation for the current platform
445#[cfg(not(any(
446    target_os = "linux",
447    target_os = "android",
448    target_os = "macos",
449    target_os = "windows",
450    target_os = "freebsd",
451    target_os = "openbsd",
452    target_os = "netbsd",
453    target_os = "dragonfly",
454    target_os = "ios"
455)))]
456pub type RecommendedWatcher = PollWatcher;
457
458/// Convenience method for creating the [`RecommendedWatcher`] for the current platform.
459pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
460where
461    F: EventHandler,
462{
463    // All recommended watchers currently implement `new`, so just call that.
464    RecommendedWatcher::new(event_handler, Config::default())
465}
466
467#[cfg(test)]
468mod tests {
469    use std::{
470        fs, iter,
471        sync::mpsc,
472        time::{Duration, Instant},
473    };
474
475    use tempfile::tempdir;
476
477    use super::{
478        Config, Error, ErrorKind, Event, NullWatcher, PollWatcher, RecommendedWatcher,
479        RecursiveMode, Result, Watcher, WatcherKind,
480    };
481    use crate::test::*;
482
483    #[test]
484    fn test_object_safe() {
485        let _watcher: &dyn Watcher = &NullWatcher;
486    }
487
488    #[test]
489    fn test_debug_impl() {
490        macro_rules! assert_debug_impl {
491            ($t:ty) => {{
492                #[allow(dead_code)]
493                trait NeedsDebug: std::fmt::Debug {}
494                impl NeedsDebug for $t {}
495            }};
496        }
497
498        assert_debug_impl!(Config);
499        assert_debug_impl!(Error);
500        assert_debug_impl!(ErrorKind);
501        assert_debug_impl!(NullWatcher);
502        assert_debug_impl!(PollWatcher);
503        assert_debug_impl!(RecommendedWatcher);
504        assert_debug_impl!(RecursiveMode);
505        assert_debug_impl!(WatcherKind);
506    }
507
508    fn iter_with_timeout(rx: &mpsc::Receiver<Result<Event>>) -> impl Iterator<Item = Event> + '_ {
509        // wait for up to 10 seconds for the events
510        let deadline = Instant::now() + Duration::from_secs(10);
511        iter::from_fn(move || {
512            if Instant::now() >= deadline {
513                return None;
514            }
515            Some(
516                rx.recv_timeout(deadline - Instant::now())
517                    .expect("did not receive expected event")
518                    .expect("received an error"),
519            )
520        })
521    }
522
523    #[test]
524    fn integration() -> std::result::Result<(), Box<dyn std::error::Error>> {
525        let dir = tempdir()?;
526
527        // set up the watcher
528        let (tx, rx) = std::sync::mpsc::channel();
529        let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
530        watcher.watch(dir.path(), RecursiveMode::Recursive)?;
531
532        // create a new file
533        let file_path = dir.path().join("file.txt");
534        fs::write(&file_path, b"Lorem ipsum")?;
535
536        println!("waiting for event at {}", file_path.display());
537
538        // wait for the create event, ignore all other events
539        for event in iter_with_timeout(&rx) {
540            if event.paths == vec![file_path.clone()]
541                || event.paths == vec![file_path.canonicalize()?]
542            {
543                return Ok(());
544            }
545
546            println!("unexpected event: {event:?}");
547        }
548
549        panic!("did not receive expected event");
550    }
551
552    #[test]
553    fn test_paths_mut() -> std::result::Result<(), Box<dyn std::error::Error>> {
554        let dir = tempdir()?;
555
556        let dir_a = dir.path().join("a");
557        let dir_b = dir.path().join("b");
558
559        fs::create_dir(&dir_a)?;
560        fs::create_dir(&dir_b)?;
561
562        let (tx, rx) = std::sync::mpsc::channel();
563        let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
564
565        // start watching a and b
566        {
567            let mut watcher_paths = watcher.paths_mut();
568            watcher_paths.add(&dir_a, RecursiveMode::Recursive)?;
569            watcher_paths.add(&dir_b, RecursiveMode::Recursive)?;
570            watcher_paths.commit()?;
571        }
572
573        // create file1 in both a and b
574        let a_file1 = dir_a.join("file1");
575        let b_file1 = dir_b.join("file1");
576        fs::write(&a_file1, b"Lorem ipsum")?;
577        fs::write(&b_file1, b"Lorem ipsum")?;
578
579        // wait for create events of a/file1 and b/file1
580        let mut a_file1_encountered: bool = false;
581        let mut b_file1_encountered: bool = false;
582        for event in iter_with_timeout(&rx) {
583            for path in event.paths {
584                a_file1_encountered =
585                    a_file1_encountered || (path == a_file1 || path == a_file1.canonicalize()?);
586                b_file1_encountered =
587                    b_file1_encountered || (path == b_file1 || path == b_file1.canonicalize()?);
588            }
589            if a_file1_encountered && b_file1_encountered {
590                break;
591            }
592        }
593        assert!(a_file1_encountered, "Did not receive event of {a_file1:?}");
594        assert!(b_file1_encountered, "Did not receive event of {b_file1:?}");
595
596        // stop watching a
597        {
598            let mut watcher_paths = watcher.paths_mut();
599            watcher_paths.remove(&dir_a)?;
600            watcher_paths.commit()?;
601        }
602
603        // create file2 in both a and b
604        let a_file2 = dir_a.join("file2");
605        let b_file2 = dir_b.join("file2");
606        fs::write(&a_file2, b"Lorem ipsum")?;
607        fs::write(&b_file2, b"Lorem ipsum")?;
608
609        // wait for the create event of b/file2 only
610        for event in iter_with_timeout(&rx) {
611            for path in event.paths {
612                assert!(
613                    path != a_file2 || path != a_file2.canonicalize()?,
614                    "Event of {a_file2:?} should not be received"
615                );
616                if path == b_file2 || path == b_file2.canonicalize()? {
617                    return Ok(());
618                }
619            }
620        }
621        panic!("Did not receive the event of {b_file2:?}");
622    }
623
624    #[test]
625    fn create_file() {
626        let tmpdir = testdir();
627        let (mut watcher, mut rx) = recommended_channel();
628        watcher.watch_recursively(&tmpdir);
629
630        let path = tmpdir.path().join("entry");
631        std::fs::File::create_new(&path).expect("create");
632
633        rx.wait_unordered([expected(path).create()]);
634    }
635
636    #[test]
637    fn create_dir() {
638        let tmpdir = testdir();
639        let (mut watcher, mut rx) = recommended_channel();
640        watcher.watch_recursively(&tmpdir);
641
642        let path = tmpdir.path().join("entry");
643        std::fs::create_dir(&path).expect("create");
644
645        rx.wait_unordered([expected(path).create()]);
646    }
647
648    #[test]
649    fn modify_file() {
650        let tmpdir = testdir();
651        let (mut watcher, mut rx) = recommended_channel();
652
653        let path = tmpdir.path().join("entry");
654        std::fs::File::create_new(&path).expect("create");
655
656        watcher.watch_recursively(&tmpdir);
657        std::fs::write(&path, b"123").expect("write");
658
659        rx.wait_unordered([expected(path).modify()]);
660    }
661
662    #[test]
663    fn remove_file() {
664        let tmpdir = testdir();
665        let (mut watcher, mut rx) = recommended_channel();
666
667        let path = tmpdir.path().join("entry");
668        std::fs::File::create_new(&path).expect("create");
669
670        watcher.watch_recursively(&tmpdir);
671        std::fs::remove_file(&path).expect("remove");
672
673        rx.wait_unordered([expected(path).remove()]);
674    }
675}