use super::event::*;
use super::{
Config, Error, ErrorKind, EventHandler, EventKindMask, RecursiveMode, Result, Watcher,
};
use crate::paths::{
WalkRoots, WatchMetadata as Watch, WatchPath, absolute_path, is_preserved_watch_root,
preserved_watch_mode, preserved_watch_roots, recursive_user_watch_ancestor, reported_path,
};
use crate::{Receiver, Sender, unbounded};
use kqueue::{EventData, EventFilter, FilterFlag, Ident};
use std::collections::HashMap;
use std::fs::metadata;
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread;
use walkdir::WalkDir;
const KQUEUE: mio::Token = mio::Token(0);
const MESSAGE: mio::Token = mio::Token(1);
struct EventLoop {
running: bool,
poll: mio::Poll,
event_loop_waker: Arc<mio::Waker>,
event_loop_tx: Sender<EventLoopMsg>,
event_loop_rx: Receiver<EventLoopMsg>,
kqueue: kqueue::Watcher,
event_handler: Box<dyn EventHandler>,
watches: HashMap<PathBuf, Watch>,
follow_symlinks: bool,
event_kinds: EventKindMask,
}
#[derive(Debug)]
pub struct KqueueWatcher {
channel: Sender<EventLoopMsg>,
waker: Arc<mio::Waker>,
}
enum EventLoopMsg {
AddWatch(WatchPath, RecursiveMode, Sender<Result<()>>),
RemoveWatch(PathBuf, Sender<Result<()>>),
GetWatchedPaths(Sender<Vec<(PathBuf, RecursiveMode)>>),
Shutdown,
}
impl EventLoop {
pub fn new(
kqueue: kqueue::Watcher,
event_handler: Box<dyn EventHandler>,
follow_symlinks: bool,
event_kinds: EventKindMask,
) -> Result<Self> {
let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
let poll = mio::Poll::new()?;
let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
let kqueue_fd = kqueue.as_raw_fd();
let mut evented_kqueue = mio::unix::SourceFd(&kqueue_fd);
poll.registry()
.register(&mut evented_kqueue, KQUEUE, mio::Interest::READABLE)?;
let event_loop = EventLoop {
running: true,
poll,
event_loop_waker,
event_loop_tx,
event_loop_rx,
kqueue,
event_handler,
watches: HashMap::new(),
follow_symlinks,
event_kinds,
};
Ok(event_loop)
}
pub fn run(self) {
let _ = thread::Builder::new()
.name("notify-rs kqueue loop".to_string())
.spawn(|| self.event_loop_thread());
}
fn event_loop_thread(mut self) {
let mut events = mio::Events::with_capacity(16);
loop {
match self.poll.poll(&mut events, None) {
Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
}
Err(e) => panic!("poll failed: {e}"),
Ok(()) => {}
}
for event in &events {
self.handle_event(event);
}
if !self.running {
break;
}
}
}
fn handle_event(&mut self, event: &mio::event::Event) {
match event.token() {
MESSAGE => {
self.handle_messages()
}
KQUEUE => {
self.handle_kqueue()
}
_ => unreachable!(),
}
}
fn handle_messages(&mut self) {
while let Ok(msg) = self.event_loop_rx.try_recv() {
match msg {
EventLoopMsg::AddWatch(path, recursive_mode, tx) => {
let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive(), true));
}
EventLoopMsg::RemoveWatch(path, tx) => {
let _ = tx.send(self.remove_watch(path, false));
}
EventLoopMsg::GetWatchedPaths(tx) => {
let _ = tx.send(
self.watches
.iter()
.filter(|(_path, watch)| watch.is_user_watch)
.map(|(_path, watch)| {
(
watch.reported_path.clone(),
if watch.user_is_recursive {
RecursiveMode::Recursive
} else {
RecursiveMode::NonRecursive
},
)
})
.collect(),
);
}
EventLoopMsg::Shutdown => {
self.running = false;
break;
}
}
}
}
fn handle_kqueue(&mut self) {
let mut remove_watches = Vec::new();
let mut refresh_dirs = Vec::new();
let mut pending_events = Vec::new();
while let Some(event) = self.kqueue.poll(None) {
log::trace!("kqueue event: {event:?}");
match event {
kqueue::Event {
data: EventData::Vnode(data),
ident: Ident::Filename(_, path),
} => {
let path = PathBuf::from(path);
let watch = self.watches.get(&path);
let event_path = watch
.map(|watch| watch.reported_path.clone())
.unwrap_or_else(|| path.clone());
let event = match data {
kqueue::Vnode::Delete => {
remove_watches.push((path.clone(), true));
Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(event_path))
}
kqueue::Vnode::Write
if watch.is_some_and(|watch| watch.is_recursive)
&& if self.follow_symlinks {
path.is_dir()
} else {
std::fs::symlink_metadata(&path)
.is_ok_and(|metadata| metadata.is_dir())
} =>
{
refresh_dirs
.push(WatchPath::from_parts(path.clone(), event_path.clone()));
std::fs::read_dir(&path)
.map(|dir| {
dir.filter_map(std::result::Result::ok)
.map(|f| f.path())
.find(|f| !self.watches.contains_key(f))
})
.map(|file| {
if let Some(file) = file {
let reported_file =
reported_path(&path, &event_path, &file);
Event::new(EventKind::Create(if file.is_dir() {
CreateKind::Folder
} else if file.is_file() {
CreateKind::File
} else {
CreateKind::Other
}))
.add_path(reported_file)
} else {
Event::new(EventKind::Modify(ModifyKind::Data(
DataChange::Any,
)))
.add_path(event_path)
}
})
.map_err(Into::into)
}
kqueue::Vnode::Write => Ok(Event::new(EventKind::Modify(
ModifyKind::Data(DataChange::Any),
))
.add_path(event_path)),
kqueue::Vnode::Extend | kqueue::Vnode::Truncate => Ok(Event::new(
EventKind::Modify(ModifyKind::Data(DataChange::Size)),
)
.add_path(event_path)),
kqueue::Vnode::Attrib => Ok(Event::new(EventKind::Modify(
ModifyKind::Metadata(MetadataKind::Any),
))
.add_path(event_path)),
kqueue::Vnode::Link => {
Ok(Event::new(EventKind::Modify(ModifyKind::Any)).add_path(event_path))
}
kqueue::Vnode::Rename => {
remove_watches.push((path.clone(), true));
Ok(
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::Any)))
.add_path(event_path),
)
}
kqueue::Vnode::Revoke => {
remove_watches.push((path.clone(), true));
Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(event_path))
}
#[allow(unreachable_patterns)]
_ => Ok(Event::new(EventKind::Other)),
};
match &event {
Ok(e) if !self.event_kinds.matches(&e.kind) => {
}
_ => pending_events.push(event),
}
}
kqueue::Event { ident: _, data: _ } => unreachable!(),
}
}
for (path, remove_recursive) in remove_watches {
self.remove_watch(path, remove_recursive).ok();
}
let mut add_watches = Vec::new();
for dir in refresh_dirs {
if !self.watches.contains_key(&dir.absolute) {
continue;
}
add_watches.extend(self.untracked_entries(&dir));
}
let watch_count = self.watches.len();
for path in add_watches {
if self.watches.contains_key(&path.absolute) {
continue;
}
self.add_watch_entries(path, true, false).ok();
}
if self.watches.len() > watch_count {
self.kqueue.watch().ok();
}
for event in pending_events {
self.event_handler.handle_event(event);
}
}
fn untracked_entries(&self, dir: &WatchPath) -> Vec<WatchPath> {
let Ok(entries) = std::fs::read_dir(&dir.absolute) else {
return Vec::new();
};
entries
.filter_map(std::result::Result::ok)
.map(|entry| entry.path())
.filter(|path| !self.watches.contains_key(path))
.map(|path| dir.child(path))
.collect()
}
fn add_watch(
&mut self,
path: WatchPath,
is_recursive: bool,
is_user_watch: bool,
) -> Result<()> {
let result = self.add_watch_entries(path, is_recursive, is_user_watch);
self.kqueue.watch()?;
result
}
fn add_watch_entries(
&mut self,
path: WatchPath,
is_recursive: bool,
is_user_watch: bool,
) -> Result<()> {
let path_is_dir = metadata(&path.absolute).map_err(Error::io)?.is_dir();
let requested_is_recursive = is_recursive && path_is_dir;
if is_user_watch {
if let Some(watch) = self
.watches
.get(&path.absolute)
.filter(|watch| watch.is_user_watch)
{
if watch.user_is_recursive == requested_is_recursive
&& watch.reported_path == path.requested
{
return Ok(());
}
let inherited_recursive_root =
if !requested_is_recursive && path_is_dir && watch.is_recursive {
recursive_user_watch_ancestor(&path.absolute, self.watches.iter())
} else {
None
};
let replaced_path = path.absolute.clone();
self.remove_watch(replaced_path.clone(), false)?;
if let Some((ancestor_path, ancestor_reported_path)) = inherited_recursive_root {
let requested =
reported_path(&ancestor_path, &ancestor_reported_path, &replaced_path);
let mut roots =
WalkRoots::new(WatchPath::from_parts(replaced_path.clone(), requested));
for entry in WalkDir::new(&replaced_path)
.follow_links(self.follow_symlinks)
.into_iter()
{
let absolute = match entry {
Ok(entry) => entry.into_path(),
Err(err) if walkdir_error_is_not_found(&err) => continue,
Err(err) => return Err(map_walkdir_error(err)),
};
let path = roots.entry(absolute.clone(), self.watches.get(&absolute));
let result = self.add_single_watch(path, true, false);
if let Err(err) = result {
if !error_is_not_found(&err) {
return Err(err);
}
}
}
}
}
}
if !requested_is_recursive {
self.add_single_watch(path, false, is_user_watch)?;
} else {
let walk = WalkDir::new(&path.absolute).follow_links(self.follow_symlinks);
let mut roots = WalkRoots::new(path);
let mut first = true;
for entry in walk {
let entry = match entry {
Ok(entry) => entry.into_path(),
Err(err) if !first && walkdir_error_is_not_found(&err) => continue,
Err(err) => return Err(map_walkdir_error(err)),
};
let path = roots.entry(entry.clone(), self.watches.get(&entry));
match self.add_single_watch(path, is_recursive, is_user_watch && first) {
Err(err) if !first && error_is_not_found(&err) => {}
result => result?,
}
first = false;
}
}
Ok(())
}
fn add_single_watch(
&mut self,
path: WatchPath,
is_recursive: bool,
is_user_watch: bool,
) -> Result<()> {
let event_filter = EventFilter::EVFILT_VNODE;
let filter_flags = FilterFlag::NOTE_DELETE
| FilterFlag::NOTE_WRITE
| FilterFlag::NOTE_EXTEND
| FilterFlag::NOTE_ATTRIB
| FilterFlag::NOTE_LINK
| FilterFlag::NOTE_RENAME
| FilterFlag::NOTE_REVOKE;
log::trace!("adding kqueue watch: {}", path.absolute.display());
self.kqueue
.add_filename(&path.absolute, event_filter, filter_flags)
.map_err(|e| Error::io(e).add_path(path.requested.clone()))?;
let existing_watch = self.watches.get(&path.absolute);
let watch = Watch::new(&path, is_recursive, is_user_watch, existing_watch);
self.watches.insert(path.absolute, watch);
Ok(())
}
fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> {
log::trace!("removing kqueue watch: {}", path.display());
let preserved_roots = preserved_watch_roots(&path, remove_recursive, self.watches.iter());
match self.watches.remove(&path) {
None => return Err(Error::watch_not_found()),
Some(watch) => {
if watch.is_recursive || remove_recursive {
self.kqueue
.remove_filename(&path, EventFilter::EVFILT_VNODE)
.map_err(|e| Error::io(e).add_path(path.clone()))?;
let mut remove_list = Vec::new();
let mut reset_list = Vec::new();
for p in self.watches.keys().filter(|p| p.starts_with(&path)) {
if let Some(user_is_recursive) = preserved_watch_mode(p, &preserved_roots) {
if !user_is_recursive || is_preserved_watch_root(p, &preserved_roots) {
reset_list.push(p.clone());
}
continue;
}
remove_list.push(p.clone());
}
for p in &remove_list {
self.kqueue
.remove_filename(p, EventFilter::EVFILT_VNODE)
.map_err(|e| Error::io(e).add_path(p.clone()))?;
}
for p in remove_list {
self.watches.remove(&p);
}
for p in reset_list {
if let Some(watch) = self.watches.get_mut(&p) {
watch.is_recursive = watch.user_is_recursive;
}
}
} else {
self.kqueue
.remove_filename(&path, EventFilter::EVFILT_VNODE)
.map_err(|e| Error::io(e).add_path(path.clone()))?;
}
self.kqueue.watch()?;
}
}
Ok(())
}
}
fn map_walkdir_error(e: walkdir::Error) -> Error {
if e.io_error().is_some() {
Error::io(e.into_io_error().unwrap())
} else {
Error::generic(&e.to_string())
}
}
fn walkdir_error_is_not_found(e: &walkdir::Error) -> bool {
e.io_error()
.is_some_and(|e| e.kind() == std::io::ErrorKind::NotFound)
}
fn error_is_not_found(e: &Error) -> bool {
matches!(&e.kind, ErrorKind::PathNotFound)
|| matches!(&e.kind, ErrorKind::Io(io_err) if io_err.kind() == std::io::ErrorKind::NotFound)
}
impl KqueueWatcher {
fn from_event_handler(
event_handler: Box<dyn EventHandler>,
follow_symlinks: bool,
event_kinds: EventKindMask,
) -> Result<Self> {
let kqueue = kqueue::Watcher::new()?;
let event_loop = EventLoop::new(kqueue, event_handler, follow_symlinks, event_kinds)?;
let channel = event_loop.event_loop_tx.clone();
let waker = event_loop.event_loop_waker.clone();
event_loop.run();
Ok(KqueueWatcher { channel, waker })
}
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
let pb = WatchPath::new(path)?;
let (tx, rx) = unbounded();
let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx);
self.channel
.send(msg)
.map_err(|e| Error::generic(&e.to_string()))?;
self.waker
.wake()
.map_err(|e| Error::generic(&e.to_string()))?;
rx.recv().map_err(|e| Error::generic(&e.to_string()))?
}
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
let pb = absolute_path(path)?;
let (tx, rx) = unbounded();
let msg = EventLoopMsg::RemoveWatch(pb, tx);
self.channel
.send(msg)
.map_err(|e| Error::generic(&e.to_string()))?;
self.waker
.wake()
.map_err(|e| Error::generic(&e.to_string()))?;
rx.recv().map_err(|e| Error::generic(&e.to_string()))?
}
fn watched_paths_inner(&self) -> Result<Vec<(PathBuf, RecursiveMode)>> {
let (tx, rx) = unbounded();
self.channel.send(EventLoopMsg::GetWatchedPaths(tx))?;
self.waker.wake()?;
rx.recv().map_err(Error::from)
}
}
impl Watcher for KqueueWatcher {
fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
Self::from_event_handler(
Box::new(event_handler),
config.follow_symlinks(),
config.event_kinds(),
)
}
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
self.watch_inner(path, recursive_mode)
}
fn unwatch(&mut self, path: &Path) -> Result<()> {
self.unwatch_inner(path)
}
fn watched_paths(&self) -> Result<Vec<(PathBuf, RecursiveMode)>> {
self.watched_paths_inner()
}
fn kind() -> crate::WatcherKind {
crate::WatcherKind::Kqueue
}
}
impl Drop for KqueueWatcher {
fn drop(&mut self) {
self.channel.send(EventLoopMsg::Shutdown).unwrap();
self.waker.wake().unwrap();
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use crate::test::{self, *};
fn watcher() -> (TestWatcher<KqueueWatcher>, test::Receiver) {
channel()
}
fn test_event_loop() -> std::result::Result<EventLoop, Box<dyn std::error::Error>> {
let kqueue = kqueue::Watcher::new()?;
Ok(EventLoop::new(
kqueue,
Box::new(|_| {}),
false,
EventKindMask::ALL,
)?)
}
#[test]
fn test_remove_recursive() -> std::result::Result<(), Box<dyn std::error::Error>> {
let path = PathBuf::from("src");
let mut watcher = KqueueWatcher::new(|event| println!("{event:?}"), Config::default())?;
watcher.watch(&path, RecursiveMode::Recursive)?;
let result = watcher.unwatch(&path);
assert!(
result.is_ok(),
"unwatch yielded error: {}",
result.unwrap_err()
);
Ok(())
}
#[test]
fn recursive_watch_is_installed_before_directory_create_event_is_delivered() {
let tmpdir = testdir();
let root = tmpdir.path();
let child = root.join("child");
let file = child.join("created-from-handler.txt");
let callback_child = child.clone();
let callback_file = file.clone();
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = KqueueWatcher::new(
move |event: Result<Event>| {
if event.as_ref().is_ok_and(|event| {
matches!(event.kind, EventKind::Create(CreateKind::Folder))
&& event.paths.contains(&callback_child)
}) {
std::fs::write(&callback_file, "created by event handler")
.expect("create file from directory-create event handler");
}
let _ = tx.send(event);
},
Config::default(),
)
.expect("create watcher");
watcher
.watch(root, RecursiveMode::Recursive)
.expect("watch recursively");
std::fs::create_dir(&child).expect("create child directory");
let mut rx = test::Receiver {
rx,
timeout: Duration::from_secs(5),
detect_changes: None,
kind: crate::WatcherKind::Kqueue,
};
rx.wait_unordered([
expected(&child).create_folder(),
expected(&file).create_file(),
]);
}
#[test]
fn internal_recursive_refresh_preserves_explicit_child()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
std::fs::create_dir(&child)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
event_loop.add_watch(WatchPath::new(&child)?, false, true)?;
event_loop.remove_watch(dir.path().to_path_buf(), false)?;
assert!(
event_loop
.watches
.get(&child)
.is_some_and(|watch| watch.is_user_watch && !watch.user_is_recursive),
"internal refresh removed explicit child watch"
);
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
let watched: HashMap<_, _> = event_loop
.watches
.iter()
.filter(|(_path, watch)| watch.is_user_watch)
.map(|(path, watch)| (path.clone(), watch.user_is_recursive))
.collect();
assert_eq!(watched.get(dir.path()), Some(&true));
assert_eq!(watched.get(&child), Some(&false));
Ok(())
}
#[test]
fn write_watches_every_new_entry() -> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
let first = dir.path().join("first");
let second = dir.path().join("second");
let file = dir.path().join("file");
std::fs::create_dir_all(first.join("nested"))?;
std::fs::create_dir_all(second.join("nested"))?;
std::fs::write(&file, "")?;
event_loop.handle_kqueue();
assert!(event_loop.watches.contains_key(&first.join("nested")));
assert!(event_loop.watches.contains_key(&second.join("nested")));
assert!(event_loop.watches.contains_key(&file));
Ok(())
}
#[test]
fn failed_refresh_registers_added_watches()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let mut event_loop = test_event_loop()?;
event_loop.follow_symlinks = true;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
let child = dir.path().join("child");
let cycle = child.join("cycle");
std::fs::create_dir(&child)?;
std::os::unix::fs::symlink(".", &cycle)?;
event_loop.handle_kqueue();
std::fs::remove_file(&cycle)?;
let new = child.join("new");
std::fs::write(&new, "")?;
event_loop.handle_kqueue();
assert!(event_loop.watches.contains_key(&new));
Ok(())
}
#[test]
fn write_rewatches_recreated_subdir() -> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
std::fs::create_dir(&child)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
std::fs::remove_dir(&child)?;
std::fs::create_dir(&child)?;
event_loop.handle_kqueue();
let new = child.join("new");
std::fs::write(&new, "")?;
event_loop.handle_kqueue();
assert!(event_loop.watches.contains_key(&new));
Ok(())
}
#[test]
fn write_watches_file_that_replaced_subdir()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
std::fs::create_dir(&child)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
std::fs::remove_dir(&child)?;
std::fs::write(&child, "")?;
event_loop.handle_kqueue();
let watch = event_loop.watches.get(&child).expect("child watch");
assert!(!watch.is_recursive);
Ok(())
}
#[test]
fn link_change_keeps_non_recursive_watch() -> std::result::Result<(), Box<dyn std::error::Error>>
{
let dir = tempfile::tempdir()?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, false, true)?;
let child = dir.path().join("child");
std::fs::create_dir(&child)?;
event_loop.handle_kqueue();
assert!(!event_loop.watches.contains_key(&child));
let watch = event_loop.watches.get(dir.path()).expect("root watch");
assert!(!watch.is_recursive);
assert!(!watch.user_is_recursive);
Ok(())
}
#[test]
fn recursive_remove_uses_tracked_watches() -> std::result::Result<(), Box<dyn std::error::Error>>
{
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
std::fs::write(&child, "")?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
assert!(event_loop.watches.contains_key(&child));
std::fs::remove_file(&child)?;
event_loop.remove_watch(dir.path().to_path_buf(), false)?;
assert!(!event_loop.watches.contains_key(&child));
Ok(())
}
#[test]
fn failed_walk_registers_added_watches() -> std::result::Result<(), Box<dyn std::error::Error>>
{
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
let locked = child.join("locked");
std::fs::create_dir_all(&locked)?;
std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o000))?;
let readable = std::fs::read_dir(&locked).is_ok();
let mut event_loop = test_event_loop()?;
let result = event_loop.add_watch(WatchPath::new(dir.path())?, true, true);
std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o755))?;
if readable {
return Ok(());
}
assert!(result.is_err());
let new = child.join("new");
std::fs::create_dir(&new)?;
event_loop.handle_kqueue();
assert!(event_loop.watches.contains_key(&new));
Ok(())
}
#[test]
fn rewatching_same_path_replaces_recursive_state()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
std::fs::create_dir(&child)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
assert!(event_loop.watches.contains_key(&child));
event_loop.add_watch(WatchPath::new(dir.path())?, false, true)?;
let watch = event_loop.watches.get(dir.path()).expect("root watch");
assert!(watch.is_user_watch);
assert!(!watch.user_is_recursive);
assert!(!watch.is_recursive);
assert!(!event_loop.watches.contains_key(&child));
Ok(())
}
#[test]
fn rewatching_child_preserves_recursive_parent_state()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
let grandchild = child.join("grandchild");
std::fs::create_dir_all(&grandchild)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
event_loop.add_watch(WatchPath::new(&child)?, false, true)?;
event_loop.add_watch(
WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
false,
true,
)?;
let child_watch = event_loop.watches.get(&child).expect("child watch");
assert!(child_watch.is_user_watch);
assert!(!child_watch.user_is_recursive);
assert!(child_watch.is_recursive);
assert_eq!(child_watch.reported_path, PathBuf::from("reported-child"));
let grandchild_watch = event_loop
.watches
.get(&grandchild)
.expect("grandchild still covered by recursive parent");
assert!(!grandchild_watch.is_user_watch);
assert!(grandchild_watch.is_recursive);
Ok(())
}
#[test]
fn nested_recursive_watch_keeps_its_spelling_under_an_outer_watch()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
let grandchild = child.join("grandchild");
std::fs::create_dir_all(&grandchild)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(
WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
true,
true,
)?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
let reported = |event_loop: &EventLoop, path: &Path| {
let watch = event_loop.watches.get(path).expect("watch");
watch.reported_path.clone()
};
assert_eq!(reported(&event_loop, dir.path()), dir.path());
assert_eq!(reported(&event_loop, &child), Path::new("reported-child"));
assert_eq!(
reported(&event_loop, &grandchild),
Path::new("reported-child/grandchild")
);
event_loop.remove_watch(dir.path().to_path_buf(), false)?;
assert_eq!(
reported(&event_loop, &grandchild),
Path::new("reported-child/grandchild")
);
Ok(())
}
#[test]
fn rewatching_carved_out_child_does_not_restore_parent_recursive_state()
-> std::result::Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let child = dir.path().join("child");
let grandchild = child.join("grandchild");
std::fs::create_dir_all(&grandchild)?;
let mut event_loop = test_event_loop()?;
event_loop.add_watch(WatchPath::new(dir.path())?, true, true)?;
event_loop.remove_watch(child.clone(), false)?;
event_loop.add_watch(WatchPath::new(&child)?, false, true)?;
event_loop.add_watch(
WatchPath::from_parts(child.clone(), PathBuf::from("reported-child")),
false,
true,
)?;
let child_watch = event_loop.watches.get(&child).expect("child watch");
assert!(child_watch.is_user_watch);
assert!(!child_watch.user_is_recursive);
assert!(!child_watch.is_recursive);
assert_eq!(child_watch.reported_path, PathBuf::from("reported-child"));
assert!(!event_loop.watches.contains_key(&grandchild));
Ok(())
}
#[test]
fn create_file() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
watcher.watch_recursively(&tmpdir);
let path = tmpdir.path().join("entry");
std::fs::File::create_new(&path).expect("create");
rx.wait_unordered([expected(path).create_file()]);
}
#[test]
fn write_file() {
let tmpdir = testdir();
let path = tmpdir.path().join("entry");
std::fs::File::create_new(&path).expect("create");
let (mut watcher, mut rx) = watcher();
watcher.watch_recursively(&tmpdir);
std::fs::write(&path, b"123").expect("write");
rx.wait_unordered([expected(&path).modify_data_any()]);
}
#[test]
fn chmod_file() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
let file = std::fs::File::create_new(&path).expect("create");
let mut permissions = file.metadata().expect("metadata").permissions();
permissions.set_readonly(true);
watcher.watch_recursively(&tmpdir);
file.set_permissions(permissions).expect("set_permissions");
rx.wait_unordered([expected(&path).modify_meta_any()]);
}
#[test]
fn rename_file() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
std::fs::File::create_new(&path).expect("create");
watcher.watch_recursively(&tmpdir);
let new_path = tmpdir.path().join("renamed");
std::fs::rename(&path, &new_path).expect("rename");
rx.wait_unordered([expected(path).rename_any(), expected(new_path).create()]);
}
#[test]
fn delete_file() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let file = tmpdir.path().join("file");
std::fs::write(&file, "").expect("write");
watcher.watch_nonrecursively(&tmpdir);
std::fs::remove_file(&file).expect("remove");
rx.wait_unordered([expected(tmpdir.path()).modify_data_any()]);
}
#[test]
fn create_file_in_non_recursive_directory_with_existing_child() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let existing = tmpdir.path().join("existing");
let created = tmpdir.path().join("created");
std::fs::write(&existing, "").expect("write");
watcher.watch_nonrecursively(&tmpdir);
std::fs::write(&created, "").expect("write");
rx.wait_unordered([expected(tmpdir.path()).modify_data_any()]);
}
#[test]
fn delete_self_file() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let file = tmpdir.path().join("file");
std::fs::write(&file, "").expect("write");
watcher.watch_nonrecursively(&file);
std::fs::remove_file(&file).expect("remove");
rx.wait_unordered([expected(file).remove_any()]);
}
#[test]
fn delete_self_dir() {
let tmpdir = testdir();
let dir = tmpdir.path().join("dir");
std::fs::create_dir(&dir).expect("create");
let (mut watcher, mut rx) = watcher();
watcher.watch_nonrecursively(&dir);
std::fs::remove_dir(&dir).expect("remove");
rx.wait_unordered([expected(&dir).remove_any()]);
}
#[test]
#[ignore = "FIXME"]
fn create_write_overwrite() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let overwritten_file = tmpdir.path().join("overwritten_file");
let overwriting_file = tmpdir.path().join("overwriting_file");
std::fs::write(&overwritten_file, "123").expect("write1");
watcher.watch_nonrecursively(&tmpdir);
std::fs::File::create(&overwriting_file).expect("create");
std::fs::write(&overwriting_file, "321").expect("write2");
std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
rx.wait_unordered([
expected(&overwriting_file).create_file(),
expected(&overwriting_file).modify_data_any().multiple(),
expected(&overwriting_file).rename_any(),
expected(&overwritten_file).rename_any(),
])
.ensure_no_tail();
}
#[test]
fn create_dir() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
watcher.watch_recursively(&tmpdir);
let path = tmpdir.path().join("entry");
std::fs::create_dir(&path).expect("create");
rx.wait_unordered([expected(&path).create_folder()]);
}
#[test]
fn chmod_dir() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
std::fs::create_dir(&path).expect("create_dir");
let mut permissions = std::fs::metadata(&path).expect("metadata").permissions();
permissions.set_readonly(true);
watcher.watch_recursively(&tmpdir);
std::fs::set_permissions(&path, permissions).expect("set_permissions");
rx.wait_unordered([expected(&path).modify_meta_any()]);
}
#[test]
fn rename_dir() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
let new_path = tmpdir.path().join("new_path");
std::fs::create_dir(&path).expect("create_dir");
watcher.watch_recursively(&tmpdir);
std::fs::rename(&path, &new_path).expect("rename");
rx.wait_ordered([
expected(&new_path).create_folder(),
expected(&path).rename_any(),
]);
}
#[test]
fn delete_dir() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
std::fs::create_dir(&path).expect("create_dir");
watcher.watch_recursively(&tmpdir);
std::fs::remove_dir(&path).expect("remove");
rx.wait_unordered([expected(path).remove_any()]);
}
#[test]
#[ignore = "FIXME"]
fn rename_dir_twice() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
let new_path = tmpdir.path().join("new_path");
let new_path2 = tmpdir.path().join("new_path2");
std::fs::create_dir(&path).expect("create_dir");
watcher.watch_recursively(&tmpdir);
std::fs::rename(&path, &new_path).expect("rename");
std::fs::rename(&new_path, &new_path2).expect("rename2");
rx.wait_unordered([
expected(&path).rename_any(),
expected(&new_path).create_folder(),
expected(&new_path).rename_any(),
expected(&new_path2).create_folder(),
]);
}
#[test]
fn move_out_of_watched_dir() {
let tmpdir = testdir();
let subdir = tmpdir.path().join("subdir");
let (mut watcher, mut rx) = watcher();
let path = subdir.join("entry");
std::fs::create_dir_all(&subdir).expect("create_dir_all");
std::fs::File::create_new(&path).expect("create");
watcher.watch_recursively(&subdir);
let new_path = tmpdir.path().join("entry");
std::fs::rename(&path, &new_path).expect("rename");
rx.wait_unordered([expected(path).rename_any()]);
}
#[test]
#[ignore = "FIXME"]
fn create_write_write_rename_write_remove() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let file1 = tmpdir.path().join("entry");
let file2 = tmpdir.path().join("entry2");
std::fs::File::create_new(&file2).expect("create file2");
let new_path = tmpdir.path().join("renamed");
watcher.watch_recursively(&tmpdir);
std::fs::write(&file1, "123").expect("write 1");
std::fs::write(&file2, "321").expect("write 2");
std::fs::rename(&file1, &new_path).expect("rename");
std::fs::write(&new_path, b"1").expect("write 3");
std::fs::remove_file(&new_path).expect("remove");
rx.wait_ordered([
expected(&file1).create_file(),
expected(&file1).modify_data_content(),
expected(&file2).modify_data_content(),
expected(&file1).rename_any(),
expected(&new_path).rename_any(),
expected(&new_path).modify_data_content(),
expected(&new_path).remove_file(),
]);
}
#[test]
#[ignore = "FIXME"]
fn rename_twice() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
std::fs::File::create_new(&path).expect("create");
watcher.watch_recursively(&tmpdir);
let new_path1 = tmpdir.path().join("renamed1");
let new_path2 = tmpdir.path().join("renamed2");
std::fs::rename(&path, &new_path1).expect("rename1");
std::fs::rename(&new_path1, &new_path2).expect("rename2");
rx.wait_unordered([
expected(&path).rename_any(),
expected(&new_path1).rename_any(),
expected(&new_path2).create(),
]);
}
#[test]
fn set_file_mtime() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
let file = std::fs::File::create_new(&path).expect("create");
watcher.watch_recursively(&tmpdir);
file.set_modified(
std::time::SystemTime::now()
.checked_sub(Duration::from_secs(60 * 60))
.expect("time"),
)
.expect("set_time");
rx.wait_unordered([expected(&path).modify_meta_any()]);
}
#[test]
fn write_file_non_recursive_watch() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let path = tmpdir.path().join("entry");
std::fs::File::create_new(&path).expect("create");
watcher.watch_nonrecursively(&path);
std::fs::write(&path, b"123").expect("write");
rx.wait_unordered([expected(path).modify_data_any()]);
}
#[test]
fn write_to_a_hardlink_pointed_to_the_watched_file_triggers_an_event() {
let tmpdir = testdir();
let (mut watcher, mut rx) = watcher();
let subdir = tmpdir.path().join("subdir");
let file = subdir.join("file");
let hardlink = tmpdir.path().join("hardlink");
std::fs::create_dir(&subdir).expect("create");
std::fs::write(&file, "").expect("file");
std::fs::hard_link(&file, &hardlink).expect("hardlink");
watcher.watch_nonrecursively(&file);
std::fs::write(&hardlink, "123123").expect("write to the hard link");
rx.wait_unordered([expected(file).modify_data_any()]);
}
#[test]
#[ignore = "FIXME"]
fn recursive_creation() {
let tmpdir = testdir();
let nested1 = tmpdir.path().join("1");
let nested2 = tmpdir.path().join("1/2");
let nested3 = tmpdir.path().join("1/2/3");
let nested4 = tmpdir.path().join("1/2/3/4");
let nested5 = tmpdir.path().join("1/2/3/4/5");
let nested6 = tmpdir.path().join("1/2/3/4/5/6");
let nested7 = tmpdir.path().join("1/2/3/4/5/6/7");
let nested8 = tmpdir.path().join("1/2/3/4/5/6/7/8");
let nested9 = tmpdir.path().join("1/2/3/4/5/6/7/8/9");
let (mut watcher, mut rx) = watcher();
watcher.watch_recursively(&tmpdir);
std::fs::create_dir_all(&nested9).expect("create_dir_all");
rx.wait_unordered([
expected(&nested1).create_folder(),
expected(&nested2).create_folder(),
expected(&nested3).create_folder(),
expected(&nested4).create_folder(),
expected(&nested5).create_folder(),
expected(&nested6).create_folder(),
expected(&nested7).create_folder(),
expected(&nested8).create_folder(),
expected(&nested9).create_folder(),
]);
}
#[test]
fn kqueue_watcher_respects_event_kind_mask() {
use crate::Watcher;
use notify_types::event::EventKindMask;
let tmpdir = testdir();
let (tx, rx) = std::sync::mpsc::channel();
let config = Config::default().with_event_kinds(EventKindMask::CREATE);
let mut watcher = KqueueWatcher::new(tx, config).expect("create watcher");
watcher
.watch(tmpdir.path(), crate::RecursiveMode::Recursive)
.expect("watch");
let path = tmpdir.path().join("test_file");
std::fs::File::create_new(&path).expect("create");
std::thread::sleep(Duration::from_millis(100));
std::fs::write(&path, "modified content").expect("write modified");
std::thread::sleep(Duration::from_millis(100));
let events: Vec<_> = rx.try_iter().filter_map(|r| r.ok()).collect();
assert!(
events.iter().any(|e| e.kind.is_create()),
"Expected CREATE event, got: {events:?}"
);
assert!(
!events.iter().any(|e| e.kind.is_modify()),
"Should not receive MODIFY events with CREATE-only mask, got: {events:?}"
);
}
}