Skip to content

Commit

Permalink
Merge pull request #630 from dfaust/remove-mock-instant
Browse files Browse the repository at this point in the history
Remove mock instant
  • Loading branch information
dfaust authored Oct 25, 2024
2 parents 7c26617 + 3bb640f commit 89b0cf8
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 65 deletions.
14 changes: 10 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ v4 commits split out to branch `v4_maintenance` starting with `4.0.16`

## notify 7.0.0 (unreleased)

- CHANGE: raise MSRV to 1.72 [#569] [#610]
- CHANGE: raise MSRV to 1.72 [#569] [#610] **breaking**
- CHANGE: move event type to notify-types crate [#559]
- CHANGE: flatten serialization of events and use camelCase [#558]
- CHANGE: remove internal use of crossbeam-channels [#569] [#610]
- CHANGE: remove internal use of crossbeam channels [#569] [#610]
- CHANGE: rename feature `crossbeam` to `crossbeam-channel` and disable it by default [#610] **breaking**
- CHANGE: upgrade mio to 1.0 [#623]
- CHANGE: add log statements [#499]
- FIX: prevent UB with illegal instruction for the windows backend [#604] [#607]
- FIX: on Linux report deleted directories correctly [#545]
- FEATURE: enable kqueue on iOS [#533]
- MISC: various minor doc updates and fixes [#535] [#536] [#543] [#565] [#592] [#595]
- MISC: update inotify to 0.10 [#547]

[#499]: https://github.com/notify-rs/notify/pull/499
[#533]: https://github.com/notify-rs/notify/pull/533
[#535]: https://github.com/notify-rs/notify/pull/535
[#536]: https://github.com/notify-rs/notify/pull/536
Expand All @@ -43,13 +46,16 @@ v4 commits split out to branch `v4_maintenance` starting with `4.0.16`

## notify-types 1.0.0 (unreleased)

New crate containing public type definitions for the notify and debouncer crates.
New crate containing public type definitions for the notify and debouncer crates. [#559]

- CHANGE: the serialization format for events has been changed to be easier to use in environments like JavaScript;
the old behavior can be restored using the new feature flag `serialization-compat-6` [#558] [#568]
the old behavior can be restored using the new feature flag **breaking**`serialization-compat-6` [#558] [#568]
- CHANGE: use instant crate (which provides an `Instant` type that works in Wasm environments) [#570]

[#558]: https://github.com/notify-rs/notify/pull/558
[#559]: https://github.com/notify-rs/notify/pull/559
[#568]: https://github.com/notify-rs/notify/pull/568
[#570]: https://github.com/notify-rs/notify/pull/570

## debouncer-full 0.4.0 (unreleased)

Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ kqueue = "1.0.8"
libc = "0.2.4"
log = "0.4.17"
mio = { version = "1.0", features = ["os-ext"] }
mock_instant = "0.3.0"
instant = "0.1.12"
nix = "0.27.0"
notify = { path = "notify" }
Expand Down
4 changes: 1 addition & 3 deletions notify-debouncer-full/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ repository.workspace = true
[features]
default = ["macos_fsevent"]
serde = ["notify-types/serde"]
mock_instant = ["dep:mock_instant", "notify-types/mock_instant"]
crossbeam-channel = ["dep:crossbeam-channel", "notify/crossbeam-channel"]
macos_fsevent = ["notify/macos_fsevent"]
macos_kqueue = ["notify/macos_kqueue"]
Expand All @@ -28,12 +27,11 @@ crossbeam-channel = { workspace = true, optional = true }
file-id.workspace = true
walkdir.workspace = true
log.workspace = true
mock_instant = { workspace = true, optional = true }

[dev-dependencies]
notify-debouncer-full = { workspace = true, features = ["mock_instant"] }
pretty_assertions.workspace = true
rstest.workspace = true
serde.workspace = true
deser-hjson.workspace = true
rand.workspace = true
tempfile.workspace = true
83 changes: 53 additions & 30 deletions notify-debouncer-full/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@
//!
//! The following crate features can be turned on or off in your cargo dependency config:
//!
//! - `crossbeam` passed down to notify, off by default
//! - `crossbeam-channel` passed down to notify, off by default
//! - `serialization-compat-6` passed down to notify, off by default
//!
//! # Caveats
//!
//! As all file events are sourced from notify, the [known problems](https://docs.rs/notify/latest/notify/#known-problems) section applies here too.

mod cache;
mod time;

#[cfg(test)]
mod testing;
Expand All @@ -69,9 +70,11 @@ use std::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
time::Duration,
time::{Duration, Instant},
};

use time::now;

pub use cache::{FileIdCache, FileIdMap, NoCache, RecommendedCache};

pub use file_id;
Expand All @@ -84,12 +87,6 @@ use notify::{
Error, ErrorKind, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, WatcherKind,
};

#[cfg(feature = "mock_instant")]
use mock_instant::Instant;

#[cfg(not(feature = "mock_instant"))]
use std::time::Instant;

/// The set of requirements for watcher debounce event handling functions.
///
/// # Example implementation
Expand Down Expand Up @@ -124,7 +121,7 @@ where
}
}

#[cfg(feature = "crossbeam")]
#[cfg(feature = "crossbeam-channel")]
impl DebounceEventHandler for crossbeam_channel::Sender<DebounceEventResult> {
fn handle_event(&mut self, event: DebounceEventResult) {
let _ = self.send(event);
Expand Down Expand Up @@ -198,7 +195,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {

/// Retrieve a vec of debounced events, removing them if not continuous
pub fn debounced_events(&mut self) -> Vec<DebouncedEvent> {
let now = Instant::now();
let now = now();
let mut events_expired = Vec::with_capacity(self.queues.len());
let mut queues_remaining = HashMap::with_capacity(self.queues.len());

Expand All @@ -211,6 +208,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
}
}

// drain the entire queue, then process the expired events and re-add the rest
// TODO: perfect fit for drain_filter https://github.com/rust-lang/rust/issues/59618
for (path, mut queue) in self.queues.drain() {
let mut kind_index = HashMap::new();
Expand Down Expand Up @@ -249,13 +247,13 @@ impl<T: FileIdCache> DebounceDataInner<T> {

/// Returns all currently stored errors
pub fn errors(&mut self) -> Vec<Error> {
let mut v = Vec::new();
std::mem::swap(&mut v, &mut self.errors);
v
std::mem::take(&mut self.errors)
}

/// Add an error entry to re-send later on
pub fn add_error(&mut self, error: Error) {
log::trace!("raw error: {error:?}");

self.errors.push(error);
}

Expand All @@ -265,7 +263,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {

if event.need_rescan() {
self.cache.rescan(&self.roots);
self.rescan_event = Some(event.into());
self.rescan_event = Some(DebouncedEvent { event, time: now() });
return;
}

Expand All @@ -277,7 +275,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {

self.cache.add_path(path, recursive_mode);

self.push_event(event, Instant::now());
self.push_event(event, now());
}
EventKind::Modify(ModifyKind::Name(rename_mode)) => {
match rename_mode {
Expand All @@ -303,7 +301,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
}
}
EventKind::Remove(_) => {
self.push_remove_event(event, Instant::now());
self.push_remove_event(event, now());
}
EventKind::Other => {
// ignore meta events
Expand All @@ -315,7 +313,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
self.cache.add_path(path, recursive_mode);
}

self.push_event(event, Instant::now());
self.push_event(event, now());
}
}
}
Expand All @@ -334,7 +332,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
}

fn handle_rename_from(&mut self, event: Event) {
let time = Instant::now();
let time = now();
let path = &event.paths[0];

// store event
Expand Down Expand Up @@ -382,7 +380,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
self.push_rename_event(path, event, time);
} else {
// move in
self.push_event(event, Instant::now());
self.push_event(event, now());
}

self.rename_event = None;
Expand Down Expand Up @@ -753,10 +751,11 @@ mod tests {

use super::*;

use mock_instant::MockClock;
use pretty_assertions::assert_eq;
use rstest::rstest;
use tempfile::tempdir;
use testing::TestCase;
use time::MockTime;

#[rstest]
fn state(
Expand Down Expand Up @@ -805,16 +804,19 @@ mod tests {
fs::read_to_string(Path::new(&format!("./test_cases/{file_name}.hjson"))).unwrap();
let mut test_case = deser_hjson::from_str::<TestCase>(&file_content).unwrap();

MockClock::set_time(Duration::default());

let time = Instant::now();
let time = now();
MockTime::set_time(time);

let mut state = test_case.state.into_debounce_data_inner(time);
state.roots = vec![(PathBuf::from("/"), RecursiveMode::Recursive)];

let mut prev_event_time = Duration::default();

for event in test_case.events {
let event_time = Duration::from_millis(event.time);
let event = event.into_debounced_event(time, None);
MockClock::set_time(event.time - time);
MockTime::advance(event_time - prev_event_time);
prev_event_time = event_time;
state.add_event(event.event);
}

Expand Down Expand Up @@ -856,21 +858,20 @@ mod tests {
"errors not as expected"
);

let backup_time = Instant::now().duration_since(time);
let backup_time = now();
let backup_queues = state.queues.clone();

for (delay, events) in expected_events {
MockClock::set_time(backup_time);
MockTime::set_time(backup_time);
state.queues = backup_queues.clone();

match delay.as_str() {
"none" => {}
"short" => MockClock::advance(Duration::from_millis(10)),
"long" => MockClock::advance(Duration::from_millis(100)),
"short" => MockTime::advance(Duration::from_millis(10)),
"long" => MockTime::advance(Duration::from_millis(100)),
_ => {
if let Ok(ts) = delay.parse::<u64>() {
let ts = time + Duration::from_millis(ts);
MockClock::set_time(ts - time);
MockTime::set_time(time + Duration::from_millis(ts));
}
}
}
Expand All @@ -887,4 +888,26 @@ mod tests {
);
}
}

#[test]
fn integration() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;

let (tx, rx) = std::sync::mpsc::channel();

let mut debouncer = new_debouncer(Duration::from_millis(10), None, tx)?;

debouncer.watch(dir.path(), RecursiveMode::Recursive)?;

fs::write(dir.path().join("file.txt"), b"Lorem ipsum")?;

let events = rx
.recv_timeout(Duration::from_secs(10))
.expect("no events received")
.expect("received an error");

assert!(!events.is_empty(), "received empty event list");

Ok(())
}
}
3 changes: 1 addition & 2 deletions notify-debouncer-full/src/testing.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::{
collections::{HashMap, VecDeque},
path::{Path, PathBuf},
time::Duration,
time::{Duration, Instant},
};

use file_id::FileId;
use mock_instant::Instant;
use notify::{
event::{
AccessKind, AccessMode, CreateKind, DataChange, Flag, MetadataKind, ModifyKind, RemoveKind,
Expand Down
47 changes: 47 additions & 0 deletions notify-debouncer-full/src/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#[cfg(not(test))]
mod build {
use std::time::Instant;

pub fn now() -> Instant {
Instant::now()
}
}

#[cfg(not(test))]
pub use build::*;

#[cfg(test)]
mod test {
use std::{
sync::Mutex,
time::{Duration, Instant},
};

thread_local! {
static NOW: Mutex<Option<Instant>> = Mutex::new(None);
}

pub fn now() -> Instant {
let time = NOW.with(|now| *now.lock().unwrap());
time.unwrap_or_else(|| Instant::now())
}

pub struct MockTime;

impl MockTime {
pub fn set_time(time: Instant) {
NOW.with(|now| *now.lock().unwrap() = Some(time));
}

pub fn advance(delta: Duration) {
NOW.with(|now| {
if let Some(n) = &mut *now.lock().unwrap() {
*n += delta;
}
});
}
}
}

#[cfg(test)]
pub use test::*;
1 change: 1 addition & 0 deletions notify-debouncer-mini/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ notify.workspace = true
notify-types.workspace = true
crossbeam-channel = { workspace = true, optional = true }
log.workspace = true
tempfile.workspace = true
Loading

0 comments on commit 89b0cf8

Please sign in to comment.