diff --git a/components/sup/src/manager/file_watcher.rs b/components/sup/src/manager/file_watcher.rs index 12a1e214d2..da0e9e1d59 100644 --- a/components/sup/src/manager/file_watcher.rs +++ b/components/sup/src/manager/file_watcher.rs @@ -5,9 +5,17 @@ use crate::{error::{Error, IndentedToString}}; use habitat_common::liveliness_checker; use log::{debug, - error}; + error, + trace, + warn}; use notify::{self, - DebouncedEvent, + event::{EventKind, + MetadataKind, + ModifyKind, + RenameMode}, + Config, + ErrorKind, + Event, RecursiveMode, Watcher}; use std::{collections::{hash_map::Entry, @@ -67,6 +75,8 @@ pub trait Callbacks { } // Essentially a pair of dirname and basename. +// JAH: This and SplitPath might be able to be merged as the difference is that file_name is +// wrapped in an Option for SplitPath but not for DirFileName #[derive(Clone, Debug, Default)] struct DirFileName { directory: PathBuf, @@ -108,6 +118,9 @@ impl IndentedToString for DirFileName { // status in mountinfo, when some change there happens. // Similar to DirFileName, but the file_name part is optional. +// JAH: This and DirFileName might be able to be merged as the difference is that file_name is +// wrapped in an Option for SplitPath but not for DirFileName +#[derive(Debug)] struct SplitPath { directory: PathBuf, file_name: Option, @@ -134,6 +147,7 @@ impl IndentedToString for SplitPath { } } +// JAH: Why both ChainLinkInfo and ProcessPathArgs? They both seem like nodes for a LinkedList // Contents of this type are for `process_path`. #[derive(Debug)] struct ProcessPathArgs { @@ -169,8 +183,9 @@ impl IndentedToString for ProcessPathArgs { } } -// This struct tells that for `path` the previous item in chain is -// `prev`. +// JAH: Why both ChainLinkInfo and ProcessPathArgs? They both seem like nodes for a LinkedList +// This struct tells that for `path` the previous item in chain is `prev`. +#[derive(Debug)] struct ChainLinkInfo { path: PathBuf, prev: Option, @@ -204,6 +219,7 @@ impl IndentedToString for PathsActionData { // This is stores information about the watched item // // TODO(krnowak): Rename it. +#[derive(Debug)] struct Common { // TODO: maybe drop this? we could also use dir_file_name.as_path() path: PathBuf, @@ -274,6 +290,7 @@ impl IndentedToString for Common { // `/h-o` and then for `/h-o/peers` subsequently. // // TODO(krnowak): Rename it. +#[derive(Debug)] struct CommonGenerator { // Usually describes what was the last item we processed. Bit more // complicated in case of symlinks. @@ -382,7 +399,7 @@ impl IndentedToString for CommonGenerator { formatter.fmt() } } - +#[derive(Debug)] // An item we are interested in. enum WatchedFile { Regular(Common), @@ -476,10 +493,13 @@ enum EventAction { PlainChange(PathBuf), RestartWatching, AddRegular(PathsActionData), + #[allow(dead_code)] DropRegular(PathsActionData), AddDirectory(PathsActionData), + #[allow(dead_code)] DropDirectory(PathsActionData), RewireSymlink(PathsActionData), + #[allow(dead_code)] DropSymlink(PathsActionData), SettlePath(PathBuf), } @@ -559,6 +579,7 @@ impl IndentedToString for PathsAction { // directories. // // TODO(asymmetric): This could be renamed to `BranchStatus`. +#[derive(Debug)] enum BranchResult { // The path already existed - may happen when dealing with // symlinks in the path. @@ -586,6 +607,7 @@ impl IndentedToString for BranchResult { } // See `BranchResult`. +#[derive(Debug)] enum LeafResult { // New path in a known directory. NewInOldDirectory(ChainLinkInfo), @@ -611,6 +633,7 @@ impl IndentedToString for LeafResult { // Used when we settle a path, so we know if we processed a path // because all the paths were already settled, or not if there were // still some left to settle. +#[derive(Debug)] enum ProcessPathStatus { // Holds a vector of new directories to watch (a result of // `process_path` function) @@ -618,6 +641,7 @@ enum ProcessPathStatus { NotExecuted(ProcessPathArgs), } +#[derive(Debug)] // Paths holds the state with regards to watching. struct Paths { // A map of paths to file info of items. If something happens to @@ -1129,12 +1153,13 @@ impl Paths { /// that happened to the regular file at the desired path. Note that /// it will call the file_appeared callback in the first iteration if /// the file existed when the watcher was created. +#[derive(Debug)] pub struct FileWatcher { callbacks: C, // The watcher itself. watcher: W, // A channel for receiving events. - rx: Receiver, + rx: Receiver>, // The paths to watch. paths: Paths, // Path to the file if it existed when we created the watcher. @@ -1173,11 +1198,9 @@ impl FileWatcher { fn create_instance

(path: P, callbacks: C, send_initial_event: bool) -> Result where P: Into { - let (tx, rx) = channel(); - let mut watcher = - W::new(tx, Duration::from_millis(WATCHER_DELAY_MS)).map_err(|err| { - Error::NotifyCreateError(err) - })?; + let (tx, rx) = channel::>(); + let config = Config::default().with_poll_interval(Duration::from_millis(WATCHER_DELAY_MS)); + let mut watcher = W::new(tx, config).map_err(Error::NotifyCreateError)?; let start_path = Self::watcher_path(path.into())?; // Initialize the Paths struct, which will hold all state // relative to file watching. @@ -1256,18 +1279,18 @@ impl FileWatcher { self.initial_real_file = None; match self.rx.try_recv() { - Ok(e) => self.handle_event(e), + Ok(notify_result) => self.handle_event(¬ify_result?), Err(TryRecvError::Empty) => Ok(()), Err(e) => Err(e.into()), } } - fn handle_event(&mut self, event: DebouncedEvent) -> Result<()> { + fn handle_event(&mut self, event: &Event) -> Result<()> { let mut actions = VecDeque::new(); debug!("in handle_event fn"); debug!("got debounced event: {:?}", event); - self.emit_directories_for_event(&event); + self.emit_directories_for_event(event); // Gather the high-level actions. actions.extend(Self::get_paths_actions(&self.paths, event)); @@ -1289,14 +1312,17 @@ impl FileWatcher { } PathsAction::DropWatch(p) => { if let Some(dir_path) = self.paths.drop_watch(&p) { - match self.watcher.unwatch(dir_path) { + match self.watcher.unwatch(&dir_path) { Ok(_) => (), // These probably may happen when the // directory was removed. Ignore them, as // we wanted to drop the watch anyway. - Err(notify::Error::PathNotFound) - | Err(notify::Error::WatchNotFound) => (), - Err(e) => return Err(Error::NotifyError(e)), + Err(e) => { + match e.kind { + ErrorKind::PathNotFound | ErrorKind::WatchNotFound => (), + _ => return Err(Error::NotifyError(e)), + } + } } } } @@ -1317,14 +1343,17 @@ impl FileWatcher { actions.push_back(PathsAction::NotifyFileDisappeared(path.clone())); } for directory in self.paths.reset() { - match self.watcher.unwatch(directory) { + match self.watcher.unwatch(directory.as_path()) { Ok(_) => (), // These probably may happen when the // directory was removed. Ignore them, as // we wanted to drop the watch anyway. - Err(notify::Error::PathNotFound) - | Err(notify::Error::WatchNotFound) => (), - Err(e) => return Err(Error::NotifyError(e)), + Err(e) => { + match e.kind { + ErrorKind::PathNotFound | ErrorKind::WatchNotFound => (), + _ => return Err(Error::NotifyError(e)), + } + } } } let process_args = Paths::path_for_processing(&self.paths.start_path); @@ -1335,35 +1364,36 @@ impl FileWatcher { Ok(()) } - fn emit_directories_for_event(&mut self, event: &DebouncedEvent) { - let paths = match event { - DebouncedEvent::NoticeWrite(ref p) - | DebouncedEvent::Write(ref p) - | DebouncedEvent::Chmod(ref p) - | DebouncedEvent::NoticeRemove(ref p) - | DebouncedEvent::Remove(ref p) - | DebouncedEvent::Create(ref p) => vec![p], - DebouncedEvent::Rename(ref from, ref to) => vec![from, to], - DebouncedEvent::Rescan => vec![], - DebouncedEvent::Error(_, ref o) => { - match o { - Some(ref p) => vec![p], - None => vec![], - } - } - }; + fn emit_directories_for_event(&mut self, notify_event: &Event) { + trace!("FileWatcher::emit_directories_for_event(&mut self,&Event)\n{:#?}", + notify_event); + // Notes on emit_directories_for_event implementation for notify 5.0.0 + // + // (1) The "if !notify_event.need_rescan() honors the original code's use of vec![] when a + // rescan was needed + // + // (2) The 4.0.x series had an error mechanism in the Debounce API that was removed. + // + // (3) In the code that follows we're iterating over all event paths contained in the event. + // The "paths: Vec" field of the Event struct in some implementations will roll up + // multiple files in a single event. See documentaton for the inotify and fsevent impls. let mut dirs = vec![]; - for path in paths { - if let Some(wf) = self.paths.get_watched_file(path) { - dirs.push(wf.get_common().dir_file_name.directory.clone()); - } else if self.paths.get_directory(path).is_some() { - dirs.push(path.clone()); - } else if let Some(df) = DirFileName::split_path(path) { - if self.paths.get_directory(&df.directory).is_some() { - dirs.push(df.directory.clone()); + if !notify_event.need_rescan() { + // This conditional honors the original code's use of vec![] in the rescan match + // What isn't fully honored is the branch for the error case hich seems to have + // be remove from the API in the transition from v4 to v5. + for path in ¬ify_event.paths { + if let Some(wf) = self.paths.get_watched_file(path) { + dirs.push(wf.get_common().dir_file_name.directory.clone()); + } else if self.paths.get_directory(path).is_some() { + dirs.push(path.clone()); + } else if let Some(df) = DirFileName::split_path(path) { + if self.paths.get_directory(&df.directory).is_some() { + dirs.push(df.directory.clone()); + } } } - } + }; debug!("event in dirs: {:?}", dirs); self.callbacks.event_in_directories(&dirs); } @@ -1386,7 +1416,10 @@ impl FileWatcher { } // Maps `EventAction`s to one or more lower-level `PathsAction`s. - fn get_paths_actions(paths: &Paths, event: DebouncedEvent) -> Vec { + fn get_paths_actions(paths: &Paths, event: &Event) -> Vec { + trace!("ENTER FileWatcher::get_paths_actions(&Paths,Event)\n{:#?}\n{:#?}", + paths, + event); let mut actions = Vec::new(); for event_action in Self::get_event_actions(paths, event) { debug!("event_action: {}", dits!(event_action)); @@ -1456,76 +1489,183 @@ impl FileWatcher { } // Maps filesystem events to high-level actions. - fn get_event_actions(paths: &Paths, event: DebouncedEvent) -> Vec { - // Usual actions on files and resulting events (assuming that - // a and b are in the same directory which we watch) - // touch a - Create(a) - // ln -sf foo a (does not matter if symlink a did exist before)- Create(a) - // mkdir a - Create(a) - // mv a b (does not matter if b existed or not) - NoticeRemove(a), Rename(a, b) - // mv ../a . - Create(a) - // mv a .. - NoticeRemove(a), Remove(a) - // rm a - NoticeRemove(a), Remove(a) - // echo foo >a (assuming a did exist earlier) - NoticeWrite(a), Write(a) - let event_action = match event { - // `Write` event will handle that. - DebouncedEvent::NoticeWrite(_) => EventAction::Ignore, - // These happen for regular files, just check if it - // affects the file we are watching. - // - // TODO(krnowak): I wonder if we should watch `Chmod` - // events for directories too. Maybe some permission - // changes can cause the directory to be unwatchable. Or - // watchable again for that matter. - DebouncedEvent::Write(ref p) | DebouncedEvent::Chmod(ref p) => { - match paths.get_watched_file(p) { - Some(&WatchedFile::Regular(_)) => EventAction::PlainChange(p.clone()), - _ => EventAction::Ignore, + fn get_event_actions(paths: &Paths, notify_event: &Event) -> Vec { + // Notes on get_event_actions implementation for notify 5.0.0 + // + // (1) In 5.0 notice events aren't currently implemented. + // https://github.com/notify-rs/notify/blob/0e354a3f51bf5ba7038f31bb0955e0bd59d73abd/notify/src/event.rs#L491 + // + // (2) In the code that follows we're iterating over all event paths contained in the event. + // The "paths: Vec" field of the Event struct in some implementations will roll up + // multiple files in a single event. See documentaton for the inotify and fsevent impls. + // + // (3) One exception to the iteration/roll up is the rename case. Its likely the rename + // case that prompted the use of paths: Vec for paths in the notify crate and the + // the code follwos what's documented in the function. + // + // (4) Another exception to the iteration/roll up are the _/wildcase cases. If we're + // ignoring the event why break it apart into multiple event actions? + // + // (5) The 4.0.x series had an error mechanism in the Debounce API that was removed. + + let mut event_actions: Vec = Vec::new(); + if notify_event.need_rescan() { + event_actions.push(EventAction::RestartWatching); + } else { + match notify_event.kind { + // Close Write: Inotify Only + // EventKind::Access(AccessKind::Close(AccessMode::Write)) => { + // Self::handle_simple_events(¬ify_event, &paths, &mut event_actions); + // } + // // Open Write: Inotify Only + // EventKind::Access(AccessKind::Open(AccessMode::Write)) => { + // Self::handle_simple_events(¬ify_event, &paths, &mut event_actions); + // } + // Creation: notify v5 kinds or + // CreateKind::Any - + // CreateKind::File - FSEvent, Inotify, KQueue + // CreateKind::Folder + // CreateKind::Other + EventKind::Create(_) => { + Self::handle_create_events(¬ify_event, &paths, &mut event_actions); } - } - DebouncedEvent::NoticeRemove(ref p) => Self::handle_notice_remove_event(paths, p), - DebouncedEvent::Remove(p) => Self::handle_remove_event(paths, p), - DebouncedEvent::Create(ref p) => { - match paths.get_watched_file(p) { - None => EventAction::Ignore, - Some(&WatchedFile::MissingRegular(ref c)) => { - EventAction::AddRegular(c.get_paths_action_data()) - } - // Create event for an already existing file or - // directory should not happen, restart watching. - Some(&WatchedFile::Regular(_)) | Some(&WatchedFile::Directory(_)) => { - EventAction::RestartWatching - } - Some(&WatchedFile::Symlink(ref c)) => { - EventAction::RewireSymlink(c.get_paths_action_data()) - } - Some(&WatchedFile::MissingDirectory(ref c)) => { - EventAction::AddDirectory(c.get_paths_action_data()) - } + // chmod + EventKind::Modify(ModifyKind::Metadata(MetadataKind::Permissions)) => { + Self::handle_simple_events(¬ify_event, &paths, &mut event_actions); } - } - DebouncedEvent::Rename(from, to) => { - let events = vec![Self::handle_notice_remove_event(paths, &to), - EventAction::SettlePath(to), - Self::handle_remove_event(paths, from)]; - // Rename is annoying in that it does not come - // together with `NoticeRemove` of the destination - // file (it is preceded with `NoticeRemove` of the - // source file only), so we just going to emulate it - // and then settle the destination path. - debug!("translated to {:?}", events); - return events; - } - DebouncedEvent::Rescan => EventAction::RestartWatching, - DebouncedEvent::Error(..) => EventAction::RestartWatching, - }; - debug!("translated to single {}", dits!(event_action)); - vec![event_action] + // File + EventKind::Modify(ModifyKind::Name(rename_mode)) => { + Self::handle_rename_event(rename_mode, + ¬ify_event, + &paths, + &mut event_actions); + } + // remove + EventKind::Remove(_) => { + Self::handle_remove_events(¬ify_event, &paths, &mut event_actions); + } + _ => { + Self::log_ignored_event(notify_event); + event_actions.push(EventAction::Ignore); + } + }; + } + + trace!("fn get_event_actions(&Paths, &Event) -> Vec\npaths: \ + {:#?}\nnotify_event: {:#?}\nevent_actions: {:#?}", + paths, + notify_event, + event_actions); + + event_actions + } + + // note the lack of s, this handles a single event + fn handle_rename_event(rename_mode: RenameMode, + notify_event: &Event, + paths: &Paths, + event_actions: &mut Vec) { + let nep = notify_event.paths.clone(); + // JAH: add checking for exactly 2 paths on rename? + match rename_mode { + RenameMode::To => { + // Possible with INotify and Windows + let to = nep[0].to_path_buf(); + event_actions.push(EventAction::SettlePath(to)); + } + RenameMode::From => { + // Possible with INotify and Windows + let from = nep[0].to_path_buf(); + event_actions.push(Self::handle_remove_event(notify_event, paths, from)); + } + RenameMode::Both => { + // Possible with INotify + let to = nep[0].to_path_buf(); + event_actions.push(EventAction::SettlePath(to)); + let from = nep[1].to_path_buf(); + event_actions.push(Self::handle_remove_event(notify_event, paths, from)); + } + _ => { + Self::log_ignored_event(notify_event); + event_actions.push(EventAction::Ignore); + // RenameMode::Any + // - "FSEvents provides no mechanism to associate the old and new sides of a rename + // event." + // - "Kqueue not provide us with the infomation nessesary to provide the new file + // name to the event."" + // RenameMode::Other + // - no references found + } + } + } + + // Logging the fact that a particular event was ignored. Ignoring an event may be the correct + // thing to do so the purpose of logging ignored event is to highlight the decisions captured + // in code for clarity when developing and debugging. + fn log_ignored_event(notify_event: &Event) { warn!("IGNORING {:?}", notify_event) } + + // Logging the fact that a particular path was ignored. Ignoring a given path may be the + // correct thing to do so the purpose of logging that a particular path was ignored is to + // highlight the decisions captured in code for assessment when developing and debugging. + fn log_ignored_path(notify_event: &Event, path_buf: &PathBuf) { + warn!("IGNORING {:?} of \n{:?}", path_buf, notify_event) + } + + fn handle_simple_events(notify_event: &Event, + paths: &Paths, + event_actions: &mut Vec) { + for path_buf in notify_event.paths.clone() { + match paths.get_watched_file(&path_buf) { + Some(&WatchedFile::Regular(_)) => { + event_actions.push(EventAction::PlainChange(path_buf)) + } + _ => { + Self::log_ignored_path(notify_event, &path_buf); + event_actions.push(EventAction::Ignore) + } + }; + } + } + + fn handle_create_events(notify_event: &Event, + paths: &Paths, + event_actions: &mut Vec) { + for path_buf in notify_event.paths.clone() { + match paths.get_watched_file(&path_buf) { + None => { + Self::log_ignored_path(notify_event, &path_buf); + event_actions.push(EventAction::Ignore) + } + Some(&WatchedFile::MissingRegular(ref c)) => { + event_actions.push(EventAction::AddRegular(c.get_paths_action_data())) + } + // Create event for an already existing file or + // directory should not happen, restart watching. + Some(&WatchedFile::Regular(_)) | Some(&WatchedFile::Directory(_)) => { + event_actions.push(EventAction::RestartWatching) + } + Some(&WatchedFile::Symlink(ref c)) => { + event_actions.push(EventAction::RewireSymlink(c.get_paths_action_data())) + } + Some(&WatchedFile::MissingDirectory(ref c)) => { + event_actions.push(EventAction::AddDirectory(c.get_paths_action_data())) + } + }; + } } - fn handle_notice_remove_event(paths: &Paths, p: &Path) -> EventAction { - match paths.get_watched_file(p) { - None => EventAction::Ignore, + // note the lack of s, this fn handles a single path from an event instead of a looping over + // all of the paths in the event + fn handle_remove_event(notify_event: &Event, paths: &Paths, path_buf: PathBuf) -> EventAction { + trace!("FileWatcher::handle_remove_event <-- singular"); + match paths.get_watched_file(path_buf.as_path()) { + None => { + // this is a perfect example of a place where ignoring is the thing to do so I'm + // tempted to not log but I'm leaving it anyway because it fits the pattern I want + // in the code + Self::log_ignored_path(notify_event, &path_buf); + EventAction::Ignore + } // Our directory was removed, moved elsewhere or // replaced. I discovered replacement scenario while // working on this code. Consider: @@ -1540,34 +1680,34 @@ impl FileWatcher { Some(&WatchedFile::Directory(ref c)) => { EventAction::DropDirectory(c.get_paths_action_data()) } + // This happens when we expected `p` to be a directory, + // but it was something else and that thing just got + // removed. + Some(&WatchedFile::MissingDirectory(_)) => { + Self::log_ignored_path(notify_event, &path_buf); + EventAction::Ignore + } // This happens when we expected `p` to be a file, but it // was something else and that thing just got removed. - Some(&WatchedFile::MissingRegular(_)) => EventAction::Ignore, + Some(&WatchedFile::MissingRegular(_)) => { + Self::log_ignored_path(notify_event, &path_buf); + EventAction::Ignore + } Some(&WatchedFile::Regular(ref c)) => { EventAction::DropRegular(c.get_paths_action_data()) } Some(&WatchedFile::Symlink(ref c)) => { EventAction::DropSymlink(c.get_paths_action_data()) } - // This happens when we expected `p` to be a directory, - // but it was something else and that thing just got - // removed. - Some(&WatchedFile::MissingDirectory(_)) => EventAction::Ignore, } } - fn handle_remove_event(paths: &Paths, path: PathBuf) -> EventAction { - match paths.get_watched_file(&path) { - // We should have dropped the watch of this file in - // `NoticeRemove`, so this should not happen - restart - // watching. - Some(&WatchedFile::Symlink(_)) - | Some(&WatchedFile::Directory(_)) - | Some(&WatchedFile::Regular(_)) => EventAction::RestartWatching, - // Possibly `path` is something that used to be - // interesting to us and got removed, try to settle it. If - // it was not that, then nothing will happen. - _ => EventAction::SettlePath(path), + fn handle_remove_events(notify_event: &Event, + paths: &Paths, + event_actions: &mut Vec) { + trace!("FileWatcher::handle_remove_events <-- plural"); + for path_buf in notify_event.paths.clone() { + event_actions.push(Self::handle_remove_event(notify_event, paths, path_buf)); } } } @@ -1576,9 +1716,9 @@ impl FileWatcher { // scenario, that involves symlinks. #[cfg(all(unix, test))] mod tests { - use crate::manager::sup_watcher::SupWatcher; + use crate::{logger, + manager::sup_watcher::SupWatcher}; use habitat_core::locked_env_var; - use log::debug; use std::{collections::{HashMap, HashSet, VecDeque}, @@ -1593,23 +1733,42 @@ mod tests { path::{Component, Path, PathBuf}, - sync::mpsc::Sender, thread, time::Duration}; - use notify::{self, - DebouncedEvent, - RawEvent, - RecursiveMode, - Watcher}; + use notify::{EventHandler, + WatcherKind}; use tempfile::TempDir; use super::{Callbacks, + Config, FileWatcher, IndentedStructFormatter, IndentedToString, - WatchedFile}; + RecursiveMode, + WatchedFile, + Watcher}; + + use log::{debug, + error, + info, + trace, + warn}; + + #[test] + fn logging_test() { + println!("println!/stdout is being logged"); + eprintln!("eprintln!/stderr is being logged"); + + trace!("trace stmts are being logged"); + debug!("debug stmts are being logged"); + info!("info stmts are being logged"); + warn!("warn stmts are being logged"); + error!("error stmts are being logged"); + + assert!(true); + } // Convenient macro for inline creation of hashmaps. macro_rules! hm( @@ -1650,198 +1809,205 @@ mod tests { init: Init { path: Some(pb!("/a/b/c")), commands: vec![], initial_file: None, }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::MkdirP(pb!("/a")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::MkdirP(pb!("/a/b")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - pb!("/a/b") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: Some(pb!("/a/b/c")), - }, - pb!("/a/b/c") => PathState { - kind: PathKind::MissingRegular, - path_rest: vec![], - prev: Some(pb!("/a/b")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::Touch(pb!("/a/b/c")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - pb!("/a/b") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: Some(pb!("/a/b/c")), - }, - pb!("/a/b/c") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/a/b")), - next: None, - }, - }, - events: vec![NotifyEvent::appeared(pb!("/a/b/c"))], }, - Step { action: StepAction::RmRF(pb!("/a/b/c")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - pb!("/a/b") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: Some(pb!("/a/b/c")), - }, - pb!("/a/b/c") => PathState { - kind: PathKind::MissingRegular, - path_rest: vec![], - prev: Some(pb!("/a/b")), - next: None, - }, - }, - events: vec![NotifyEvent::disappeared(pb!("/a/b/c"))], }, - Step { action: StepAction::RmRF(pb!("/a/b")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::RmRF(pb!("/a")), - dirs: hm! { - pb!("/") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: None, - }, - }, - events: vec![], },], }, + // Step 0 + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: None, + }, + }, + events: vec![], }, + // step 1 + TestStep { action: StepAction::MkdirP(pb!("/a")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: None, + }, + }, + events: vec![], }, + // step 2 + TestStep { action: StepAction::MkdirP(pb!("/a/b")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + pb!("/a/b") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: Some(pb!("/a/b/c")), + }, + pb!("/a/b/c") => PathState { + kind: PathKind::MissingRegular, + path_rest: vec![], + prev: Some(pb!("/a/b")), + next: None, + }, + }, + events: vec![], }, + // step 3 + TestStep { action: StepAction::Touch(pb!("/a/b/c")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + pb!("/a/b") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: Some(pb!("/a/b/c")), + }, + pb!("/a/b/c") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/a/b")), + next: None, + }, + }, + events: vec![TestEvent::appeared(pb!("/a/b/c"))], }, + // step 4 + TestStep { action: StepAction::RmRF(pb!("/a/b/c")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + pb!("/a/b") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: Some(pb!("/a/b/c")), + }, + pb!("/a/b/c") => PathState { + kind: PathKind::MissingRegular, + path_rest: vec![], + prev: Some(pb!("/a/b")), + next: None, + }, + }, + events: vec![TestEvent::disappeared(pb!("/a/b/c"))], }, + // step 5 + TestStep { action: StepAction::RmRF(pb!("/a/b")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: None, + }, + }, + events: vec![], }, + // step 6 + TestStep { action: StepAction::RmRF(pb!("/a")), + dirs: hm! { + pb!("/") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: None, + }, + }, + events: vec![], },], }, TestCase { name: "Quick remove directories/files", init: Init { path: Some(pb!("/a/b/c")), commands: vec![InitCommand::MkdirP(pb!("/a/b")), InitCommand::Touch(pb!("/a/b/c")),], initial_file: Some(pb!("/a/b/c")), }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - pb!("/a/b") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: Some(pb!("/a/b/c")), - }, - pb!("/a/b/c") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/a/b")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::RmRF(pb!("/a")), - dirs: hm! { - pb!("/") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: None, - }, - }, - events: vec![NotifyEvent::disappeared(pb!("/a/b/c"))], },], }, + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + pb!("/a/b") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: Some(pb!("/a/b/c")), + }, + pb!("/a/b/c") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/a/b")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::RmRF(pb!("/a")), + dirs: hm! { + pb!("/") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: None, + }, + }, + events: vec![TestEvent::disappeared(pb!("/a/b/c"))], },], }, TestCase { name: "Basic symlink tracking", init: Init { path: Some(pb!("/a")), commands: vec![InitCommand::Touch(pb!("/1")), @@ -1855,125 +2021,125 @@ mod tests { InitCommand::LnS(pb!("3"), pb!("/tmp/link")),], initial_file: Some(pb!("/1")), }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 5, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: None, - next: Some(pb!("/s1")), - }, - pb!("/s1") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/a")), - next: Some(pb!("/s2")), - }, - pb!("/s2") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/s1")), - next: Some(pb!("/s3")), - }, - pb!("/s3") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/s2")), - next: Some(pb!("/1")), - }, - pb!("/1") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/s3")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::RmRF(pb!("/s2")), - dirs: hm! { - pb!("/") => 3, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: None, - next: Some(pb!("/s1")), - }, - pb!("/s1") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/a")), - next: Some(pb!("/s2")), - }, - pb!("/s2") => PathState { - kind: PathKind::MissingRegular, - path_rest: vec![], - prev: Some(pb!("/s1")), - next: None, - }, - }, - events: vec![NotifyEvent::disappeared(pb!("/1"))], }, - Step { action: StepAction::LnS(pb!("/2"), pb!("/s2")), - dirs: hm! { - pb!("/") => 4, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: None, - next: Some(pb!("/s1")), - }, - pb!("/s1") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/a")), - next: Some(pb!("/s2")), - }, - pb!("/s2") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/s1")), - next: Some(pb!("/2")), - }, - pb!("/2") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/s2")), - next: None, - }, - }, - events: vec![NotifyEvent::appeared(pb!("/2"))], }, - Step { action: StepAction::Mv(pb!("/tmp/link"), pb!("/s1")), - dirs: hm! { - pb!("/") => 3, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: None, - next: Some(pb!("/s1")), - }, - pb!("/s1") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: Some(pb!("/a")), - next: Some(pb!("/3")), - }, - pb!("/3") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/s1")), - next: None, - }, - }, - events: vec![NotifyEvent::disappeared(pb!("/2")), - NotifyEvent::appeared(pb!("/3")),], },], }, + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 5, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: None, + next: Some(pb!("/s1")), + }, + pb!("/s1") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/a")), + next: Some(pb!("/s2")), + }, + pb!("/s2") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/s1")), + next: Some(pb!("/s3")), + }, + pb!("/s3") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/s2")), + next: Some(pb!("/1")), + }, + pb!("/1") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/s3")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::RmRF(pb!("/s2")), + dirs: hm! { + pb!("/") => 3, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: None, + next: Some(pb!("/s1")), + }, + pb!("/s1") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/a")), + next: Some(pb!("/s2")), + }, + pb!("/s2") => PathState { + kind: PathKind::MissingRegular, + path_rest: vec![], + prev: Some(pb!("/s1")), + next: None, + }, + }, + events: vec![TestEvent::disappeared(pb!("/1"))], }, + TestStep { action: StepAction::LnS(pb!("/2"), pb!("/s2")), + dirs: hm! { + pb!("/") => 4, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: None, + next: Some(pb!("/s1")), + }, + pb!("/s1") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/a")), + next: Some(pb!("/s2")), + }, + pb!("/s2") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/s1")), + next: Some(pb!("/2")), + }, + pb!("/2") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/s2")), + next: None, + }, + }, + events: vec![TestEvent::appeared(pb!("/2"))], }, + TestStep { action: StepAction::Mv(pb!("/tmp/link"), pb!("/s1")), + dirs: hm! { + pb!("/") => 3, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: None, + next: Some(pb!("/s1")), + }, + pb!("/s1") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: Some(pb!("/a")), + next: Some(pb!("/3")), + }, + pb!("/3") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/s1")), + next: None, + }, + }, + events: vec![TestEvent::disappeared(pb!("/2")), + TestEvent::appeared(pb!("/3")),], },], }, TestCase { name: "Emulate Kubernetes ConfigMap", init: Init { path: Some(pb!("/a")), commands: vec![InitCommand::MkdirP(pb!("/old")), @@ -1988,116 +2154,117 @@ mod tests { InitCommand::LnS(pb!("new"), pb!("/tmp/link")),], initial_file: Some(pb!("/old/a")), }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 3, - pb!("/old") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: None, - next: Some(pb!("/data")), - }, - pb!("/data") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("a")], - prev: Some(pb!("/a")), - next: Some(pb!("/old")), - }, - pb!("/old") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("a")], - prev: Some(pb!("/data")), - next: Some(pb!("/old/a")), - }, - pb!("/old/a") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/old")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::Mv(pb!("/tmp/link"), pb!("/data")), - dirs: hm! { - pb!("/") => 3, - pb!("/new") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Symlink, - path_rest: vec![], - prev: None, - next: Some(pb!("/data")), - }, - pb!("/data") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("a")], - prev: Some(pb!("/a")), - next: Some(pb!("/new")), - }, - pb!("/new") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("a")], - prev: Some(pb!("/data")), - next: Some(pb!("/new/a")), - }, - pb!("/new/a") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/new")), - next: None, - }, - }, - events: vec![NotifyEvent::disappeared(pb!("/old/a")), - NotifyEvent::appeared(pb!("/new/a")),], },], }, + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 3, + pb!("/old") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: None, + next: Some(pb!("/data")), + }, + pb!("/data") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("a")], + prev: Some(pb!("/a")), + next: Some(pb!("/old")), + }, + pb!("/old") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("a")], + prev: Some(pb!("/data")), + next: Some(pb!("/old/a")), + }, + pb!("/old/a") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/old")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::Mv(pb!("/tmp/link"), + pb!("/data")), + dirs: hm! { + pb!("/") => 3, + pb!("/new") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Symlink, + path_rest: vec![], + prev: None, + next: Some(pb!("/data")), + }, + pb!("/data") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("a")], + prev: Some(pb!("/a")), + next: Some(pb!("/new")), + }, + pb!("/new") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("a")], + prev: Some(pb!("/data")), + next: Some(pb!("/new/a")), + }, + pb!("/new/a") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/new")), + next: None, + }, + }, + events: vec![TestEvent::disappeared(pb!("/old/a")), + TestEvent::appeared(pb!("/new/a")),], },], }, TestCase { name: "Symlink loop, pointing to itself", init: Init { path: Some(pb!("/a/b/c")), commands: vec![InitCommand::MkdirP(pb!("/a")), InitCommand::LnS(pb!("b"), pb!("/a/b")),], initial_file: None, }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::RmRF(pb!("/a/b")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: None, - }, - }, - events: vec![], },], }, + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::RmRF(pb!("/a/b")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: None, + }, + }, + events: vec![], },], }, TestCase { name: "Dropping looping symlink and adding a new one instead", init: Init { path: Some(pb!("/a/b/c/d")), commands: vec![InitCommand::MkdirP(pb!("/a")), @@ -2106,144 +2273,144 @@ mod tests { InitCommand::LnS(pb!("/a/b/c"), pb!("/x/c")),], initial_file: None, }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 2, - pb!("/a") => 1, - pb!("/x") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c"), os!("d")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a")), - next: Some(pb!("/x")), - }, - pb!("/x") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a/b")), - next: Some(pb!("/x/c")), - }, - pb!("/x/c") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("d")], - prev: Some(pb!("/x")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::RmRF(pb!("/x/c")), - dirs: hm! { - pb!("/") => 2, - pb!("/a") => 1, - pb!("/x") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c"), os!("d")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a")), - next: Some(pb!("/x")), - }, - pb!("/x") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a/b")), - next: Some(pb!("/x/c")), - }, - pb!("/x/c") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("d")], - prev: Some(pb!("/x")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::Touch(pb!("/x/d")), - dirs: hm! { - pb!("/") => 2, - pb!("/a") => 1, - pb!("/x") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c"), os!("d")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a")), - next: Some(pb!("/x")), - }, - pb!("/x") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a/b")), - next: Some(pb!("/x/c")), - }, - pb!("/x/c") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("d")], - prev: Some(pb!("/x")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::LnS(pb!("."), pb!("/x/c")), - dirs: hm! { - pb!("/") => 2, - pb!("/a") => 1, - pb!("/x") => 2, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c"), os!("d")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a")), - next: Some(pb!("/x")), - }, - pb!("/x") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a/b")), - next: Some(pb!("/x/c")), - }, - pb!("/x/c") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("d")], - prev: Some(pb!("/x")), - next: Some(pb!("/x/d")), - }, - pb!("/x/d") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/x/c")), - next: None, - }, - }, - events: vec![NotifyEvent::appeared(pb!("/x/d"))], },], }, + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 2, + pb!("/a") => 1, + pb!("/x") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c"), os!("d")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a")), + next: Some(pb!("/x")), + }, + pb!("/x") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a/b")), + next: Some(pb!("/x/c")), + }, + pb!("/x/c") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("d")], + prev: Some(pb!("/x")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::RmRF(pb!("/x/c")), + dirs: hm! { + pb!("/") => 2, + pb!("/a") => 1, + pb!("/x") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c"), os!("d")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a")), + next: Some(pb!("/x")), + }, + pb!("/x") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a/b")), + next: Some(pb!("/x/c")), + }, + pb!("/x/c") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("d")], + prev: Some(pb!("/x")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::Touch(pb!("/x/d")), + dirs: hm! { + pb!("/") => 2, + pb!("/a") => 1, + pb!("/x") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c"), os!("d")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a")), + next: Some(pb!("/x")), + }, + pb!("/x") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a/b")), + next: Some(pb!("/x/c")), + }, + pb!("/x/c") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("d")], + prev: Some(pb!("/x")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::LnS(pb!("."), pb!("/x/c")), + dirs: hm! { + pb!("/") => 2, + pb!("/a") => 1, + pb!("/x") => 2, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c"), os!("d")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a")), + next: Some(pb!("/x")), + }, + pb!("/x") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a/b")), + next: Some(pb!("/x/c")), + }, + pb!("/x/c") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("d")], + prev: Some(pb!("/x")), + next: Some(pb!("/x/d")), + }, + pb!("/x/d") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/x/c")), + next: None, + }, + }, + events: vec![TestEvent::appeared(pb!("/x/d"))], },], }, TestCase { name: "Rewiring symlink loop", init: Init { path: Some(pb!("/a/b/c/d")), commands: vec![InitCommand::MkdirP(pb!("/a")), @@ -2256,130 +2423,131 @@ mod tests { InitCommand::LnS(pb!("."), pb!("/tmp/link")),], initial_file: None, }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 2, - pb!("/a") => 1, - pb!("/x") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c"), os!("d")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a")), - next: Some(pb!("/x")), - }, - pb!("/x") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a/b")), - next: Some(pb!("/x/c")), - }, - pb!("/x/c") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("d")], - prev: Some(pb!("/x")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::Mv(pb!("/tmp/link"), pb!("/x/c")), - dirs: hm! { - pb!("/") => 2, - pb!("/a") => 1, - pb!("/x") => 2, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c"), os!("d")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a")), - next: Some(pb!("/x")), - }, - pb!("/x") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c"), os!("d")], - prev: Some(pb!("/a/b")), - next: Some(pb!("/x/c")), - }, - pb!("/x/c") => PathState { - kind: PathKind::Symlink, - path_rest: vec![os!("d")], - prev: Some(pb!("/x")), - next: Some(pb!("/x/d")), - }, - pb!("/x/d") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/x/c")), - next: None, - }, - }, - events: vec![NotifyEvent::appeared(pb!("/x/d"))], },], }, + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 2, + pb!("/a") => 1, + pb!("/x") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c"), os!("d")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a")), + next: Some(pb!("/x")), + }, + pb!("/x") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a/b")), + next: Some(pb!("/x/c")), + }, + pb!("/x/c") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("d")], + prev: Some(pb!("/x")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::Mv(pb!("/tmp/link"), + pb!("/x/c")), + dirs: hm! { + pb!("/") => 2, + pb!("/a") => 1, + pb!("/x") => 2, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c"), os!("d")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a")), + next: Some(pb!("/x")), + }, + pb!("/x") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c"), os!("d")], + prev: Some(pb!("/a/b")), + next: Some(pb!("/x/c")), + }, + pb!("/x/c") => PathState { + kind: PathKind::Symlink, + path_rest: vec![os!("d")], + prev: Some(pb!("/x")), + next: Some(pb!("/x/d")), + }, + pb!("/x/d") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/x/c")), + next: None, + }, + }, + events: vec![TestEvent::appeared(pb!("/x/d"))], },], }, TestCase { name: "Moving a directory", init: Init { path: Some(pb!("/a/b/c")), commands: vec![InitCommand::MkdirP(pb!("/a/b")), InitCommand::Touch(pb!("/a/b/c")),], initial_file: Some(pb!("/a/b/c")), }, - steps: vec![Step { action: StepAction::Nop, - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - pb!("/a/b") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: Some(pb!("/a/b/c")), - }, - pb!("/a/b/c") => PathState { - kind: PathKind::Regular, - path_rest: vec![], - prev: Some(pb!("/a/b")), - next: None, - }, - }, - events: vec![], }, - Step { action: StepAction::Mv(pb!("/a/b"), pb!("/a/d")), - dirs: hm! { - pb!("/") => 1, - pb!("/a") => 1, - }, - paths: hm! { - pb!("/a") => PathState { - kind: PathKind::Directory, - path_rest: vec![os!("b"), os!("c")], - prev: None, - next: Some(pb!("/a/b")), - }, - pb!("/a/b") => PathState { - kind: PathKind::MissingDirectory, - path_rest: vec![os!("c")], - prev: Some(pb!("/a")), - next: None, - }, - }, - events: vec![NotifyEvent::disappeared(pb!("/a/b/c"))], },], },] + steps: vec![TestStep { action: StepAction::Nop, + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + pb!("/a/b") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: Some(pb!("/a/b/c")), + }, + pb!("/a/b/c") => PathState { + kind: PathKind::Regular, + path_rest: vec![], + prev: Some(pb!("/a/b")), + next: None, + }, + }, + events: vec![], }, + TestStep { action: StepAction::Mv(pb!("/a/b"), pb!("/a/d")), + dirs: hm! { + pb!("/") => 1, + pb!("/a") => 1, + }, + paths: hm! { + pb!("/a") => PathState { + kind: PathKind::Directory, + path_rest: vec![os!("b"), os!("c")], + prev: None, + next: Some(pb!("/a/b")), + }, + pb!("/a/b") => PathState { + kind: PathKind::MissingDirectory, + path_rest: vec![os!("c")], + prev: Some(pb!("/a")), + next: None, + }, + }, + events: vec![TestEvent::disappeared(pb!("/a/b/c"))], },], },] } fn run_test_case(tc: &TestCase) { @@ -2392,6 +2560,8 @@ mod tests { #[test] fn file_watcher() { + logger::init(); + let lock = lock_env_var(); lock.unset(); @@ -2406,16 +2576,16 @@ mod tests { lock.set("aarch64-darwin"); // When using the PollWatcher variant of SupWatcher, the - // behavior is different than the NotifyWatcher with respect to the + // behavior is different than the Watcher with respect to the // the timing of generated events as well as the number of events // generated. In the first two test cases, there were extraneous - // events generated that are not generated by the NotifyWatcher and + // events generated that are not generated by the Watcher and // these needed to be ignored. In the later test cases, the PollWatcher // failed to generate the expected events and did not pass regardless of // the timing or number of iterations. Since these later test cases are // beyond the scope of our watchers, they were skipped for the PollWatcher. // Also note that the criteria for passing was based on the original - // NotifyWatcher where these events were generated as expected. + // Watcher where these events were generated as expected. let cases = get_test_cases(); let polling_cases = &cases[0..2]; @@ -2428,6 +2598,7 @@ mod tests { // Commands that can be executed at the test case init. // // Tests may come and go, so some of the variants may be unused. + #[derive(Debug)] #[allow(dead_code)] enum InitCommand { MkdirP(PathBuf), @@ -2435,6 +2606,7 @@ mod tests { LnS(PathBuf, PathBuf), } + #[derive(Debug)] // Description of the init phase for test case. struct Init { // The path to the file that will be watched. @@ -2469,7 +2641,7 @@ mod tests { } // Simplified description of the WatchedFile's Common struct. - #[derive(Clone)] + #[derive(Clone, Debug)] struct PathState { kind: PathKind, path_rest: Vec, @@ -2489,30 +2661,31 @@ mod tests { } #[derive(Clone, Copy, Debug, PartialEq)] - enum NotifyEventKind { + enum TestEventKind { Appeared, Modified, Disappeared, } #[derive(Clone, Debug, PartialEq)] - struct NotifyEvent { + struct TestEvent { path: PathBuf, - kind: NotifyEventKind, + kind: TestEventKind, } - impl NotifyEvent { - fn new(path: PathBuf, kind: NotifyEventKind) -> Self { Self { path, kind } } + impl TestEvent { + fn new(path: PathBuf, kind: TestEventKind) -> Self { Self { path, kind } } - fn appeared(path: PathBuf) -> Self { Self::new(path, NotifyEventKind::Appeared) } + fn appeared(path: PathBuf) -> Self { Self::new(path, TestEventKind::Appeared) } - fn modified(path: PathBuf) -> Self { Self::new(path, NotifyEventKind::Modified) } + fn modified(path: PathBuf) -> Self { Self::new(path, TestEventKind::Modified) } - fn disappeared(path: PathBuf) -> Self { Self::new(path, NotifyEventKind::Disappeared) } + fn disappeared(path: PathBuf) -> Self { Self::new(path, TestEventKind::Disappeared) } } // A description of the single step in the test case. - struct Step { + #[derive(Debug)] + struct TestStep { // Action to execute at the beginning of the step. action: StepAction, // Expected watched directories together with the use count, @@ -2524,10 +2697,10 @@ mod tests { // Expected events that happened when executing the step // command. The events map to the `file_*` functions in // `Callbacks` trait. - events: Vec, + events: Vec, } - impl IndentedToString for Step { + impl IndentedToString for TestStep { fn indented_to_string(&self, spaces: &str, repeat: usize) -> String { let mut formatter = IndentedStructFormatter::new("Step", spaces, repeat); formatter.add_debug("action", &self.action); @@ -2538,20 +2711,21 @@ mod tests { } } + #[derive(Debug)] struct TestCase { // Not used directly, but describes the test. Can be used // later for debugging. #[allow(dead_code)] name: &'static str, init: Init, - steps: Vec, + steps: Vec, } // The implementation of `Callbacks` trait for testing purposes. - #[derive(Default)] + #[derive(Debug, Default)] struct TestCallbacks { // A list of events that happened when executing the step. - events: Vec, + events: Vec, // A set of ignored directories. Usually it is just `/` and // `/tmp`. ignored_dirs: HashSet, @@ -2570,21 +2744,23 @@ mod tests { impl Callbacks for TestCallbacks { fn file_appeared(&mut self, real_path: &Path) { - self.events - .push(NotifyEvent::appeared(real_path.to_owned())); + trace!("TestCallbacks::file_appeared({:?})", real_path); + self.events.push(TestEvent::appeared(real_path.to_owned())); } fn file_modified(&mut self, real_path: &Path) { - self.events - .push(NotifyEvent::modified(real_path.to_owned())); + trace!("TestCallbacks::file_modified({:?})", real_path); + self.events.push(TestEvent::modified(real_path.to_owned())); } fn file_disappeared(&mut self, real_path: &Path) { + trace!("TestCallbacks::file_disappeared({:?})", real_path); self.events - .push(NotifyEvent::disappeared(real_path.to_owned())); + .push(TestEvent::disappeared(real_path.to_owned())); } fn event_in_directories(&mut self, paths: &[PathBuf]) { + trace!("TestCallbacks::event_in_directories({:?})", paths); for path in paths { if self.ignored_dirs.contains(path) { debug!("got event in ignored dirs"); @@ -2595,8 +2771,9 @@ mod tests { } } - // The implementation of notify::Watcher trait for testing + // The implementation of Watcher trait for testing // purposes. + #[derive(Debug)] struct TestWatcher { // The real watcher that does the grunt work. real_watcher: SupWatcher, @@ -2607,36 +2784,46 @@ mod tests { } impl Watcher for TestWatcher { - fn new_raw(tx: Sender) -> notify::Result { - Ok(TestWatcher { real_watcher: SupWatcher::new_raw(tx)?, - watched_dirs: HashSet::new(), }) - } - - fn new(tx: Sender, d: Duration) -> notify::Result { - Ok(TestWatcher { real_watcher: SupWatcher::new(tx, d)?, + fn new(event_handler: F, config: Config) -> notify::Result { + Ok(TestWatcher { real_watcher: SupWatcher::new(event_handler, config)?, watched_dirs: HashSet::new(), }) } - fn watch>(&mut self, path: P, mode: RecursiveMode) -> notify::Result<()> { - if !self.watched_dirs.insert(path.as_ref().to_owned()) { + fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> notify::Result<()> { + if !self.watched_dirs.insert(path.to_path_buf()) { panic!("Trying to watch a path {} we are already watching", - path.as_ref().display(),); + path.display()); } - if mode == RecursiveMode::Recursive { + if recursive_mode == RecursiveMode::Recursive { panic!("Recursive watch should not ever happen"); } - self.real_watcher.watch(path, mode) + self.real_watcher.watch(path, recursive_mode) } - fn unwatch>(&mut self, path: P) -> notify::Result<()> { - if !self.watched_dirs.remove(&path.as_ref().to_owned()) { + fn unwatch(&mut self, path: &Path) -> notify::Result<()> { + if !self.watched_dirs.remove(path) { panic!("Trying to unwatch a path {} we were not watching", - path.as_ref().display(),); + path.display()); } self.real_watcher.unwatch(path) } + + // For now we are using the default implementation of configure() provided + // by the notify crate which returns Ok(false) signalling that runtime + // configuration is not supported. + + fn kind() -> WatcherKind + where Self: Sized + { + unimplemented!("Currently lacking a good way to report the WatcherKind") + // JAH: self.kind() would be spiffy if we were a method + } } + // JAH: (1) If this is going to exist then why not just a field inside of the TestWatcher since + // that exists? (2) This, and all dits!/its!/IndentedToString stuff could probably be replaced + // by format!/{:#?} + #[derive(Debug)] struct DebugInfo { logs_per_level: Vec>, } @@ -2667,6 +2854,7 @@ mod tests { } } + #[derive(Debug)] struct WatcherSetup { init_path: PathBuf, watcher: FileWatcher, @@ -2674,6 +2862,7 @@ mod tests { // Structure used for executing the initial commands and step // actions. + #[derive(Debug)] struct FsOps<'a> { debug_info: &'a mut DebugInfo, root: &'a PathBuf, @@ -2765,15 +2954,14 @@ mod tests { match (self.parent_is_watched(&ff), self.parent_is_watched(&tt)) { (true, true) | (true, false) => { if self.path_is_watched(&ff) { - // Since we are watching both moved path and - // its parent, we are going to receive double - // notice remove event followed by the rename - // event. - 3 - } else { - // Two events - notice remove, and rename or - // remove. + // Since we are watching both moved path and its parent, we are going to + // receive double notice remove event followed by the rename event. 2 + // JAH: In v5 as it stands there are no notice events so changed from 3 to 2 + } else { + // Two events - notice remove, and rename or remove. + 1 + // JAH: In v5 as it stands there are no notice events so changed from 2 to 1 } } (false, true) => { @@ -2820,6 +3008,9 @@ mod tests { self.debug_info,) }); } + debug!("FsOps::rm_rf(&mut self, &Path) -> \ + u32\nself:{:#?}\nPath:{:#?}\nu32/event_count:{:#?}\n", + self, path, event_count); event_count } @@ -2831,9 +3022,9 @@ mod tests { if !self.parent_is_watched(&path) { continue; } - // Two events for each deletion in the watched path - - // remove notice and remove. - event_count += 2; + // Two events for each deletion in the watched path - remove notice and remove. + event_count += 1; + // JAH: In v5 as it stands there are no notice events so changed from 2 to 1 let metadata = match path.symlink_metadata() { Ok(m) => m, Err(err) => { @@ -2896,6 +3087,7 @@ mod tests { } } + #[derive(Debug)] struct TestCaseRunner { debug_info: DebugInfo, // We don't use this field anywhere, but it will drop the temp @@ -2950,7 +3142,8 @@ mod tests { fn run_steps(&mut self, mut setup: WatcherSetup, tc_initial_file: &Option, - steps: &[Step]) { + steps: &[TestStep]) { + trace!("\n================\nTEST=CASE=BEGINS\n================"); let mut initial_file = tc_initial_file.clone(); let mut actual_initial_file = setup.watcher.initial_real_file.clone(); @@ -2960,6 +3153,8 @@ mod tests { }; for (step_idx, step) in steps.iter().enumerate() { + trace!("++++++++++++++++\n++BEGIN Step {:?}++\n++++++++++++++++", + step_idx); self.debug_info.push_level(); self.debug_info .add(format!("step {}:\n{}", step_idx, dits!(step))); @@ -2982,10 +3177,10 @@ mod tests { &mut setup.watcher.get_mut_callbacks().events); } self.debug_info.pop_level(); - debug!("\n\n\n++++++++++++++++\n++++STEP+END++++\n++++++++++++++++\n\n\n"); + debug!("\n++++++++++++++++\n++++STEP+END++++\n++++++++++++++++"); } - debug!("\n\n\n================\n=TEST=CASE=ENDS=\n================\n\n\n"); + debug!("\n================\n=TEST=CASE=ENDS=\n================"); } fn execute_step_action(&mut self, setup: &mut WatcherSetup, action: &StepAction) -> u32 { @@ -3018,8 +3213,8 @@ mod tests { let mut iterations = expected_event_count; // Through experimentation it was determined that the PollWatcher is less responsive - // and emits more events than the NotifyWatcher. The initial sleep used in - // NotifyWatcher was not adequate to pass the tests and was increased as a + // and emits more events than the Watcher. The initial sleep used in + // Watcher was not adequate to pass the tests and was increased as a // result. Also, the number of iterations required is larger for the // PollWatcher case as there were intermediate events observed that would // lead to test case failure with the original iteration count used. @@ -3095,8 +3290,8 @@ mod tests { // determine the final state of the watched. fn test_events_polling(&mut self, real_initial_file: Option, - step_events: &[NotifyEvent], - actual_events: &mut Vec) { + step_events: &[TestEvent], + actual_events: &mut Vec) { let expected_events = self.fixup_expected_events(step_events, real_initial_file); self.debug_info .add(format!("fixed up expected events: {:?}", expected_events)); @@ -3109,8 +3304,8 @@ mod tests { fn test_events(&mut self, real_initial_file: Option, - step_events: &[NotifyEvent], - actual_events: &mut Vec) { + step_events: &[TestEvent], + actual_events: &mut Vec) { let expected_events = self.fixup_expected_events(step_events, real_initial_file); self.debug_info .add(format!("fixed up expected events: {:?}", expected_events)); @@ -3129,7 +3324,7 @@ mod tests { } // For running watcher tests, this requires a WatcherType so we can - // delineate between the NotifyWatcher and PollWatcher specific behaviors. + // delineate between the Watcher and PollWatcher specific behaviors. fn get_fs_ops_with_dirs<'a>(&'a mut self, watched_dirs: &'a HashSet) -> FsOps<'a> { let mut fs_ops = self.get_fs_ops_init(); fs_ops.watched_dirs = Some(watched_dirs); @@ -3216,16 +3411,16 @@ mod tests { } fn fixup_expected_events(&self, - events: &[NotifyEvent], + events: &[TestEvent], real_initial_file: Option) - -> Vec { + -> Vec { let mut expected_events = match real_initial_file { - Some(path) => vec![NotifyEvent::appeared(path)], + Some(path) => vec![TestEvent::appeared(path)], None => Vec::new(), }; expected_events.extend(events.iter() .map(|e| { - NotifyEvent::new(self.prepend_root(&e.path), e.kind) + TestEvent::new(self.prepend_root(&e.path), e.kind) })); expected_events } diff --git a/components/sup/src/manager/spec_watcher.rs b/components/sup/src/manager/spec_watcher.rs index 6668fd2bc5..0618c199fb 100644 --- a/components/sup/src/manager/spec_watcher.rs +++ b/components/sup/src/manager/spec_watcher.rs @@ -8,7 +8,8 @@ use crate::{error::{Error, sup_watcher::SupWatcher}}; use log::{error, trace}; -use notify::{DebouncedEvent, +use notify::{Config, + Event, RecursiveMode, Watcher}; use std::{sync::mpsc::{self, @@ -45,7 +46,7 @@ pub struct SpecWatcher { // purposes (`Drop` kills the threads that the watcher spawns to do // its work). _watcher: SupWatcher, - channel: Receiver, + channel: Receiver>, } impl SpecWatcher { @@ -89,8 +90,9 @@ impl SpecWatcher { fn new(spec_dir: &SpecDir) -> Result { let (tx, rx) = mpsc::channel(); let delay = SpecWatcherDelay::configured_value(); - let mut watcher = SupWatcher::new(tx, delay.0)?; - watcher.watch(spec_dir, RecursiveMode::NonRecursive)?; + let config = Config::default().with_poll_interval(delay.0); + let mut watcher = SupWatcher::new(tx, config)?; + watcher.watch(spec_dir.as_ref(), RecursiveMode::NonRecursive)?; Ok(SpecWatcher { _watcher: watcher, channel: rx, }) } diff --git a/components/sup/src/manager/sup_watcher.rs b/components/sup/src/manager/sup_watcher.rs index 8d0fc235d2..7504c47b71 100644 --- a/components/sup/src/manager/sup_watcher.rs +++ b/components/sup/src/manager/sup_watcher.rs @@ -1,63 +1,60 @@ //! Watcher interface implementation for Habitat Supervisor. - use habitat_core::package::target::{PackageTarget, AARCH64_DARWIN}; -use log::debug; use notify::{poll::PollWatcher, - DebouncedEvent, + Config, + EventHandler, RecommendedWatcher, RecursiveMode, Result, - Watcher}; + Watcher, + WatcherKind}; use std::{env, path::Path, - str::FromStr, - sync::mpsc::Sender, - time::Duration}; + str::FromStr}; +#[derive(Debug)] pub enum SupWatcher { Native(RecommendedWatcher), Fallback(PollWatcher), } impl Watcher for SupWatcher { - fn new_raw(tx: Sender) -> Result { - let target = PackageTarget::from_str(&env::var("HAB_STUDIO_HOST_ARCH"). - unwrap_or_default()). - unwrap_or_else(|_| PackageTarget::active_target()); - if target == AARCH64_DARWIN { - Ok(SupWatcher::Fallback(PollWatcher::new_raw(tx).unwrap())) - } else { - Ok(SupWatcher::Native(RecommendedWatcher::new_raw(tx).unwrap())) - } - } - - fn new(tx: Sender, delay: Duration) -> Result { + fn new(event_handler: F, config: Config) -> Result { let target = PackageTarget::from_str(&env::var("HAB_STUDIO_HOST_ARCH"). unwrap_or_default()). unwrap_or_else(|_| PackageTarget::active_target()); if target == AARCH64_DARWIN { - debug!("Using pollwatcher"); - Ok(SupWatcher::Fallback(PollWatcher::new(tx, delay).unwrap())) + Ok(SupWatcher::Fallback(PollWatcher::new(event_handler, config).unwrap())) } else { - debug!("Using native watcher"); - Ok(SupWatcher::Native(RecommendedWatcher::new(tx, delay).unwrap())) + Ok(SupWatcher::Native(RecommendedWatcher::new(event_handler, config).unwrap())) } } - fn watch>(&mut self, path: P, recursive_mode: RecursiveMode) -> Result<()> { + fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> { match self { SupWatcher::Native(watcher) => watcher.watch(path, recursive_mode), SupWatcher::Fallback(watcher) => watcher.watch(path, recursive_mode), } } - fn unwatch>(&mut self, path: P) -> Result<()> { + fn unwatch(&mut self, path: &Path) -> Result<()> { match self { SupWatcher::Native(watcher) => watcher.unwatch(path), SupWatcher::Fallback(watcher) => watcher.unwatch(path), } } + + // For now we are using the default implementation of configure() provided + // by the notify crate which returns Ok(false) signalling that runtime + // configuration is not supported. + + fn kind() -> WatcherKind + where Self: Sized + { + unimplemented!("Don't have a good way to accurate report the watcher in use at this level") + // NOTE: self.kind() would be spiffy if we were a method + } } #[cfg(test)] @@ -73,11 +70,12 @@ mod test { fn sup_watcher_constructor_test_polling() { let (sender, _) = channel(); let delay = Duration::from_millis(1000); + let config = Config::default().with_poll_interval(delay); let lock = lock_env_var(); lock.set("aarch64-darwin"); - let _sup_watcher = SupWatcher::new(sender, delay); + let _sup_watcher = SupWatcher::new(sender, config); let watcher_type = match _sup_watcher { Ok(SupWatcher::Native(_sup_watcher)) => "Native", Ok(SupWatcher::Fallback(_sup_watcher)) => "Fallback", @@ -93,11 +91,12 @@ mod test { fn sup_watcher_constructor_test_notify() { let (sender, _) = channel(); let delay = Duration::from_millis(1000); + let config = Config::default().with_poll_interval(delay); let lock = lock_env_var(); lock.unset(); - let _sup_watcher = SupWatcher::new(sender, delay); + let _sup_watcher = SupWatcher::new(sender, config); let watcher_type = match _sup_watcher { Ok(SupWatcher::Native(_sup_watcher)) => "Native", Ok(SupWatcher::Fallback(_sup_watcher)) => "Fallback",