Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/tests/annealer_corpus/notify_lib.rs

38.8 KiB, 1 run

created by r1870400018:11756, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Cross-platform file system notification library
2//!
3//! # Installation
4//!
5//! ```toml
6//! [dependencies]
7//! notify = "9.0.0-rc.4"
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 = "9.0.0-rc.4", 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, RecursiveMode, Result, 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_os = "freebsd",
118//! # target_os = "openbsd",
119//! # target_os = "dragonfly",
120//! # target_os = "netbsd")))]
121//! # { // "." doesn't exist on BSD for some reason in CI
122//! watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
123//! # }
124//! # #[cfg(any())]
125//! # { // don't run this in doctests, it blocks forever
126//! // Block forever, printing out events as they come in
127//! for res in rx {
128//! match res {
129//! Ok(event) => println!("event: {:?}", event),
130//! Err(e) => println!("watch error: {:?}", e),
131//! }
132//! }
133//! # }
134//!
135//! Ok(())
136//! }
137//! ```
138//!
139//! ## With different configurations
140//!
141//! It is possible to create several watchers with different configurations or implementations that
142//! all call the same event function. This can accommodate advanced behaviour or work around limits.
143//!
144//! ```rust
145//! # use notify::{RecursiveMode, Result, Watcher};
146//! # use std::path::Path;
147//! #
148//! # fn main() -> Result<()> {
149//! fn event_fn(res: Result<notify::Event>) {
150//! match res {
151//! Ok(event) => println!("event: {:?}", event),
152//! Err(e) => println!("watch error: {:?}", e),
153//! }
154//! }
155//!
156//! let mut watcher1 = notify::recommended_watcher(event_fn)?;
157//! // we will just use the same watcher kind again here
158//! let mut watcher2 = notify::recommended_watcher(event_fn)?;
159//! # #[cfg(not(any(
160//! # target_os = "freebsd",
161//! # target_os = "openbsd",
162//! # target_os = "dragonfly",
163//! # target_os = "netbsd")))]
164//! # { // "." doesn't exist on BSD for some reason in CI
165//! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?;
166//! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?;
167//! # }
168//! // dropping the watcher1/2 here (no loop etc) will end the program
169//! #
170//! # Ok(())
171//! # }
172//! ```
173
174#![deny(missing_docs)]
175
176pub use config::{Config, PathOp, RecursiveMode, WatchPathConfig, WindowsPathSeparatorStyle};
177pub use error::{Error, ErrorKind, Result, UpdatePathsError};
178pub use notify_types::event::{self, Event, EventKind, EventKindMask};
179use std::path::{Path, PathBuf};
180
181pub(crate) type StdResult<T, E> = std::result::Result<T, E>;
182pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
183pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
184#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
185pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
186
187#[inline]
188pub(crate) fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
189 std::sync::mpsc::channel()
190}
191
192#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
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 config;
236mod error;
237mod paths;
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
288#[cfg(feature = "futures")]
289impl EventHandler for futures::channel::mpsc::UnboundedSender<Result<Event>> {
290 fn handle_event(&mut self, event: Result<Event>) {
291 let _ = self.unbounded_send(event);
292 }
293}
294
295#[cfg(feature = "tokio")]
296impl EventHandler for tokio::sync::mpsc::UnboundedSender<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/// Type that can deliver file activity notifications
327///
328/// `Watcher` is implemented per platform using the best implementation available on that platform.
329/// In addition to such event driven implementations, a polling implementation is also provided
330/// that should work on any platform.
331///
332/// # Creating a watcher
333///
334/// Because `Watcher` is a trait, Rust usually can't infer the concrete watcher type when calling
335/// `Watcher::new(...)` directly. Prefer [`recommended_watcher`] / [`RecommendedWatcher`] (or a
336/// specific backend like [`PollWatcher`]) when constructing a watcher.
337///
338/// ```no_run
339/// use notify::{Event, RecursiveMode, Result, Watcher};
340/// use std::{path::Path, sync::mpsc};
341///
342/// fn main() -> Result<()> {
343/// let (tx, _rx) = mpsc::channel::<Result<Event>>();
344/// let mut watcher = notify::recommended_watcher(tx)?;
345/// watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
346/// Ok(())
347/// }
348/// ```
349pub trait Watcher {
350 /// Create a new watcher with an initial Config.
351 fn new<F: EventHandler>(event_handler: F, config: config::Config) -> Result<Self>
352 where
353 Self: Sized;
354 /// Begin watching a new path.
355 ///
356 /// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is
357 /// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise
358 /// only the directory and its immediate children will be watched.
359 ///
360 /// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only
361 /// for the file.
362 ///
363 /// Event paths are reported using the same root representation as `path`. If `path` is
364 /// relative, emitted event paths are relative to the process current directory at the time this
365 /// method is called. If `path` is absolute, emitted event paths are absolute. Convert `path`
366 /// before calling this method if your application needs a specific representation.
367 ///
368 /// On success, calling this method again for the same backend-resolved path replaces the
369 /// existing watch for that path. The recursive mode is updated to the new value, a second
370 /// independent watch is not added, and a single call to [`Watcher::unwatch`] removes the
371 /// watched path.
372 ///
373 /// On some platforms, if the `path` is renamed or removed while being watched, behaviour may
374 /// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted
375 /// one may non-recursively watch the _parent_ directory as well and manage related events.
376 ///
377 /// [#165]: https://github.com/notify-rs/notify/issues/165
378 /// [#166]: https://github.com/notify-rs/notify/issues/166
379 fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> 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 in batch.
390 ///
391 /// For some [`Watcher`] implementations this method provides better performance than multiple
392 /// calls to [`Watcher::watch`] and [`Watcher::unwatch`] if you want to add/remove many paths at once.
393 ///
394 /// # Errors
395 ///
396 /// Returns [`UpdatePathsError`] if any operation fails. Operations are applied sequentially.
397 /// When an error occurs, processing stops: operations before `origin` have been applied,
398 /// `origin` is the operation that failed (if known), and `remaining` are the operations that
399 /// were not attempted. `remaining` does not include `origin`.
400 ///
401 /// # Examples
402 ///
403 /// ```
404 /// # use notify::{Watcher, RecursiveMode, PathOp};
405 /// # use std::path::{Path, PathBuf};
406 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
407 /// # let many_paths_to_add: Vec<PathBuf> = vec![];
408 /// # let many_paths_to_remove: Vec<PathBuf> = vec![];
409 /// let mut watcher = notify::NullWatcher;
410 /// let mut batch = Vec::new();
411 ///
412 /// for path in many_paths_to_add {
413 /// batch.push(PathOp::watch_recursive(path));
414 /// }
415 ///
416 /// for path in many_paths_to_remove {
417 /// batch.push(PathOp::unwatch(path));
418 /// }
419 ///
420 /// // real work is done there
421 /// watcher.update_paths(batch)?;
422 /// # Ok(())
423 /// # }
424 /// ```
425 fn update_paths(&mut self, ops: Vec<PathOp>) -> StdResult<(), UpdatePathsError> {
426 update_paths(ops, |op| match op {
427 PathOp::Watch(path, config) => self
428 .watch(&path, config.recursive_mode())
429 .map_err(|e| (PathOp::Watch(path, config), e)),
430 PathOp::Unwatch(path) => self.unwatch(&path).map_err(|e| (PathOp::Unwatch(path), e)),
431 })
432 }
433
434 /// Configure the watcher at runtime.
435 ///
436 /// See the [`Config`](config/struct.Config.html) struct for all configuration options.
437 ///
438 /// # Returns
439 ///
440 /// - `Ok(true)` on success.
441 /// - `Ok(false)` if the watcher does not support or implement the option.
442 /// - `Err(notify::Error)` on failure.
443 fn configure(&mut self, _option: Config) -> Result<bool> {
444 Ok(false)
445 }
446
447 /// Returns the currently watched paths and their recursive modes.
448 ///
449 /// Returned paths use the same representation that was passed to [`Watcher::watch`] or
450 /// [`Watcher::update_paths`].
451 ///
452 /// # Errors
453 ///
454 /// Returns an error if the watcher implementation cannot provide this information.
455 fn watched_paths(&self) -> Result<Vec<(PathBuf, RecursiveMode)>> {
456 Err(Error::generic(
457 "listing watched paths is not supported by this watcher",
458 ))
459 }
460
461 /// Returns the watcher kind, allowing to perform backend-specific tasks
462 fn kind() -> WatcherKind
463 where
464 Self: Sized;
465}
466
467/// The recommended [`Watcher`] implementation for the current platform
468#[cfg(any(target_os = "linux", target_os = "android"))]
469pub type RecommendedWatcher = INotifyWatcher;
470/// The recommended [`Watcher`] implementation for the current platform
471#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
472pub type RecommendedWatcher = FsEventWatcher;
473/// The recommended [`Watcher`] implementation for the current platform
474#[cfg(target_os = "windows")]
475pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
476/// The recommended [`Watcher`] implementation for the current platform
477#[cfg(any(
478 target_os = "freebsd",
479 target_os = "openbsd",
480 target_os = "netbsd",
481 target_os = "dragonfly",
482 target_os = "ios",
483 all(target_os = "macos", feature = "macos_kqueue")
484))]
485pub type RecommendedWatcher = KqueueWatcher;
486/// The recommended [`Watcher`] implementation for the current platform
487#[cfg(not(any(
488 target_os = "linux",
489 target_os = "android",
490 target_os = "macos",
491 target_os = "windows",
492 target_os = "freebsd",
493 target_os = "openbsd",
494 target_os = "netbsd",
495 target_os = "dragonfly",
496 target_os = "ios"
497)))]
498pub type RecommendedWatcher = PollWatcher;
499
500/// Convenience method for creating the [`RecommendedWatcher`] for the current platform.
501///
502/// This is often the most ergonomic way to construct a watcher, because calling `Watcher::new(...)`
503/// requires specifying the concrete watcher type.
504///
505/// ```no_run
506/// use notify::{RecursiveMode, Result, Watcher};
507/// use std::path::Path;
508///
509/// fn main() -> Result<()> {
510/// let mut watcher = notify::recommended_watcher(|res| println!("event: {res:?}"))?;
511/// watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
512/// Ok(())
513/// }
514/// ```
515pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
516where
517 F: EventHandler,
518{
519 // All recommended watchers currently implement `new`, so just call that.
520 RecommendedWatcher::new(event_handler, Config::default())
521}
522
523pub(crate) fn update_paths<F>(ops: Vec<PathOp>, mut apply: F) -> StdResult<(), UpdatePathsError>
524where
525 F: FnMut(PathOp) -> StdResult<(), (PathOp, Error)>,
526{
527 let mut iter = ops.into_iter();
528 while let Some(op) = iter.next() {
529 if let Err((error_op, source)) = apply(op) {
530 return Err(UpdatePathsError {
531 source,
532 origin: Some(error_op),
533 remaining: iter.collect(),
534 });
535 }
536 }
537 Ok(())
538}
539
540#[cfg(test)]
541mod tests {
542 use std::{
543 collections::HashSet,
544 fs, iter,
545 path::{Path, PathBuf},
546 sync::mpsc,
547 time::{Duration, Instant},
548 };
549
550 use tempfile::tempdir;
551
552 use super::{
553 Config, Error, ErrorKind, Event, NullWatcher, PathOp, PollWatcher, RecommendedWatcher,
554 RecursiveMode, Result, StdResult, WatchPathConfig, Watcher, WatcherKind,
555 };
556 use crate::test::*;
557
558 #[test]
559 fn test_object_safe() {
560 let _watcher: &dyn Watcher = &NullWatcher;
561 }
562
563 #[test]
564 fn test_debug_impl() {
565 macro_rules! assert_debug_impl {
566 ($t:ty) => {{
567 #[allow(dead_code)]
568 trait NeedsDebug: std::fmt::Debug {}
569 impl NeedsDebug for $t {}
570 }};
571 }
572
573 assert_debug_impl!(Config);
574 assert_debug_impl!(Error);
575 assert_debug_impl!(ErrorKind);
576 assert_debug_impl!(NullWatcher);
577 assert_debug_impl!(PollWatcher);
578 assert_debug_impl!(RecommendedWatcher);
579 assert_debug_impl!(RecursiveMode);
580 assert_debug_impl!(WatcherKind);
581 }
582
583 fn iter_with_timeout(rx: &mpsc::Receiver<Result<Event>>) -> impl Iterator<Item = Event> + '_ {
584 // wait for up to 10 seconds for the events
585 let deadline = Instant::now() + Duration::from_secs(10);
586 iter::from_fn(move || {
587 if Instant::now() >= deadline {
588 return None;
589 }
590 Some(
591 rx.recv_timeout(deadline - Instant::now())
592 .expect("did not receive expected event")
593 .expect("received an error"),
594 )
595 })
596 }
597
598 fn canonical_or_path(path: &Path) -> PathBuf {
599 path.canonicalize()
600 .expect("test paths should always be canonicalizable")
601 }
602
603 fn canonical_watch_set(
604 paths: Vec<(PathBuf, RecursiveMode)>,
605 ) -> HashSet<(PathBuf, RecursiveMode)> {
606 paths
607 .into_iter()
608 .map(|(path, recursive_mode)| (canonical_or_path(&path), recursive_mode))
609 .collect()
610 }
611
612 fn matches_path(path: &Path, expected: &Path, canonical_expected: Option<&PathBuf>) -> bool {
613 path == expected || canonical_expected.is_some_and(|canonical| path == canonical)
614 }
615
616 fn update_paths_unwatch_with_retry(
617 watcher: &mut RecommendedWatcher,
618 path: &Path,
619 ) -> Result<()> {
620 const FSEVENT_UNWATCH_RETRIES: usize = 5;
621 const FSEVENT_UNWATCH_RETRY_BASE_DELAY: Duration = Duration::from_millis(50);
622
623 for attempt in 0..=FSEVENT_UNWATCH_RETRIES {
624 match watcher.update_paths(vec![PathOp::unwatch(path)]) {
625 Ok(()) => return Ok(()),
626 Err(err)
627 if RecommendedWatcher::kind() == WatcherKind::Fsevent
628 && matches!(
629 &err.source.kind,
630 ErrorKind::Io(io_err) if io_err.raw_os_error() == Some(9)
631 )
632 && attempt < FSEVENT_UNWATCH_RETRIES =>
633 {
634 let delay_factor = 1u32 << attempt;
635 std::thread::sleep(FSEVENT_UNWATCH_RETRY_BASE_DELAY * delay_factor);
636 }
637 Err(err) => return Err(err.into()),
638 }
639 }
640
641 unreachable!("fsevent unwatch retries must return or error")
642 }
643
644 #[test]
645 fn integration() -> std::result::Result<(), Box<dyn std::error::Error>> {
646 let dir = tempdir()?;
647
648 // set up the watcher
649 let (tx, rx) = std::sync::mpsc::channel();
650 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
651 watcher.watch(dir.path(), RecursiveMode::Recursive)?;
652
653 // create a new file
654 let file_path = dir.path().join("file.txt");
655 fs::write(&file_path, b"Lorem ipsum")?;
656
657 println!("waiting for event at {}", file_path.display());
658
659 // wait for the create event, ignore all other events
660 for event in iter_with_timeout(&rx) {
661 if event.paths == vec![file_path.clone()]
662 || event.paths == vec![file_path.canonicalize()?]
663 {
664 return Ok(());
665 }
666
667 println!("unexpected event: {event:?}");
668 }
669
670 panic!("did not receive expected event");
671 }
672
673 #[test]
674 fn event_paths_preserve_relative_watch_root(
675 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
676 let cwd = std::env::current_dir()?;
677 let dir = tempfile::Builder::new()
678 .prefix("notify-relative-")
679 .tempdir_in(&cwd)?;
680 let relative_dir = dir.path().strip_prefix(&cwd)?.to_path_buf();
681 let relative_file = relative_dir.join("file.txt");
682 let absolute_file = dir.path().join("file.txt");
683
684 let (tx, rx) = std::sync::mpsc::channel();
685 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
686 watcher.watch(&relative_dir, RecursiveMode::Recursive)?;
687
688 assert!(
689 watcher
690 .watched_paths()?
691 .iter()
692 .any(|(path, mode)| path == &relative_dir && *mode == RecursiveMode::Recursive),
693 "watched_paths() did not preserve relative watch path"
694 );
695
696 fs::write(&absolute_file, b"Lorem ipsum")?;
697
698 for event in iter_with_timeout(&rx) {
699 if event.paths.iter().any(|path| path == &relative_file) {
700 assert!(
701 event.paths.iter().all(|path| path.is_relative()),
702 "relative watch emitted absolute path: {event:?}"
703 );
704 return Ok(());
705 }
706
707 println!("unexpected event: {event:?}");
708 }
709
710 panic!("did not receive expected relative event path");
711 }
712
713 #[test]
714 #[cfg(target_os = "windows")]
715 fn test_windows_trash_dir() -> std::result::Result<(), Box<dyn std::error::Error>> {
716 use crate::recommended_watcher;
717
718 let dir = tempdir()?;
719 let child_dir = dir.path().join("child");
720 fs::create_dir(&child_dir)?;
721
722 let mut watcher = recommended_watcher(|_| {
723 // Do something with the event
724 })?;
725 watcher.watch(&child_dir, RecursiveMode::NonRecursive)?;
726
727 trash::delete(&child_dir)?;
728
729 watcher.watch(dir.path(), RecursiveMode::NonRecursive)?;
730
731 Ok(())
732 }
733
734 #[test]
735 fn test_update_paths() -> std::result::Result<(), Box<dyn std::error::Error>> {
736 let dir = tempdir()?;
737
738 let dir_a = dir.path().join("a");
739 let dir_b = dir.path().join("b");
740
741 fs::create_dir(&dir_a)?;
742 fs::create_dir(&dir_b)?;
743
744 let (tx, rx) = std::sync::mpsc::channel();
745 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
746
747 // start watching a and b
748 watcher.update_paths(vec![
749 PathOp::Watch(
750 dir_a.clone(),
751 WatchPathConfig::new(RecursiveMode::Recursive),
752 ),
753 PathOp::Watch(
754 dir_b.clone(),
755 WatchPathConfig::new(RecursiveMode::Recursive),
756 ),
757 ])?;
758
759 // create file1 in both a and b
760 let a_file1 = dir_a.join("file1");
761 let b_file1 = dir_b.join("file1");
762 fs::write(&a_file1, b"Lorem ipsum")?;
763 fs::write(&b_file1, b"Lorem ipsum")?;
764 let a_file1_canonical = a_file1.canonicalize().ok();
765 let b_file1_canonical = b_file1.canonicalize().ok();
766
767 // wait for create events of a/file1 and b/file1
768 let mut a_file1_encountered: bool = false;
769 let mut b_file1_encountered: bool = false;
770 for event in iter_with_timeout(&rx) {
771 for path in event.paths {
772 a_file1_encountered = a_file1_encountered
773 || matches_path(&path, &a_file1, a_file1_canonical.as_ref());
774 b_file1_encountered = b_file1_encountered
775 || matches_path(&path, &b_file1, b_file1_canonical.as_ref());
776 }
777 if a_file1_encountered && b_file1_encountered {
778 break;
779 }
780 }
781 assert!(a_file1_encountered, "Did not receive event of {a_file1:?}");
782 assert!(b_file1_encountered, "Did not receive event of {b_file1:?}");
783
784 // stop watching a
785 update_paths_unwatch_with_retry(&mut watcher, &dir_a)?;
786
787 // create file2 in both a and b
788 let a_file2 = dir_a.join("file2");
789 let b_file2 = dir_b.join("file2");
790 fs::write(&a_file2, b"Lorem ipsum")?;
791 fs::write(&b_file2, b"Lorem ipsum")?;
792 let a_file2_canonical = a_file2.canonicalize().ok();
793 let b_file2_canonical = b_file2.canonicalize().ok();
794
795 // wait for the create event of b/file2 only
796 for event in iter_with_timeout(&rx) {
797 for path in event.paths {
798 assert!(
799 !matches_path(&path, &a_file2, a_file2_canonical.as_ref()),
800 "Event of {a_file2:?} should not be received"
801 );
802 if matches_path(&path, &b_file2, b_file2_canonical.as_ref()) {
803 return Ok(());
804 }
805 }
806 }
807 panic!("Did not receive the event of {b_file2:?}");
808 }
809
810 #[test]
811 fn watched_paths_reflect_watch_and_unwatch(
812 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
813 let dir = tempdir()?;
814 let dir_a = dir.path().join("a");
815 let dir_b = dir.path().join("b");
816 fs::create_dir(&dir_a)?;
817 fs::create_dir(&dir_b)?;
818
819 let (tx, _rx) = std::sync::mpsc::channel();
820 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
821
822 watcher.watch(&dir_a, RecursiveMode::Recursive)?;
823 watcher.watch(&dir_b, RecursiveMode::NonRecursive)?;
824
825 let watched = canonical_watch_set(watcher.watched_paths()?);
826 assert!(watched.contains(&(canonical_or_path(&dir_a), RecursiveMode::Recursive)));
827 assert!(watched.contains(&(canonical_or_path(&dir_b), RecursiveMode::NonRecursive)));
828
829 watcher.unwatch(&dir_a)?;
830
831 let watched = canonical_watch_set(watcher.watched_paths()?);
832 assert!(!watched.contains(&(canonical_or_path(&dir_a), RecursiveMode::Recursive)));
833 assert!(watched.contains(&(canonical_or_path(&dir_b), RecursiveMode::NonRecursive)));
834
835 Ok(())
836 }
837
838 #[test]
839 fn rewatching_same_path_replaces_recursive_mode(
840 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
841 let dir = tempdir()?;
842 let root = canonical_or_path(dir.path());
843
844 let (tx, _rx) = std::sync::mpsc::channel();
845 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
846
847 watcher.watch(dir.path(), RecursiveMode::Recursive)?;
848 watcher.watch(dir.path(), RecursiveMode::NonRecursive)?;
849
850 let watched = canonical_watch_set(watcher.watched_paths()?);
851 assert!(watched.contains(&(root.clone(), RecursiveMode::NonRecursive)));
852 assert!(!watched.contains(&(root.clone(), RecursiveMode::Recursive)));
853 assert_eq!(
854 watched.iter().filter(|(path, _mode)| path == &root).count(),
855 1
856 );
857
858 watcher.unwatch(dir.path())?;
859
860 let watched = canonical_watch_set(watcher.watched_paths()?);
861 assert!(!watched.iter().any(|(path, _mode)| path == &root));
862
863 Ok(())
864 }
865
866 #[test]
867 fn overlapping_recursive_watch_preserves_explicit_child(
868 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
869 let dir = tempdir()?;
870 let child = dir.path().join("child");
871 fs::create_dir(&child)?;
872
873 let (tx, _rx) = std::sync::mpsc::channel();
874 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
875
876 watcher.watch(&child, RecursiveMode::NonRecursive)?;
877 watcher.watch(dir.path(), RecursiveMode::Recursive)?;
878
879 let watched = canonical_watch_set(watcher.watched_paths()?);
880 assert!(watched.contains(&(canonical_or_path(dir.path()), RecursiveMode::Recursive)));
881 assert!(watched.contains(&(canonical_or_path(&child), RecursiveMode::NonRecursive)));
882
883 watcher.unwatch(dir.path())?;
884
885 let watched = canonical_watch_set(watcher.watched_paths()?);
886 assert!(!watched.contains(&(canonical_or_path(dir.path()), RecursiveMode::Recursive)));
887 assert!(watched.contains(&(canonical_or_path(&child), RecursiveMode::NonRecursive)));
888
889 Ok(())
890 }
891
892 #[test]
893 fn overlapping_recursive_child_rewrites_descendant_event_paths(
894 ) -> std::result::Result<(), Box<dyn std::error::Error>> {
895 let cwd = std::env::current_dir()?;
896 let dir = tempfile::Builder::new()
897 .prefix("notify-overlap-")
898 .tempdir_in(&cwd)?;
899 let relative_dir = dir.path().strip_prefix(&cwd)?.to_path_buf();
900 let child = dir.path().join("child");
901 let grandchild = child.join("grandchild");
902 fs::create_dir_all(&grandchild)?;
903
904 let (tx, rx) = std::sync::mpsc::channel();
905 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
906
907 watcher.watch(&relative_dir, RecursiveMode::Recursive)?;
908 watcher.watch(&child, RecursiveMode::Recursive)?;
909 watcher.unwatch(&relative_dir)?;
910
911 let watched = watcher.watched_paths()?;
912 assert!(
913 watched
914 .iter()
915 .any(|(path, mode)| path == &child && *mode == RecursiveMode::Recursive),
916 "watched_paths() did not preserve explicit child path: {watched:?}"
917 );
918
919 let file = grandchild.join("file.txt");
920 let stale_file = relative_dir
921 .join("child")
922 .join("grandchild")
923 .join("file.txt");
924 fs::write(&file, b"Lorem ipsum")?;
925
926 for event in iter_with_timeout(&rx) {
927 if event.paths.iter().any(|path| path == &file) {
928 assert!(
929 event.paths.iter().all(|path| path.is_absolute()),
930 "absolute child watch emitted non-absolute path: {event:?}"
931 );
932 return Ok(());
933 }
934
935 assert!(
936 !event.paths.iter().any(|path| path == &stale_file),
937 "stale parent-relative file path after parent unwatch: {event:?}"
938 );
939 }
940
941 panic!("did not receive expected child event path");
942 }
943
944 #[test]
945 fn update_paths_in_a_loop_with_errors() -> StdResult<(), Box<dyn std::error::Error>> {
946 let dir = tempdir()?;
947 let existing_dir_1 = dir.path().join("existing_dir_1");
948 let not_existent_file = dir.path().join("not_existent_file");
949 let existing_dir_2 = dir.path().join("existing_dir_2");
950
951 fs::create_dir(&existing_dir_1)?;
952 fs::create_dir(&existing_dir_2)?;
953
954 let mut paths_to_add = vec![
955 PathOp::watch_recursive(existing_dir_1.clone()),
956 PathOp::watch_recursive(not_existent_file.clone()),
957 PathOp::watch_recursive(existing_dir_2.clone()),
958 ];
959
960 let (tx, rx) = std::sync::mpsc::channel();
961 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
962
963 while !paths_to_add.is_empty() {
964 if let Err(e) = watcher.update_paths(std::mem::take(&mut paths_to_add)) {
965 paths_to_add = e.remaining;
966 }
967 }
968
969 fs::write(existing_dir_1.join("1"), "")?;
970 fs::write(&not_existent_file, "")?;
971 let waiting_path = existing_dir_2.join("1");
972 fs::write(&waiting_path, "")?;
973 let waiting_path_canonical = waiting_path.canonicalize().ok();
974
975 for event in iter_with_timeout(&rx) {
976 let path = event
977 .paths
978 .first()
979 .unwrap_or_else(|| panic!("event must have a path: {event:?}"));
980 assert!(
981 path != &not_existent_file,
982 "unexpected {not_existent_file:?} event"
983 );
984 if matches_path(path, &waiting_path, waiting_path_canonical.as_ref()) {
985 return Ok(());
986 }
987 }
988
989 panic!("Did not receive the event of {waiting_path:?}");
990 }
991
992 #[test]
993 fn create_file() {
994 let tmpdir = testdir();
995 let (mut watcher, mut rx) = recommended_channel();
996 watcher.watch_recursively(&tmpdir);
997
998 let path = tmpdir.path().join("entry");
999 std::fs::File::create_new(&path).expect("create");
1000
1001 rx.wait_unordered([expected(path).create()]);
1002 }
1003
1004 #[test]
1005 fn create_dir() {
1006 let tmpdir = testdir();
1007 let (mut watcher, mut rx) = recommended_channel();
1008 watcher.watch_recursively(&tmpdir);
1009
1010 let path = tmpdir.path().join("entry");
1011 std::fs::create_dir(&path).expect("create");
1012
1013 rx.wait_unordered([expected(path).create()]);
1014 }
1015
1016 #[test]
1017 fn modify_file() {
1018 let tmpdir = testdir();
1019 let (mut watcher, mut rx) = recommended_channel();
1020
1021 let path = tmpdir.path().join("entry");
1022 std::fs::File::create_new(&path).expect("create");
1023
1024 watcher.watch_recursively(&tmpdir);
1025 std::fs::write(&path, b"123").expect("write");
1026
1027 rx.wait_unordered([expected(path).modify()]);
1028 }
1029
1030 #[test]
1031 fn remove_file() {
1032 let tmpdir = testdir();
1033 let (mut watcher, mut rx) = recommended_channel();
1034
1035 let path = tmpdir.path().join("entry");
1036 std::fs::File::create_new(&path).expect("create");
1037
1038 watcher.watch_recursively(&tmpdir);
1039 std::fs::remove_file(&path).expect("remove");
1040
1041 rx.wait_unordered([expected(path).remove()]);
1042 }
1043
1044 #[cfg(feature = "futures")]
1045 #[tokio::test]
1046 async fn futures_unbounded_sender_as_handler() {
1047 use crate::recommended_watcher;
1048 use futures::StreamExt;
1049
1050 let tmpdir = testdir();
1051 let (tx, mut rx) = futures::channel::mpsc::unbounded();
1052 let mut watcher = recommended_watcher(tx).unwrap();
1053 watcher
1054 .watch(tmpdir.path(), RecursiveMode::NonRecursive)
1055 .unwrap();
1056
1057 std::fs::create_dir(tmpdir.path().join("1")).unwrap();
1058
1059 tokio::time::timeout(Duration::from_secs(5), rx.next())
1060 .await
1061 .expect("timeout")
1062 .expect("No event")
1063 .expect("Error");
1064 }
1065
1066 #[cfg(feature = "tokio")]
1067 #[tokio::test]
1068 async fn tokio_unbounded_sender_as_handler() {
1069 use crate::recommended_watcher;
1070
1071 let tmpdir = testdir();
1072 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
1073 let mut watcher = recommended_watcher(tx).unwrap();
1074 watcher
1075 .watch(tmpdir.path(), RecursiveMode::NonRecursive)
1076 .unwrap();
1077
1078 std::fs::create_dir(tmpdir.path().join("1")).unwrap();
1079
1080 tokio::time::timeout(Duration::from_secs(5), rx.recv())
1081 .await
1082 .expect("timeout")
1083 .expect("No event")
1084 .expect("Error");
1085 }
1086
1087 #[test]
1088 fn update_paths_error_contains_errored_path() {
1089 let err = super::update_paths(
1090 [
1091 PathOp::unwatch("1"),
1092 PathOp::unwatch("2"),
1093 PathOp::unwatch("3"),
1094 ]
1095 .into(),
1096 |op| {
1097 if op.as_path() == Path::new("2") {
1098 Err((op, super::Error::path_not_found()))
1099 } else {
1100 Ok(())
1101 }
1102 },
1103 )
1104 .unwrap_err();
1105 assert_eq!(
1106 &err.into_iter().map(PathOp::into_path).collect::<Vec<_>>(),
1107 &[PathBuf::from("2"), PathBuf::from("3"),]
1108 )
1109 }
1110}