use notify::event::*;
use notify::{Config, EventKindMask, RecommendedWatcher, RecursiveMode, Watcher};
use std::fs;
use std::path::PathBuf;
use std::sync::mpsc;
use std::time::Duration;
fn setup_temp_git_repo() -> (PathBuf, tempfile::TempDir) {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path().canonicalize().unwrap();
std::process::Command::new("git")
.args(["init", "-b", "main"])
.current_dir(&dir)
.output()
.unwrap();
fs::write(dir.join("hello.txt"), "hello\n").unwrap();
fs::create_dir_all(dir.join("src")).unwrap();
fs::write(dir.join("src/main.rs"), "fn main() {}\n").unwrap();
std::process::Command::new("git")
.args(["add", "-A"])
.current_dir(&dir)
.output()
.unwrap();
std::process::Command::new("git")
.args(["commit", "-m", "init"])
.current_dir(&dir)
.output()
.unwrap();
(dir, tmp)
}
#[test]
fn raw_notify_nonrecursive_detects_deletion() {
let (dir, _tmp) = setup_temp_git_repo();
let (tx, rx) = mpsc::channel();
let config = Config::default()
.with_follow_symlinks(false)
.with_event_kinds(EventKindMask::CORE);
let mut watcher = RecommendedWatcher::new(
move |res: notify::Result<Event>| {
if let Ok(ev) = res {
let _ = tx.send(ev);
}
},
config,
)
.unwrap();
watcher
.watch(dir.as_path(), RecursiveMode::NonRecursive)
.unwrap();
std::thread::sleep(Duration::from_millis(500));
while rx.try_recv().is_ok() {}
let file_path = dir.join("testfile.txt");
fs::write(&file_path, "content\n").unwrap();
let mut got_create = false;
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ev) => {
eprintln!(" [create phase] event: {:?} paths={:?}", ev.kind, ev.paths);
if matches!(ev.kind, EventKind::Create(_)) && ev.paths.contains(&file_path) {
got_create = true;
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(_) => break,
}
}
assert!(got_create, "Expected Create event for testfile.txt");
std::thread::sleep(Duration::from_millis(300));
while rx.try_recv().is_ok() {}
fs::remove_file(&file_path).unwrap();
eprintln!(" File deleted: {}", file_path.display());
let mut got_removal_event = false;
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ev) => {
eprintln!(" [delete phase] event: {:?} paths={:?}", ev.kind, ev.paths);
if ev.paths.contains(&file_path) {
got_removal_event = true;
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(_) => break,
}
}
assert!(
got_removal_event,
"Expected some event for deleted testfile.txt but got none within 5s"
);
}
#[test]
fn raw_notify_multi_nonrecursive_detects_deletion() {
let (dir, _tmp) = setup_temp_git_repo();
let (tx, rx) = mpsc::channel();
let config = Config::default()
.with_follow_symlinks(false)
.with_event_kinds(EventKindMask::CORE);
let mut watcher = RecommendedWatcher::new(
move |res: notify::Result<Event>| {
if let Ok(ev) = res {
let _ = tx.send(ev);
}
},
config,
)
.unwrap();
watcher
.watch(dir.as_path(), RecursiveMode::NonRecursive)
.unwrap();
watcher
.watch(&dir.join("src"), RecursiveMode::NonRecursive)
.unwrap();
watcher
.watch(&dir.join(".git"), RecursiveMode::NonRecursive)
.unwrap();
watcher
.watch(&dir.join(".git/refs"), RecursiveMode::Recursive)
.unwrap();
std::thread::sleep(Duration::from_millis(500));
while rx.try_recv().is_ok() {}
let file_path = dir.join("testfile.txt");
fs::write(&file_path, "content\n").unwrap();
let mut got_create = false;
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ev) => {
eprintln!(" [multi-create] event: {:?} paths={:?}", ev.kind, ev.paths);
if matches!(ev.kind, EventKind::Create(_)) && ev.paths.contains(&file_path) {
got_create = true;
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(_) => break,
}
}
assert!(
got_create,
"Expected Create event for testfile.txt with multi-watch"
);
std::thread::sleep(Duration::from_millis(300));
while rx.try_recv().is_ok() {}
fs::remove_file(&file_path).unwrap();
eprintln!(" File deleted: {}", file_path.display());
let mut got_removal_event = false;
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ev) => {
eprintln!(" [multi-delete] event: {:?} paths={:?}", ev.kind, ev.paths);
if ev.paths.contains(&file_path) {
got_removal_event = true;
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(_) => break,
}
}
assert!(
got_removal_event,
"Expected some event for deleted testfile.txt with multi-watch but got none within 5s"
);
}
#[test]
fn debounced_nonrecursive_detects_deletion() {
use notify_debouncer_full::{DebounceEventResult, NoCache, new_debouncer_opt};
let (dir, _tmp) = setup_temp_git_repo();
let (tx, rx) = mpsc::channel();
let config = Config::default()
.with_follow_symlinks(false)
.with_event_kinds(EventKindMask::CORE);
let mut debouncer: notify_debouncer_full::Debouncer<RecommendedWatcher, NoCache> =
new_debouncer_opt(
Duration::from_millis(250),
Some(Duration::from_millis(125)),
move |result: DebounceEventResult| {
if let Ok(events) = result {
for ev in events {
eprintln!(
" [debounced-cb] kind={:?} paths={:?}",
ev.event.kind, ev.event.paths
);
let _ = tx.send(ev);
}
}
},
NoCache::new(),
config,
)
.unwrap();
debouncer
.watch(dir.as_path(), RecursiveMode::NonRecursive)
.unwrap();
debouncer
.watch(&dir.join("src"), RecursiveMode::NonRecursive)
.unwrap();
debouncer
.watch(&dir.join(".git"), RecursiveMode::NonRecursive)
.unwrap();
debouncer
.watch(&dir.join(".git/refs"), RecursiveMode::Recursive)
.unwrap();
std::thread::sleep(Duration::from_secs(1));
while rx.try_recv().is_ok() {}
let file_path = dir.join("testfile.txt");
fs::write(&file_path, "content\n").unwrap();
let mut got_create = false;
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ev) => {
if ev.event.paths.contains(&file_path) {
got_create = true;
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(_) => break,
}
}
assert!(got_create, "Expected Create event via debouncer");
std::thread::sleep(Duration::from_millis(500));
while rx.try_recv().is_ok() {}
fs::remove_file(&file_path).unwrap();
eprintln!(" File deleted: {}", file_path.display());
let mut got_event = false;
let mut event_kind = String::new();
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ev) => {
if ev.event.paths.contains(&file_path) {
event_kind = format!("{:?}", ev.event.kind);
got_event = true;
break;
}
}
Err(mpsc::RecvTimeoutError::Timeout) => continue,
Err(_) => break,
}
}
assert!(
got_event,
"Expected some event for deleted testfile.txt via debouncer but got none within 5s"
);
eprintln!(" Got event kind: {}", event_kind);
}