use std::io::Write;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use edamame::watcher::{Debouncer, FileWatcher, NotifyWatcher, WatchedChange, WatchedEvent};
fn expect_change(ev: WatchedEvent) -> WatchedChange {
match ev {
WatchedEvent::Change(c) => c,
WatchedEvent::Removed { path } => {
panic!("expected Change, got Removed on {}", path.display())
}
WatchedEvent::ReadError { path, error } => {
panic!(
"expected Change, got ReadError on {}: {error}",
path.display()
)
}
}
}
#[test]
fn debouncer_window_default_is_idle() {
let d = Debouncer::new(Duration::from_millis(200));
assert!(d.deadline().is_none());
}
#[test]
fn debouncer_record_then_fire() {
let mut d = Debouncer::new(Duration::from_millis(200));
let t0 = Instant::now();
d.record(t0);
assert_eq!(d.deadline(), Some(t0 + Duration::from_millis(200)));
assert!(!d.fire_if_due(t0 + Duration::from_millis(150)));
assert!(d.fire_if_due(t0 + Duration::from_millis(200)));
assert!(d.deadline().is_none(), "deadline cleared after firing");
}
#[test]
fn debouncer_burst_extends_deadline() {
let mut d = Debouncer::new(Duration::from_millis(200));
let t0 = Instant::now();
d.record(t0);
let t1 = t0 + Duration::from_millis(120);
d.record(t1);
assert!(!d.fire_if_due(t0 + Duration::from_millis(200)));
assert!(d.fire_if_due(t1 + Duration::from_millis(200)));
}
#[test]
#[ignore = "requires live filesystem notifications (inotify/FSEvents)"]
fn notify_watcher_emits_debounced_change_on_external_write() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("file.md");
std::fs::write(&path, "initial").expect("seed");
let (tx, rx) = mpsc::channel::<WatchedEvent>();
let mut w = NotifyWatcher::new(tx).expect("build watcher");
w.watch(&path).expect("watch");
std::thread::sleep(Duration::from_millis(80));
let mut f = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&path)
.expect("open");
write!(f, "updated").expect("write");
drop(f);
let change = expect_change(
rx.recv_timeout(Duration::from_millis(1500))
.expect("expected a debounced change"),
);
assert_eq!(change.path, path);
assert_eq!(change.contents, "updated");
}
#[test]
fn force_reconcile_emits_change_synchronously() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("file.md");
std::fs::write(&path, "alpha").expect("seed");
let (tx, rx) = mpsc::channel::<WatchedEvent>();
let mut w = NotifyWatcher::new(tx).expect("build watcher");
w.watch(&path).expect("watch");
std::thread::sleep(Duration::from_millis(80));
while rx.try_recv().is_ok() {}
std::fs::write(&path, "beta").expect("rewrite");
w.force_reconcile().expect("reconcile");
let change = expect_change(
rx.recv_timeout(Duration::from_millis(500))
.expect("forced reconcile must deliver promptly"),
);
assert_eq!(change.contents, "beta");
}
#[test]
fn unwatch_stops_event_delivery() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("file.md");
std::fs::write(&path, "x").expect("seed");
let (tx, rx) = mpsc::channel::<WatchedEvent>();
let mut w = NotifyWatcher::new(tx).expect("build watcher");
w.watch(&path).expect("watch");
std::thread::sleep(Duration::from_millis(80));
while rx.try_recv().is_ok() {}
w.unwatch().expect("unwatch");
std::thread::sleep(Duration::from_millis(20));
std::fs::write(&path, "y").expect("rewrite");
std::thread::sleep(Duration::from_millis(400));
assert!(
rx.try_recv().is_err(),
"no change should be delivered after unwatch",
);
}
#[test]
#[ignore = "requires live filesystem notifications (inotify/FSEvents)"]
fn rewatching_a_different_file_redirects_events() {
let dir = tempfile::tempdir().expect("tempdir");
let a = dir.path().join("a.md");
let b = dir.path().join("b.md");
std::fs::write(&a, "aa").expect("seed a");
std::fs::write(&b, "bb").expect("seed b");
let (tx, rx) = mpsc::channel::<WatchedEvent>();
let mut w = NotifyWatcher::new(tx).expect("build watcher");
w.watch(&a).expect("watch a");
std::thread::sleep(Duration::from_millis(80));
while rx.try_recv().is_ok() {}
w.watch(&b).expect("swap to b");
std::thread::sleep(Duration::from_millis(80));
while rx.try_recv().is_ok() {}
std::fs::write(&a, "aa-changed").expect("write a");
std::thread::sleep(Duration::from_millis(400));
assert!(rx.try_recv().is_err(), "old-file events must be filtered");
std::fs::write(&b, "bb-changed").expect("write b");
let change = expect_change(
rx.recv_timeout(Duration::from_millis(1500))
.expect("new-file change must be delivered"),
);
assert_eq!(change.path, b);
assert_eq!(change.contents, "bb-changed");
}