Skip to main content

notify_debouncer_full/
lib.rs

1//! A debouncer for [notify] that is optimized for ease of use.
2//!
3//! * Only emits a single `Rename` event if the rename `From` and `To` events can be matched
4//! * Merges multiple `Rename` events
5//! * Takes `Rename` events into account and updates paths for events that occurred before the rename event, but which haven't been emitted, yet
6//! * Optionally keeps track of the file system IDs all files and stitches rename events together (macOS FS Events, Windows)
7//! * Emits only one `Remove` event when deleting a directory (inotify)
8//! * Doesn't emit duplicate create events
9//! * Doesn't emit `Modify` events after a `Create` event
10//!
11//! # Installation
12//!
13//! ```toml
14//! [dependencies]
15//! notify-debouncer-full = "0.7.0"
16//! ```
17//!
18//! In case you want to select specific features of notify,
19//! specify notify as dependency explicitly in your dependencies.
20//! Otherwise you can just use the re-export of notify from debouncer-full.
21//!
22//! ```toml
23//! notify-debouncer-full = "0.7.0"
24//! notify = { version = "..", features = [".."] }
25//! ```
26//!
27//! # Examples
28//!
29//! ```rust,no_run
30//! # use std::path::Path;
31//! # use std::time::Duration;
32//! use notify_debouncer_full::{notify::*, new_debouncer, DebounceEventResult};
33//!
34//! // Select recommended watcher for debouncer.
35//! // Using a callback here, could also be a channel.
36//! let mut debouncer = new_debouncer(Duration::from_secs(2), None, |result: DebounceEventResult| {
37//!     match result {
38//!         Ok(events) => events.iter().for_each(|event| println!("{event:?}")),
39//!         Err(errors) => errors.iter().for_each(|error| println!("{error:?}")),
40//!     }
41//! }).unwrap();
42//!
43//! // Add a path to be watched. All files and directories at that path and
44//! // below will be monitored for changes.
45//! debouncer.watch(".", RecursiveMode::Recursive).unwrap();
46//! ```
47//!
48//! # Features
49//!
50//! The following crate features can be turned on or off in your cargo dependency config:
51//!
52//! - `serde` passed down to notify-types, off by default
53//! - `web-time` passed down to notify-types, off by default
54//! - `crossbeam-channel` passed down to notify, off by default
55//! - `flume` passed down to notify, off by default
56//! - `macos_fsevent` passed down to notify, off by default
57//! - `macos_kqueue` passed down to notify, off by default
58//! - `serialization-compat-6` passed down to notify, off by default
59//!
60//! # Caveats
61//!
62//! As all file events are sourced from notify, the [known problems](https://docs.rs/notify/latest/notify/#known-problems) section applies here too.
63
64mod cache;
65mod time;
66
67#[cfg(test)]
68mod testing;
69
70#[cfg(not(target_family = "wasm"))]
71mod file_id_map;
72
73use std::{
74    cmp::Reverse,
75    collections::{BinaryHeap, HashMap, VecDeque},
76    path::{Path, PathBuf},
77    sync::{
78        atomic::{AtomicBool, Ordering},
79        Arc, Mutex,
80    },
81    time::{Duration, Instant},
82};
83
84use time::now;
85
86pub use cache::{FileIdCache, NoCache, RecommendedCache};
87
88#[cfg(not(target_family = "wasm"))]
89pub use file_id_map::FileIdMap;
90
91pub use file_id;
92pub use notify;
93pub use notify_types::debouncer_full::DebouncedEvent;
94
95use file_id::FileId;
96use notify::{
97    event::{ModifyKind, RemoveKind, RenameMode},
98    Error, ErrorKind, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, WatcherKind,
99};
100
101/// The set of requirements for watcher debounce event handling functions.
102///
103/// # Example implementation
104///
105/// ```rust,no_run
106/// # use notify::{Event, Result, EventHandler};
107/// # use notify_debouncer_full::{DebounceEventHandler, DebounceEventResult};
108///
109/// /// Prints received events
110/// struct EventPrinter;
111///
112/// impl DebounceEventHandler for EventPrinter {
113///     fn handle_event(&mut self, result: DebounceEventResult) {
114///         match result {
115///             Ok(events) => events.iter().for_each(|event| println!("{event:?}")),
116///             Err(errors) => errors.iter().for_each(|error| println!("{error:?}")),
117///         }
118///     }
119/// }
120/// ```
121pub trait DebounceEventHandler: Send + 'static {
122    /// Handles an event.
123    fn handle_event(&mut self, event: DebounceEventResult);
124}
125
126impl<F> DebounceEventHandler for F
127where
128    F: FnMut(DebounceEventResult) + Send + 'static,
129{
130    fn handle_event(&mut self, event: DebounceEventResult) {
131        (self)(event);
132    }
133}
134
135#[cfg(feature = "crossbeam-channel")]
136impl DebounceEventHandler for crossbeam_channel::Sender<DebounceEventResult> {
137    fn handle_event(&mut self, event: DebounceEventResult) {
138        let _ = self.send(event);
139    }
140}
141
142#[cfg(feature = "flume")]
143impl DebounceEventHandler for flume::Sender<DebounceEventResult> {
144    fn handle_event(&mut self, event: DebounceEventResult) {
145        let _ = self.send(event);
146    }
147}
148
149impl DebounceEventHandler for std::sync::mpsc::Sender<DebounceEventResult> {
150    fn handle_event(&mut self, event: DebounceEventResult) {
151        let _ = self.send(event);
152    }
153}
154
155/// A result of debounced events.
156/// Comes with either a vec of events or vec of errors.
157pub type DebounceEventResult = Result<Vec<DebouncedEvent>, Vec<Error>>;
158
159type DebounceData<T> = Arc<Mutex<DebounceDataInner<T>>>;
160
161#[derive(Debug, Clone, Default, PartialEq, Eq)]
162struct Queue {
163    /// Events must be stored in the following order:
164    /// 1. `remove` or `move out` event
165    /// 2. `rename` event
166    /// 3. Other events
167    events: VecDeque<DebouncedEvent>,
168}
169
170impl Queue {
171    fn was_created(&self) -> bool {
172        self.events.front().is_some_and(|event| {
173            matches!(
174                event.kind,
175                EventKind::Create(_) | EventKind::Modify(ModifyKind::Name(RenameMode::To))
176            )
177        })
178    }
179
180    fn was_removed(&self) -> bool {
181        self.events.front().is_some_and(|event| {
182            matches!(
183                event.kind,
184                EventKind::Remove(_) | EventKind::Modify(ModifyKind::Name(RenameMode::From))
185            )
186        })
187    }
188}
189
190#[derive(Debug)]
191pub(crate) struct DebounceDataInner<T> {
192    queues: HashMap<PathBuf, Queue>,
193    roots: Vec<(PathBuf, RecursiveMode)>,
194    cache: T,
195    rename_event: Option<(DebouncedEvent, Option<FileId>)>,
196    rescan_event: Option<DebouncedEvent>,
197    errors: Vec<Error>,
198    timeout: Duration,
199}
200
201impl<T: FileIdCache> DebounceDataInner<T> {
202    pub(crate) fn new(cache: T, timeout: Duration) -> Self {
203        Self {
204            queues: HashMap::new(),
205            roots: Vec::new(),
206            cache,
207            rename_event: None,
208            rescan_event: None,
209            errors: Vec::new(),
210            timeout,
211        }
212    }
213
214    /// Retrieve a vec of debounced events, removing them if not continuous
215    pub fn debounced_events(&mut self) -> Vec<DebouncedEvent> {
216        let now = now();
217        let mut events_expired = Vec::with_capacity(self.queues.len());
218        let mut queues_remaining = HashMap::with_capacity(self.queues.len());
219
220        if let Some(event) = self.rescan_event.take() {
221            if now.saturating_duration_since(event.time) >= self.timeout {
222                log::trace!("debounced event: {event:?}");
223                events_expired.push(event);
224            } else {
225                self.rescan_event = Some(event);
226            }
227        }
228
229        // drain the entire queue, then process the expired events and re-add the rest
230        // TODO: perfect fit for drain_filter https://github.com/rust-lang/rust/issues/59618
231        for (path, mut queue) in self.queues.drain() {
232            let mut kind_index = HashMap::new();
233
234            while let Some(event) = queue.events.pop_front() {
235                // remove previous event of the same kind
236                if let Some(idx) = kind_index.get(&event.kind).copied() {
237                    events_expired.remove(idx);
238
239                    kind_index.values_mut().for_each(|i| {
240                        if *i > idx {
241                            *i -= 1
242                        }
243                    })
244                }
245
246                if now.saturating_duration_since(event.time) >= self.timeout {
247                    kind_index.insert(event.kind, events_expired.len());
248
249                    events_expired.push(event);
250                } else {
251                    queue.events.push_front(event);
252                    break;
253                }
254            }
255
256            if !queue.events.is_empty() {
257                queues_remaining.insert(path, queue);
258            }
259        }
260
261        self.queues = queues_remaining;
262
263        sort_events(events_expired)
264    }
265
266    /// Returns all currently stored errors
267    pub fn errors(&mut self) -> Vec<Error> {
268        std::mem::take(&mut self.errors)
269    }
270
271    /// Add an error entry to re-send later on
272    pub fn add_error(&mut self, error: Error) {
273        log::trace!("raw error: {error:?}");
274
275        self.errors.push(error);
276    }
277
278    /// Add new event to debouncer cache
279    pub fn add_event(&mut self, event: Event) {
280        log::trace!("raw event: {event:?}");
281
282        if event.need_rescan() {
283            self.cache.rescan(&self.roots);
284            self.rescan_event = Some(DebouncedEvent { event, time: now() });
285            return;
286        }
287
288        let path = match event.paths.first() {
289            Some(path) => path,
290            None => {
291                log::info!("skipping event with no paths: {event:?}");
292                return;
293            }
294        };
295
296        match &event.kind {
297            EventKind::Create(_) => {
298                let recursive_mode = self.recursive_mode(path);
299
300                self.cache.add_path(path, recursive_mode);
301
302                self.push_event(event, now());
303            }
304            EventKind::Modify(ModifyKind::Name(rename_mode)) => {
305                match rename_mode {
306                    RenameMode::Any => {
307                        if event.paths[0].exists() {
308                            self.handle_rename_to(event);
309                        } else {
310                            self.handle_rename_from(event);
311                        }
312                    }
313                    RenameMode::To => {
314                        self.handle_rename_to(event);
315                    }
316                    RenameMode::From => {
317                        self.handle_rename_from(event);
318                    }
319                    RenameMode::Both => {
320                        // ignore and handle `To` and `From` events instead
321                    }
322                    RenameMode::Other => {
323                        // unused
324                    }
325                }
326            }
327            EventKind::Remove(_) => {
328                self.push_remove_event(event, now());
329            }
330            EventKind::Other => {
331                // ignore meta events
332            }
333            _ => {
334                if self.cache.cached_file_id(path).is_none() {
335                    let recursive_mode = self.recursive_mode(path);
336
337                    self.cache.add_path(path, recursive_mode);
338                }
339
340                self.push_event(event, now());
341            }
342        }
343    }
344
345    fn recursive_mode(&mut self, path: &Path) -> RecursiveMode {
346        self.roots
347            .iter()
348            .find_map(|(root, recursive_mode)| {
349                if path.starts_with(root) {
350                    Some(*recursive_mode)
351                } else {
352                    None
353                }
354            })
355            .unwrap_or(RecursiveMode::NonRecursive)
356    }
357
358    fn handle_rename_from(&mut self, event: Event) {
359        let time = now();
360        let path = &event.paths[0];
361
362        // store event
363        let file_id = self.cache.cached_file_id(path).map(|id| *id.as_ref());
364        self.rename_event = Some((DebouncedEvent::new(event.clone(), time), file_id));
365
366        self.cache.remove_path(path);
367
368        self.push_event(event, time);
369    }
370
371    fn handle_rename_to(&mut self, event: Event) {
372        let recursive_mode = self.recursive_mode(&event.paths[0]);
373
374        self.cache.add_path(&event.paths[0], recursive_mode);
375
376        let trackers_match = self
377            .rename_event
378            .as_ref()
379            .and_then(|(e, _)| e.tracker())
380            .and_then(|from_tracker| {
381                event
382                    .attrs
383                    .tracker()
384                    .map(|to_tracker| from_tracker == to_tracker)
385            })
386            .unwrap_or_default();
387
388        let file_ids_match = self
389            .rename_event
390            .as_ref()
391            .and_then(|(_, id)| id.as_ref())
392            .and_then(|from_file_id| {
393                self.cache
394                    .cached_file_id(&event.paths[0])
395                    .map(|to_file_id| from_file_id == to_file_id.as_ref())
396            })
397            .unwrap_or_default();
398
399        if trackers_match || file_ids_match {
400            // connect rename
401            let (mut rename_event, _) = self.rename_event.take().unwrap(); // unwrap is safe because `rename_event` must be set at this point
402            let path = rename_event.paths.remove(0);
403            let time = rename_event.time;
404            self.push_rename_event(path, event, time);
405        } else {
406            // move in
407            self.push_event(event, now());
408        }
409
410        self.rename_event = None;
411    }
412
413    fn push_rename_event(&mut self, path: PathBuf, event: Event, time: Instant) {
414        self.cache.remove_path(&path);
415
416        let mut source_queue = self.queues.remove(&path).unwrap_or_default();
417
418        // remove rename `from` event
419        source_queue.events.pop_back();
420
421        // remove existing rename event
422        let (remove_index, original_path, original_time) = source_queue
423            .events
424            .iter()
425            .enumerate()
426            .find_map(|(index, e)| {
427                if matches!(
428                    e.kind,
429                    EventKind::Modify(ModifyKind::Name(RenameMode::Both))
430                ) {
431                    Some((Some(index), e.paths[0].clone(), e.time))
432                } else {
433                    None
434                }
435            })
436            .unwrap_or((None, path, time));
437
438        if let Some(remove_index) = remove_index {
439            source_queue.events.remove(remove_index);
440        }
441
442        // split off remove or move out event and add it back to the events map
443        if source_queue.was_removed() {
444            let event = source_queue.events.pop_front().unwrap();
445
446            self.queues.insert(
447                event.paths[0].clone(),
448                Queue {
449                    events: [event].into(),
450                },
451            );
452        }
453
454        // update paths
455        for e in &mut source_queue.events {
456            e.paths = vec![event.paths[0].clone()];
457        }
458
459        // insert rename event at the front, unless the file was just created
460        if !source_queue.was_created() {
461            source_queue.events.push_front(DebouncedEvent {
462                event: Event {
463                    kind: EventKind::Modify(ModifyKind::Name(RenameMode::Both)),
464                    paths: vec![original_path, event.paths[0].clone()],
465                    attrs: event.attrs,
466                },
467                time: original_time,
468            });
469        }
470
471        if let Some(target_queue) = self.queues.get_mut(&event.paths[0]) {
472            if !target_queue.was_created() {
473                let mut remove_event = DebouncedEvent {
474                    event: Event {
475                        kind: EventKind::Remove(RemoveKind::Any),
476                        paths: vec![event.paths[0].clone()],
477                        attrs: Default::default(),
478                    },
479                    time: original_time,
480                };
481                if !target_queue.was_removed() {
482                    remove_event.event = remove_event.event.set_info("override");
483                }
484                source_queue.events.push_front(remove_event);
485            }
486            *target_queue = source_queue;
487        } else {
488            self.queues.insert(event.paths[0].clone(), source_queue);
489        }
490    }
491
492    fn push_remove_event(&mut self, event: Event, time: Instant) {
493        let path = &event.paths[0];
494
495        // remove child queues
496        self.queues.retain(|p, _| !p.starts_with(path) || p == path);
497
498        // remove cached file ids
499        self.cache.remove_path(path);
500
501        match self.queues.get_mut(path) {
502            Some(queue) if queue.was_created() => {
503                self.queues.remove(path);
504            }
505            Some(queue) => {
506                queue.events = [DebouncedEvent::new(event, time)].into();
507            }
508            None => {
509                self.push_event(event, time);
510            }
511        }
512    }
513
514    fn push_event(&mut self, event: Event, time: Instant) {
515        let path = &event.paths[0];
516
517        if let Some(queue) = self.queues.get_mut(path) {
518            // Skip duplicate create events and modifications right after creation.
519            // This code relies on backends never emitting a `Modify` event with kind other than `Name` for a rename event.
520            if match event.kind {
521                EventKind::Modify(
522                    ModifyKind::Any
523                    | ModifyKind::Data(_)
524                    | ModifyKind::Metadata(_)
525                    | ModifyKind::Other,
526                )
527                | EventKind::Create(_) => !queue.was_created(),
528                _ => true,
529            } {
530                queue.events.push_back(DebouncedEvent::new(event, time));
531            }
532        } else {
533            self.queues.insert(
534                path.to_path_buf(),
535                Queue {
536                    events: [DebouncedEvent::new(event, time)].into(),
537                },
538            );
539        }
540    }
541}
542
543/// Debouncer guard, stops the debouncer on drop.
544#[derive(Debug)]
545pub struct Debouncer<T: Watcher, C: FileIdCache> {
546    watcher: T,
547    debouncer_thread: Option<std::thread::JoinHandle<()>>,
548    data: DebounceData<C>,
549    stop: Arc<AtomicBool>,
550}
551
552impl<T: Watcher, C: FileIdCache> Debouncer<T, C> {
553    /// Stop the debouncer, waits for the event thread to finish.
554    /// May block for the duration of one `tick_rate`.
555    pub fn stop(mut self) {
556        self.set_stop();
557        if let Some(t) = self.debouncer_thread.take() {
558            let _ = t.join();
559        }
560    }
561
562    /// Stop the debouncer, does not wait for the event thread to finish.
563    pub fn stop_nonblocking(self) {
564        self.set_stop();
565    }
566
567    fn set_stop(&self) {
568        self.stop.store(true, Ordering::Relaxed);
569    }
570
571    #[deprecated = "`Debouncer` provides all methods from `Watcher` itself now. Remove `.watcher()` and use those methods directly."]
572    pub fn watcher(&mut self) {}
573
574    #[deprecated = "`Debouncer` now manages root paths automatically. Remove all calls to `add_root` and `remove_root`."]
575    pub fn cache(&mut self) {}
576
577    fn add_root(&mut self, path: impl Into<PathBuf>, recursive_mode: RecursiveMode) {
578        let path = path.into();
579
580        let mut data = self.data.lock().unwrap();
581
582        // skip, if the root has already been added
583        if data.roots.iter().any(|(p, _)| p == &path) {
584            return;
585        }
586
587        data.roots.push((path.clone(), recursive_mode));
588
589        data.cache.add_path(&path, recursive_mode);
590    }
591
592    fn remove_root(&mut self, path: impl AsRef<Path>) {
593        let mut data = self.data.lock().unwrap();
594
595        data.roots.retain(|(root, _)| !root.starts_with(&path));
596
597        data.cache.remove_path(path.as_ref());
598    }
599
600    pub fn watch(
601        &mut self,
602        path: impl AsRef<Path>,
603        recursive_mode: RecursiveMode,
604    ) -> notify::Result<()> {
605        self.watcher.watch(path.as_ref(), recursive_mode)?;
606        self.add_root(path.as_ref(), recursive_mode);
607        Ok(())
608    }
609
610    pub fn unwatch(&mut self, path: impl AsRef<Path>) -> notify::Result<()> {
611        self.watcher.unwatch(path.as_ref())?;
612        self.remove_root(path);
613        Ok(())
614    }
615
616    pub fn configure(&mut self, option: notify::Config) -> notify::Result<bool> {
617        self.watcher.configure(option)
618    }
619
620    pub fn kind() -> WatcherKind
621    where
622        Self: Sized,
623    {
624        T::kind()
625    }
626}
627
628impl<T: Watcher, C: FileIdCache> Drop for Debouncer<T, C> {
629    fn drop(&mut self) {
630        self.set_stop();
631    }
632}
633
634/// Creates a new debounced watcher with custom configuration.
635///
636/// Timeout is the amount of time after which a debounced event is emitted.
637///
638/// If `tick_rate` is `None`, notify will select a tick rate that is 1/4 of the provided timeout.
639pub fn new_debouncer_opt<F: DebounceEventHandler, T: Watcher, C: FileIdCache + Send + 'static>(
640    timeout: Duration,
641    tick_rate: Option<Duration>,
642    mut event_handler: F,
643    file_id_cache: C,
644    config: notify::Config,
645) -> Result<Debouncer<T, C>, Error> {
646    let data = Arc::new(Mutex::new(DebounceDataInner::new(file_id_cache, timeout)));
647    let stop = Arc::new(AtomicBool::new(false));
648
649    let tick_div = 4;
650    let tick = match tick_rate {
651        Some(v) => {
652            if v > timeout {
653                return Err(Error::new(ErrorKind::Generic(format!(
654                    "Invalid tick_rate, tick rate {v:?} > {timeout:?} timeout!"
655                ))));
656            }
657            v
658        }
659        None => timeout.checked_div(tick_div).ok_or_else(|| {
660            Error::new(ErrorKind::Generic(format!(
661                "Failed to calculate tick as {timeout:?}/{tick_div}!"
662            )))
663        })?,
664    };
665
666    let data_c = data.clone();
667    let stop_c = stop.clone();
668    let thread = std::thread::Builder::new()
669        .name("notify-rs debouncer loop".to_string())
670        .spawn(move || loop {
671            if stop_c.load(Ordering::Acquire) {
672                break;
673            }
674            std::thread::sleep(tick);
675            let send_data;
676            let errors;
677            {
678                let mut lock = data_c.lock().unwrap();
679                send_data = lock.debounced_events();
680                errors = lock.errors();
681            }
682            if !send_data.is_empty() {
683                event_handler.handle_event(Ok(send_data));
684            }
685            if !errors.is_empty() {
686                event_handler.handle_event(Err(errors));
687            }
688        })?;
689
690    let data_c = data.clone();
691    let watcher = T::new(
692        move |e: Result<Event, Error>| {
693            let mut lock = data_c.lock().unwrap();
694
695            match e {
696                Ok(e) => lock.add_event(e),
697                // can't have multiple TX, so we need to pipe that through our debouncer
698                Err(e) => lock.add_error(e),
699            }
700        },
701        config,
702    )?;
703
704    let guard = Debouncer {
705        watcher,
706        debouncer_thread: Some(thread),
707        data,
708        stop,
709    };
710
711    Ok(guard)
712}
713
714/// Short function to create a new debounced watcher with the recommended debouncer and the built-in file ID cache.
715///
716/// Timeout is the amount of time after which a debounced event is emitted.
717///
718/// If `tick_rate` is `None`, notify will select a tick rate that is 1/4 of the provided timeout.
719pub fn new_debouncer<F: DebounceEventHandler>(
720    timeout: Duration,
721    tick_rate: Option<Duration>,
722    event_handler: F,
723) -> Result<Debouncer<RecommendedWatcher, RecommendedCache>, Error> {
724    new_debouncer_opt::<F, RecommendedWatcher, RecommendedCache>(
725        timeout,
726        tick_rate,
727        event_handler,
728        RecommendedCache::new(),
729        notify::Config::default(),
730    )
731}
732
733fn sort_events(events: Vec<DebouncedEvent>) -> Vec<DebouncedEvent> {
734    let mut sorted = Vec::with_capacity(events.len());
735
736    // group events by path
737    let mut events_by_path: HashMap<_, VecDeque<_>> =
738        events.into_iter().fold(HashMap::new(), |mut acc, event| {
739            acc.entry(event.paths.last().cloned().unwrap_or_default())
740                .or_default()
741                .push_back(event);
742            acc
743        });
744
745    // push events for different paths in chronological order and keep the order of events with the same path
746
747    let mut min_time_heap = events_by_path
748        .iter()
749        .map(|(path, events)| Reverse((events[0].time, path.clone())))
750        .collect::<BinaryHeap<_>>();
751
752    while let Some(Reverse((min_time, path))) = min_time_heap.pop() {
753        // unwrap is safe because only paths from `events_by_path` are added to `min_time_heap`
754        // and they are never removed from `events_by_path`.
755        let events = events_by_path.get_mut(&path).unwrap();
756
757        let mut push_next = false;
758
759        while events.front().is_some_and(|event| event.time <= min_time) {
760            // unwrap is safe because `pop_front` mus return some in order to enter the loop
761            let event = events.pop_front().unwrap();
762            sorted.push(event);
763            push_next = true;
764        }
765
766        if push_next {
767            if let Some(event) = events.front() {
768                min_time_heap.push(Reverse((event.time, path)));
769            }
770        }
771    }
772
773    sorted
774}
775
776#[cfg(test)]
777mod tests {
778    use std::{fs, path::Path};
779
780    use super::*;
781
782    use pretty_assertions::assert_eq;
783    use rstest::rstest;
784    use tempfile::tempdir;
785    use testing::TestCase;
786    use time::MockTime;
787
788    #[rstest]
789    fn state(
790        #[values(
791            "add_create_event",
792            "add_create_event_after_remove_event",
793            "add_create_dir_event_twice",
794            "add_event_with_no_paths_is_ok",
795            "add_modify_any_event_after_create_event",
796            "add_modify_content_event_after_create_event",
797            "add_rename_from_event",
798            "add_rename_from_event_after_create_event",
799            "add_rename_from_event_after_modify_event",
800            "add_rename_from_event_after_create_and_modify_event",
801            "add_rename_from_event_after_rename_from_event",
802            "add_rename_to_event",
803            "add_rename_to_dir_event",
804            "add_rename_from_and_to_event",
805            "add_rename_from_and_to_event_after_create",
806            "add_rename_from_and_to_event_after_rename",
807            "add_rename_from_and_to_event_after_modify_content",
808            "add_rename_from_and_to_event_override_created",
809            "add_rename_from_and_to_event_override_modified",
810            "add_rename_from_and_to_event_override_removed",
811            "add_rename_from_and_to_event_with_file_ids",
812            "add_rename_from_and_to_event_with_different_file_ids",
813            "add_rename_from_and_to_event_with_different_tracker",
814            "add_rename_both_event",
815            "add_remove_event",
816            "add_remove_event_after_create_event",
817            "add_remove_event_after_modify_event",
818            "add_remove_event_after_create_and_modify_event",
819            "add_remove_parent_event_after_remove_child_event",
820            "add_errors",
821            "debounce_modify_events",
822            "emit_continuous_modify_content_events",
823            "emit_events_in_chronological_order",
824            "emit_events_with_a_prepended_rename_event",
825            "emit_close_events_only_once",
826            "emit_modify_event_after_close_event",
827            "emit_needs_rescan_event",
828            "read_file_id_without_create_event",
829            "sort_events_chronologically",
830            "sort_events_with_reordering"
831        )]
832        file_name: &str,
833    ) {
834        let file_content =
835            fs::read_to_string(Path::new(&format!("./test_cases/{file_name}.hjson"))).unwrap();
836        let mut test_case = deser_hjson::from_str::<TestCase>(&file_content).unwrap();
837
838        let time = now();
839        MockTime::set_time(time);
840
841        let mut state = test_case.state.into_debounce_data_inner(time);
842        state.roots = vec![(PathBuf::from("/"), RecursiveMode::Recursive)];
843
844        let mut prev_event_time = Duration::default();
845
846        for event in test_case.events {
847            let event_time = Duration::from_millis(event.time);
848            let event = event.into_debounced_event(time, None);
849            MockTime::advance(event_time - prev_event_time);
850            prev_event_time = event_time;
851            state.add_event(event.event);
852        }
853
854        for error in test_case.errors {
855            let error = error.into_notify_error();
856            state.add_error(error);
857        }
858
859        let expected_errors = std::mem::take(&mut test_case.expected.errors);
860        let expected_events = std::mem::take(&mut test_case.expected.events);
861        let expected_state = test_case.expected.into_debounce_data_inner(time);
862        assert_eq!(
863            state.queues, expected_state.queues,
864            "queues not as expected"
865        );
866        assert_eq!(
867            state.rename_event, expected_state.rename_event,
868            "rename event not as expected"
869        );
870        assert_eq!(
871            state.rescan_event, expected_state.rescan_event,
872            "rescan event not as expected"
873        );
874        assert_eq!(
875            state.cache.paths, expected_state.cache.paths,
876            "cache not as expected"
877        );
878
879        assert_eq!(
880            state
881                .errors
882                .iter()
883                .map(|e| format!("{e:?}"))
884                .collect::<Vec<_>>(),
885            expected_errors
886                .iter()
887                .map(|e| format!("{:?}", e.clone().into_notify_error()))
888                .collect::<Vec<_>>(),
889            "errors not as expected"
890        );
891
892        let backup_time = now();
893        let backup_queues = state.queues.clone();
894
895        for (delay, events) in expected_events {
896            MockTime::set_time(backup_time);
897            state.queues = backup_queues.clone();
898
899            match delay.as_str() {
900                "none" => {}
901                "short" => MockTime::advance(Duration::from_millis(10)),
902                "long" => MockTime::advance(Duration::from_millis(100)),
903                _ => {
904                    if let Ok(ts) = delay.parse::<u64>() {
905                        MockTime::set_time(time + Duration::from_millis(ts));
906                    }
907                }
908            }
909
910            let events = events
911                .into_iter()
912                .map(|event| event.into_debounced_event(time, None))
913                .collect::<Vec<_>>();
914
915            assert_eq!(
916                state.debounced_events(),
917                events,
918                "debounced events after a `{delay}` delay"
919            );
920        }
921    }
922
923    #[test]
924    fn integration() -> Result<(), Box<dyn std::error::Error>> {
925        let dir = tempdir()?;
926
927        // set up the watcher
928        let (tx, rx) = std::sync::mpsc::channel();
929        let mut debouncer = new_debouncer(Duration::from_millis(10), None, tx)?;
930        debouncer.watch(dir.path(), RecursiveMode::Recursive)?;
931
932        // create a new file
933        let file_path = dir.path().join("file.txt");
934        fs::write(&file_path, b"Lorem ipsum")?;
935
936        println!("waiting for event at {}", file_path.display());
937
938        // wait for up to 10 seconds for the create event, ignore all other events
939        let deadline = Instant::now() + Duration::from_secs(10);
940        while deadline > Instant::now() {
941            let events = rx
942                .recv_timeout(deadline - Instant::now())
943                .expect("did not receive expected event")
944                .expect("received an error");
945
946            for event in events {
947                if event.event.paths == vec![file_path.clone()]
948                    || event.event.paths == vec![file_path.canonicalize()?]
949                {
950                    return Ok(());
951                }
952
953                println!("unexpected event: {event:?}");
954            }
955        }
956
957        panic!("did not receive expected event");
958    }
959}