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}