Skip to main content

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