From 79210f9e7d77dbe8a542018b9602be49c54054f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 11:51:17 +1200 Subject: [PATCH 1/9] Copy windows test from v4 --- tests/windows.rs | 156 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 tests/windows.rs diff --git a/tests/windows.rs b/tests/windows.rs new file mode 100644 index 00000000..1a054b78 --- /dev/null +++ b/tests/windows.rs @@ -0,0 +1,156 @@ +#![cfg(target_os = "windows")] +#![allow(dead_code)] + +use std::{ + sync::{Arc, Mutex}, + thread, + time::{Duration, Instant}, +}; + +use crossbeam_channel::unbounded; +use notify::{ + windows::{MetaEvent, ReadDirectoryChangesWatcher}, + Event, RecursiveMode, Result, Watcher, +}; +use tempfile::TempDir; + +fn wait_for_disconnect(rx: &crossbeam_channel::Receiver) { + loop { + if let Err(crossbeam_channel::TryRecvError::Disconnected) = rx.try_recv() { + break; + } + thread::sleep(Duration::from_millis(10)); + } +} + +#[test] +fn shutdown() { + // create a watcher for N directories. start the watcher, then shut it down. + // inspect the watcher to make sure that it received final callbacks for all + // N watchers. + let dir_count = 100; + + // to get meta events, we have to pass in the meta channel + let (meta_tx, meta_rx) = unbounded(); + + // hook a channel to the watcher + let (tx, rx) = unbounded(); + + { + let mut dirs: Vec = Vec::new(); + let mut w = ReadDirectoryChangesWatcher::create( + Arc::new(Mutex::new(move |er: Result| { + tx.send(er.unwrap()).unwrap(); + })), + meta_tx, + ) + .unwrap(); + + for _ in 0..dir_count { + let d = tempfile::Builder::new() + .prefix("rsnotifytest") + .tempdir() + .expect("failed to create temporary directory"); + dirs.push(d); + } + + // need the ref, otherwise it's a move and the dir will be dropped! + for d in &dirs { + w.watch(d.path(), RecursiveMode::Recursive).unwrap(); + } + + // unwatch half of the directories, let the others get stopped when we go out of scope + for d in &dirs[0..dir_count / 2] { + w.unwatch(d.path()).unwrap(); + } + + thread::sleep(Duration::from_millis(2000)); // sleep to unhook the watches + } + + wait_for_disconnect(&rx); + + const TIMEOUT_MS: u64 = 60000; // give it PLENTY of time before we declare failure + let start = Instant::now(); + + let mut watchers_shutdown = 0; + while watchers_shutdown != dir_count && start.elapsed() < Duration::from_millis(TIMEOUT_MS) { + if let Ok(actual) = meta_rx.try_recv() { + match actual { + MetaEvent::SingleWatchComplete => watchers_shutdown += 1, + _ => (), + } + } + thread::sleep(Duration::from_millis(1)); // don't burn cpu, can take some time for completion events to fire + } + + assert_eq!(dir_count, watchers_shutdown); +} + +#[test] +fn watch_server_can_be_awakened() { + // hook a channel to the watcher + let (tx, _) = unbounded(); + + let (meta_tx, meta_rx) = unbounded(); + let mut w = ReadDirectoryChangesWatcher::create( + Arc::new(Mutex::new(move |er: Result| { + tx.send(er.unwrap()).unwrap(); + })), + meta_tx, + ) + .unwrap(); + + let d = tempfile::Builder::new() + .prefix("rsnotifytest") + .tempdir() + .expect("failed to create temporary directory"); + w.watch(d.path(), RecursiveMode::Recursive).unwrap(); + + // should be at least one awaken in there + const TIMEOUT_MS: u64 = 5000; + let start = Instant::now(); + + let mut awakened = false; + while !awakened && start.elapsed() < Duration::from_millis(TIMEOUT_MS) { + if let Ok(actual) = meta_rx.try_recv() { + match actual { + MetaEvent::WatcherAwakened => awakened = true, + _ => (), + } + } + thread::sleep(Duration::from_millis(50)); + } + + assert!(awakened); +} + +#[test] +#[ignore] +#[cfg(feature = "manual_tests")] +// repeatedly watch and unwatch a directory; make sure process memory does not increase. +// you use task manager to watch the memory; it will fluctuate a bit, but should not leak overall +fn memtest_manual() { + let mut i = 0; + loop { + let (tx, rx) = unbounded(); + let d = tempfile::Builder::new() + .prefix("rsnotifytest") + .tempdir() + .expect("failed to create temporary directory"); + { + let (meta_tx, _) = unbounded(); + let mut w = ReadDirectoryChangesWatcher::create( + Arc::new(Mutex::new(move |er: Result| { + tx.send(er.unwrap()).unwrap(); + })), + meta_tx, + ) + .unwrap(); + w.watch(d.path(), RecursiveMode::Recursive).unwrap(); + thread::sleep(Duration::from_millis(1)); // this should make us run pretty hot but not insane + } + wait_for_disconnect(&rx); + i += 1; + println!("memtest {}", i); + } +} From 3fc5a318c2741fb293b6fd86ac14633d57f12cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 12:36:40 +1200 Subject: [PATCH 2/9] Copy notify test from v4 --- tests/notify.rs | 1307 ++++++++++++++++++++++++++++++++++++++++++++ tests/utils/mod.rs | 234 ++++++++ 2 files changed, 1541 insertions(+) create mode 100644 tests/notify.rs create mode 100644 tests/utils/mod.rs diff --git a/tests/notify.rs b/tests/notify.rs new file mode 100644 index 00000000..fb35c0f5 --- /dev/null +++ b/tests/notify.rs @@ -0,0 +1,1307 @@ +use crossbeam_channel::unbounded; +use notify::*; + +mod utils; +use utils::*; + +const TEMP_DIR: &str = "temp_dir"; + +#[test] +fn create_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + // macOS FsEvent needs some time to discard old events from its log. + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + } else if cfg!(target_os = "windows") { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + )] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } +} + +#[test] +fn write_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.write("file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive create event + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ] + ); + } else if cfg!(target_os = "windows") { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + )] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } +} + +#[test] +#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)] +fn modify_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.chmod("file1"); + + sleep_macos(10); + + if cfg!(target_os = "windows") { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); + panic!("windows cannot distinguish between chmod and write"); + } else if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + panic!("macos cannot distinguish between chmod and create"); + } else { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ),] + ); + } +} + +#[test] +fn delete_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.remove("file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive create event + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn rename_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("file1a", "file1b"); + + if cfg!(target_os = "macos") { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), // excessive create event + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ) + ] + ); + } else { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ) + ] + ); + } +} + +#[test] +#[cfg_attr(target_os = "macos", ignore)] +fn move_out_create_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir/file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("watch_dir/file1", "file1b"); + tdir.create("watch_dir/file1"); + + if cfg!(target_os = "macos") { + // fsevent interprets a move_out as a rename event + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + panic!("move event should be a remove event; fsevent conflates rename and create events"); + } else if cfg!(target_os = "linux") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)] +fn create_write_modify_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("file1"); + tdir.write("file1"); + tdir.chmod("file1"); + + if cfg!(target_os = "windows") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ] + ); + panic!("windows cannot distinguish between chmod and write"); + } else if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ] + ); + panic!("macos cannot distinguish between chmod and create"); + } else if cfg!(target_os = "linux") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ] + ); + } +} + +#[test] +fn create_rename_overwrite_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1b"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("file1a"); + tdir.rename("file1a", "file1b"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "windows") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1b"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(1) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(1) + ) + ] + ); + } else if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ( + tdir.mkpath("file1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + } else { + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1a"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ) + ] + ); + } +} + +#[test] +fn rename_rename_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("file1a", "file1b"); + tdir.rename("file1b", "file1c"); + + if cfg!(target_os = "macos") { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ) + ] + ); + } else { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ) + ] + ); + } +} + +#[test] +fn create_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1"); + + let actual = if cfg!(target_os = "macos") { + recv_events(&rx) + } else { + recv_events(&rx) + }; + + assert_eq!( + actual, + vec![( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); +} + +// https://github.com/passcod/notify/issues/124 +#[test] +#[cfg_attr(target_os = "macos", ignore)] +fn create_directory_watch_subdirectories() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1"); + tdir.create("dir1/dir2"); + + sleep(100); + + tdir.create("dir1/dir2/file1"); + + let actual = if cfg!(target_os = "macos") { + recv_events(&rx) + } else { + recv_events(&rx) + }; + + if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/dir2/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/dir2/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ] + ); + } else if cfg!(target_os = "windows") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/dir2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/dir2/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/dir2/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)] +fn modify_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.chmod("dir1"); + + if cfg!(target_os = "windows") { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); + panic!("windows cannot distinguish between chmod and write"); + } else if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + panic!("macos cannot distinguish between chmod and create"); + } else { + // TODO: emit chmod event only once + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ] + ); + } +} + +#[test] +fn delete_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.remove("dir1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive create event + ( + tdir.mkpath("dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn rename_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1a"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("dir1a", "dir1b"); + + if cfg!(target_os = "macos") { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), // excessive create event + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ] + ); + } else { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ] + ); + } +} + +#[test] +#[cfg_attr(target_os = "macos", ignore)] +fn move_out_create_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir/dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("watch_dir/dir1", "dir1b"); + tdir.create("watch_dir/dir1"); + + if cfg!(target_os = "macos") { + // fsevent interprets a move_out as a rename event + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + panic!("move event should be a remove event; fsevent conflates rename and create events"); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +// https://github.com/passcod/notify/issues/124 +// fails consistently on windows, macos -- tbd? +#[test] +#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)] +fn move_in_directory_watch_subdirectories() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir", "dir1/dir2"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("dir1", "watch_dir/dir1"); + + sleep(100); + + tdir.create("watch_dir/dir1/dir2/file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + } else if cfg!(target_os = "linux") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1/dir2/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1/dir2/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("watch_dir/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1/dir2/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +#[cfg_attr(target_os = "windows", ignore)] +fn create_rename_overwrite_directory() { + // overwriting directories doesn't work on windows + if cfg!(target_os = "windows") { + panic!("cannot overwrite directory on windows"); + } + + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1b"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1a"); + tdir.rename("dir1a", "dir1b"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ( + tdir.mkpath("dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + } else if cfg!(target_os = "linux") { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ] + ); + } else { + unimplemented!(); + } +} + +#[test] +fn rename_rename_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1a"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher = recommended_watcher(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("dir1a", "dir1b"); + tdir.rename("dir1b", "dir1c"); + + if cfg!(target_os = "macos") { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ] + ); + } else { + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("dir1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ] + ); + } +} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs new file mode 100644 index 00000000..335af08f --- /dev/null +++ b/tests/utils/mod.rs @@ -0,0 +1,234 @@ +use std::{ + fs, + io::Write, + path::PathBuf, + process, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, + }, + thread, + time::{Duration, Instant}, +}; + +use crossbeam_channel::{Receiver, RecvTimeoutError}; +use notify::*; +use tempfile::TempDir; + +#[cfg(not(target_os = "windows"))] +use std::os::unix::fs::PermissionsExt; + +#[cfg(not(target_os = "windows"))] +const TIMEOUT_MS: u64 = 100; +#[cfg(target_os = "windows")] +const TIMEOUT_MS: u64 = 3000; // windows can take a while + +pub fn recv_events_with_timeout( + rx: &Receiver>, + timeout: Duration, +) -> Vec<(PathBuf, EventKind, Option)> { + let start = Instant::now(); + + let mut evs = Vec::new(); + + while start.elapsed() < timeout { + let time_left = timeout - start.elapsed(); + match rx.recv_timeout(time_left) { + Ok(Ok(ev)) => { + let tracker = ev.tracker(); + let kind = ev.kind; + for path in ev.paths { + evs.push((path, kind.clone(), tracker)); + } + } + Ok(Err(e)) => panic!("unexpected event err: {:?}", e), + Err(RecvTimeoutError::Timeout) => break, + Err(RecvTimeoutError::Disconnected) => panic!("unexpected channel disconnection"), + } + } + evs +} + +pub fn fail_after(test_name: &'static str, duration: Duration) -> impl Drop { + struct SuccessOnDrop(Arc); + impl Drop for SuccessOnDrop { + fn drop(&mut self) { + self.0.store(true, SeqCst) + } + } + + let finished = SuccessOnDrop(Arc::new(AtomicBool::new(false))); + // timeout the test to catch deadlocks + { + let finished = finished.0.clone(); + thread::spawn(move || { + thread::sleep(duration); + if finished.load(SeqCst) == false { + println!("test `{}` timed out", test_name); + process::abort(); + } + }); + } + finished +} + +pub fn recv_events(rx: &Receiver>) -> Vec<(PathBuf, EventKind, Option)> { + recv_events_with_timeout(rx, Duration::from_millis(TIMEOUT_MS)) +} + +pub fn extract_cookies(events: &[(PathBuf, EventKind, Option)]) -> Vec { + let mut cookies = Vec::new(); + for &(_, _, e_c) in events { + if let Some(cookie) = e_c { + if !cookies.contains(&cookie) { + cookies.push(cookie); + } + } + } + cookies +} + +// Sleep for `duration` in milliseconds +pub fn sleep(duration: u64) { + thread::sleep(Duration::from_millis(duration)); +} + +// Sleep for `duration` in milliseconds if running on macOS +pub fn sleep_macos(duration: u64) { + if cfg!(target_os = "macos") { + thread::sleep(Duration::from_millis(duration)); + } +} + +// Sleep for `duration` in milliseconds if running on Windows +pub fn sleep_windows(duration: u64) { + if cfg!(target_os = "windows") { + thread::sleep(Duration::from_millis(duration)); + } +} + +pub trait TestHelpers { + /// Return path relative to the TempDir. Directory separator must be a forward slash, and will be converted to the platform's native separator. + fn mkpath(&self, p: &str) -> PathBuf; + /// Create file or directory. Directories must contain the phrase "dir" otherwise they will be interpreted as files. + fn create(&self, p: &str); + /// Create all files and directories in the `paths` list. Directories must contain the phrase "dir" otherwise they will be interpreted as files. + fn create_all(&self, paths: Vec<&str>); + /// Rename file or directory. + fn rename(&self, a: &str, b: &str); + /// Toggle "other" rights on linux and macOS and "readonly" on windows + fn chmod(&self, p: &str); + /// Write some data to a file + fn write(&self, p: &str); + /// Remove file or directory + fn remove(&self, p: &str); +} + +impl TestHelpers for TempDir { + fn mkpath(&self, p: &str) -> PathBuf { + let mut path = self + .path() + .canonicalize() + .expect("failed to canonicalize path") + .to_owned(); + for part in p.split('/').collect::>() { + if part != "." { + path.push(part); + } + } + path + } + + fn create(&self, p: &str) { + let path = self.mkpath(p); + if path + .components() + .last() + .unwrap() + .as_os_str() + .to_str() + .unwrap() + .contains("dir") + { + fs::create_dir_all(path).expect("failed to create directory"); + } else { + let parent = path + .parent() + .expect("failed to get parent directory") + .to_owned(); + if !parent.exists() { + fs::create_dir_all(parent).expect("failed to create parent directory"); + } + fs::File::create(path).expect("failed to create file"); + } + } + + fn create_all(&self, paths: Vec<&str>) { + for p in paths { + self.create(p); + } + } + + fn rename(&self, a: &str, b: &str) { + let path_a = self.mkpath(a); + let path_b = self.mkpath(b); + fs::rename(&path_a, &path_b).expect("failed to rename file or directory"); + } + + #[cfg(not(target_os = "windows"))] + fn chmod(&self, p: &str) { + let path = self.mkpath(p); + let mut permissions = fs::metadata(&path) + .expect("failed to get metadata") + .permissions(); + let u = (permissions.mode() / 100) % 10; + let g = (permissions.mode() / 10) % 10; + let o = if permissions.mode() % 10 == 0 { g } else { 0 }; + permissions.set_mode(u * 100 + g * 10 + o); + fs::set_permissions(path, permissions).expect("failed to chmod file or directory"); + } + + #[cfg(target_os = "windows")] + fn chmod(&self, p: &str) { + let path = self.mkpath(p); + let mut permissions = fs::metadata(&path) + .expect("failed to get metadata") + .permissions(); + let r = permissions.readonly(); + permissions.set_readonly(!r); + fs::set_permissions(path, permissions).expect("failed to chmod file or directory"); + } + + fn write(&self, p: &str) { + let path = self.mkpath(p); + + let mut file = fs::OpenOptions::new() + .write(true) + .open(path) + .expect("failed to open file"); + + file.write(b"some data").expect("failed to write to file"); + file.sync_all().expect("failed to sync file"); + } + + fn remove(&self, p: &str) { + let path = self.mkpath(p); + if path.is_dir() { + fs::remove_dir(path).expect("failed to remove directory"); + } else { + fs::remove_file(path).expect("failed to remove file"); + } + } +} + +macro_rules! assert_eq_any { + ($left:expr, $right1:expr, $right2:expr) => ({ + match (&($left), &($right1), &($right2)) { + (left_val, right1_val, right2_val) => { + if *left_val != *right1_val && *left_val != *right2_val { + panic!("assertion failed: `(left != right1 or right2)` (left: `{:?}`, right1: `{:?}`, right2: `{:?}`)", left_val, right1_val, right2_val) + } + } + } + }) +} From ee5b0f6249a11c4ce1e9b2c2c8fd941a9d506c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 13:12:48 +1200 Subject: [PATCH 3/9] Copy watcher test from v4 --- tests/utils/mod.rs | 13 +- tests/watcher.rs | 1891 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1902 insertions(+), 2 deletions(-) create mode 100644 tests/watcher.rs diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 335af08f..6ff53ea8 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,3 +1,5 @@ +#![allow(dead_code, unused_macros)] + use std::{ fs, io::Write, @@ -11,8 +13,8 @@ use std::{ time::{Duration, Instant}, }; -use crossbeam_channel::{Receiver, RecvTimeoutError}; -use notify::*; +use crossbeam_channel::{Receiver, RecvTimeoutError, Sender}; +use notify::{*, poll::PollWatcherConfig}; use tempfile::TempDir; #[cfg(not(target_os = "windows"))] @@ -88,6 +90,13 @@ pub fn extract_cookies(events: &[(PathBuf, EventKind, Option)]) -> Vec>, delay_ms: u64) -> Result { + PollWatcher::with_config(tx, PollWatcherConfig { + poll_interval: Duration::from_millis(delay_ms), + compare_contents: false, +}) +} + // Sleep for `duration` in milliseconds pub fn sleep(duration: u64) { thread::sleep(Duration::from_millis(duration)); diff --git a/tests/watcher.rs b/tests/watcher.rs new file mode 100644 index 00000000..d2d6d4e1 --- /dev/null +++ b/tests/watcher.rs @@ -0,0 +1,1891 @@ +use std::{env, path::Path, thread}; + +use crossbeam_channel::unbounded; +use notify::*; + +use utils::*; +mod utils; + +const TEMP_DIR: &str = "temp_dir"; + +#[cfg(target_os = "linux")] +#[test] +fn new_inotify() { + let (tx, _) = unbounded(); + let w: Result = Watcher::new(tx); + assert!(w.is_ok()); +} + +#[cfg(target_os = "macos")] +#[test] +fn new_fsevent() { + let (tx, _) = unbounded(); + let w: Result = Watcher::new(tx); + assert!(w.is_ok()); +} + +#[test] +fn new_null() { + let (tx, _) = unbounded(); + let w: Result = Watcher::new(tx); + assert!(w.is_ok()); +} + +#[test] +fn new_poll() { + let (tx, _) = unbounded(); + let w: Result = Watcher::new(tx); + assert!(w.is_ok()); +} + +#[test] +fn new_recommended() { + let (tx, _) = unbounded(); + let w: Result = Watcher::new(tx); + assert!(w.is_ok()); +} + +// if this test builds, it means RecommendedWatcher is Send. +#[test] +fn test_watcher_send() { + let (tx, _) = unbounded(); + + let mut watcher: RecommendedWatcher = Watcher::new(tx).unwrap(); + + thread::spawn(move || { + watcher + .watch(Path::new("."), RecursiveMode::Recursive) + .unwrap(); + }) + .join() + .unwrap(); +} + +// if this test builds, it means RecommendedWatcher is Sync. +#[test] +fn test_watcher_sync() { + use std::sync::{Arc, RwLock}; + + let (tx, _) = unbounded(); + + let watcher: RecommendedWatcher = Watcher::new(tx).unwrap(); + let watcher = Arc::new(RwLock::new(watcher)); + + thread::spawn(move || { + let mut watcher = watcher.write().unwrap(); + watcher + .watch(Path::new("."), RecursiveMode::Recursive) + .unwrap(); + }) + .join() + .unwrap(); +} + +#[test] +fn watch_relative() { + // both of the following tests set the same environment variable, so they must not run in parallel + { + // watch_relative_directory + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("dir1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, _) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + watcher + .unwatch(Path::new("dir1")) + .expect("failed to unwatch directory"); + + if cfg!(not(target_os = "windows")) { + match watcher.unwatch(Path::new("dir1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } + } + { + // watch_relative_file + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("file1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, _) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + watcher + .unwatch(Path::new("file1")) + .expect("failed to unwatch file"); + + if cfg!(not(target_os = "windows")) { + match watcher.unwatch(Path::new("file1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } + } +} + +#[test] +fn watch_recursive_create_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1"); + sleep(10); + tdir.create("dir1/file1"); + + sleep_macos(100); + + watcher + .unwatch(&tdir.mkpath(".")) + .expect("failed to unwatch directory"); + + sleep_windows(100); + + tdir.create("dir1/file2"); + + let actual = if cfg!(target_os = "windows") { + // Windows may sneak a write event in there + let mut events = recv_events(&rx); + events.retain(|(_, ref kind, _)| { + matches!( + kind, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)) + ) + }); + events + } else { + recv_events(&rx) + }; + + if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ) + ] + ); + } +} + +#[test] +fn watch_recursive_move() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1a"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1a/file1"); + tdir.rename("dir1a", "dir1b"); + sleep(10); + tdir.create("dir1b/file2"); + + let actual = if cfg!(target_os = "windows") { + // Windows may sneak a write event in there + let mut events = recv_events(&rx); + events.retain(|(_, ref kind, _)| { + matches!( + kind, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)) + ) + }); + events + } else { + recv_events(&rx) + }; + + if cfg!(target_os = "macos") { + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), // excessive create event + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b/file2"), + EventKind::Create(event::CreateKind::Any), + None + ) + ] + ); + } else if cfg!(target_os = "linux") { + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1a/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b/file2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1b/file2"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } else { + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("dir1b/file2"), + EventKind::Create(event::CreateKind::Any), + None + ) + ] + ); + } +} + +#[test] +#[cfg_attr(target_os = "macos", ignore)] +fn watch_recursive_move_in() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir", "dir1a/dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("dir1a", "watch_dir/dir1b"); + sleep(10); + tdir.create("watch_dir/dir1b/dir1/file1"); + + let actual = if cfg!(target_os = "windows") { + // Windows may sneak a write event in there + let mut events = recv_events(&rx); + events.retain(|(_, ref kind, _)| { + matches!( + kind, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)) + ) + }); + events + } else { + recv_events(&rx) + }; + + if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), // fsevent interprets a move_to as a rename event + ( + tdir.mkpath("watch_dir/dir1b/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ) + ] + ); + panic!("move event should be a create event"); + } else if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +#[cfg_attr(target_os = "macos", ignore)] +fn watch_recursive_move_out() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir/dir1a/dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("watch_dir/dir1a/dir1/file1"); + tdir.rename("watch_dir/dir1a", "dir1b"); + sleep(10); + tdir.create("dir1b/dir1/file2"); + + let actual = if cfg!(target_os = "windows") { + // Windows may sneak a write event in there + let mut events = recv_events(&rx); + events.retain(|(_, ref kind, _)| { + matches!( + kind, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)) + ) + }); + events + } else { + recv_events(&rx) + }; + + if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1a/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1a"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive create event + ( + tdir.mkpath("watch_dir/dir1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ) // fsevent interprets a move_out as a rename event + ] + ); + panic!("move event should be a remove event"); + } else if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1a/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1a/dir1/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), + ( + tdir.mkpath("watch_dir/dir1a"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1a/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1a"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } +} + +#[test] +fn watch_nonrecursive() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::NonRecursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir2"); + sleep(10); + tdir.create_all(vec!["file0", "dir1/file1", "dir2/file2"]); + + if cfg!(target_os = "linux") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("dir2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file0"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file0"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("dir2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file0"), + EventKind::Create(event::CreateKind::Any), + None + ) + ] + ); + } +} + +#[test] +fn watch_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("file1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.write("file1"); + tdir.create("file2"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive write create + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ] + ); + } else if cfg!(target_os = "linux") { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + )] + ); + } +} + +#[test] +fn poll_watch_recursive_create_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + let (tx, rx) = unbounded(); + let mut watcher = poll_with_delay_ms(tx, 50).expect("failed to create poll watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.create("dir1/file1"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + watcher + .unwatch(&tdir.mkpath(".")) + .expect("failed to unwatch directory"); + + tdir.create("dir1/file2"); + + let mut actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("."), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ) + ] + ); +} + +#[test] +#[ignore] // fails sometimes on AppVeyor +fn poll_watch_recursive_move() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1a"]); + + let (tx, rx) = unbounded(); + let mut watcher = poll_with_delay_ms(tx, 50).expect("failed to create poll watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.create("dir1a/file1"); + + let mut actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1a"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir1a/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.rename("dir1a", "dir1b"); + tdir.create("dir1b/file2"); + + actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + if cfg!(target_os = "windows") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("."), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir1a"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("dir1a/file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1b"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir1b/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1b/file2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("."), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir1a"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("dir1a/file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1b/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1b/file2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +#[ignore] // fails sometimes on AppVeyor +fn poll_watch_recursive_move_in() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir", "dir1a/dir1"]); + + let (tx, rx) = unbounded(); + let mut watcher = poll_with_delay_ms(tx, 50).expect("failed to create poll watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.rename("dir1a", "watch_dir/dir1b"); + tdir.create("watch_dir/dir1b/dir1/file1"); + + let mut actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + if cfg!(target_os = "windows") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("watch_dir/dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // extra write event + ( + tdir.mkpath("watch_dir/dir1b/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("watch_dir/dir1b"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1b/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +#[ignore] // fails sometimes on AppVeyor +fn poll_watch_recursive_move_out() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["watch_dir/dir1a/dir1"]); + + let (tx, rx) = unbounded(); + let mut watcher = poll_with_delay_ms(tx, 50).expect("failed to create poll watcher"); + watcher + .watch(&tdir.mkpath("watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.create("watch_dir/dir1a/dir1/file1"); + + let mut actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir/dir1a/dir1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("watch_dir/dir1a/dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.rename("watch_dir/dir1a", "dir1b"); + tdir.create("dir1b/dir1/file2"); + + actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("watch_dir"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("watch_dir/dir1a"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1a/dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("watch_dir/dir1a/dir1/file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); +} + +#[test] +fn poll_watch_nonrecursive() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + let (tx, rx) = unbounded(); + let mut watcher = poll_with_delay_ms(tx, 50).expect("failed to create poll watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::NonRecursive) + .expect("failed to watch directory"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.create_all(vec!["file1", "dir1/file1", "dir2/file1"]); + + let mut actual = recv_events(&rx); + actual.sort_by(|a, b| a.0.cmp(&b.0)); + + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("."), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // parent directory gets modified + ( + tdir.mkpath("dir2"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); +} + +#[test] +fn poll_watch_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + let (tx, rx) = unbounded(); + let mut watcher = poll_with_delay_ms(tx, 50).expect("failed to create poll watcher"); + watcher + .watch(&tdir.mkpath("file1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep(1100); // PollWatcher has only a resolution of 1 second + + tdir.write("file1"); + tdir.create("file2"); + + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + )] + ); +} + +#[test] +fn watch_nonexisting() { + let tdir1 = tempfile::Builder::new() + .prefix("temp_dir1") + .tempdir() + .expect("failed to create temporary directory"); + let tdir2 = tempfile::Builder::new() + .prefix("temp_dir2") + .tempdir() + .expect("failed to create temporary directory"); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir1.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + let result = watcher.watch(&tdir2.mkpath("non_existing"), RecursiveMode::Recursive); + assert!(result.is_err()); + + // make sure notify is still working + + sleep_windows(100); + + tdir1.create("file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + (recv_events(&rx)), + vec![( + tdir1.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + } else if cfg!(target_os = "windows") { + assert_eq!( + recv_events(&rx), + vec![( + tdir1.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + )] + ); + } else { + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir1.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir1.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } +} + +#[test] +fn unwatch_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, _) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + match watcher.unwatch(&tdir.mkpath("file1")) { + Ok(_) => (), + Err(e) => panic!("{:?}", e), + } +} + +#[test] +fn unwatch_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + sleep_macos(10); + + let (tx, _) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + match watcher.unwatch(&tdir.mkpath("dir1")) { + Ok(_) => (), + Err(e) => panic!("{:?}", e), + } +} + +#[test] +#[cfg_attr(target_os = "windows", ignore)] +fn unwatch_nonexisting() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep_macos(10); + + let (tx, _) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + + match watcher.unwatch(&tdir.mkpath("file1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } +} + +#[test] +fn self_delete_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + sleep_windows(100); + + tdir.remove("file1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "windows") { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } else if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Metadata(event::MetadataKind::Any)), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } + + if cfg!(not(any(target_os = "windows", target_os = "macos"))) { + tdir.create("file1"); + + assert_eq!(recv_events(&rx), vec![]); + + match watcher.unwatch(&tdir.mkpath("file1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +#[cfg_attr(target_os = "windows", ignore)] +fn self_delete_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.remove("dir1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "windows") { + assert_eq!(actual, vec![]); + } else if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } + + tdir.create("dir1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), // excessive remove event + ] + ); + } else { + assert_eq!(actual, vec![]); + } + + if cfg!(not(any(target_os = "windows", target_os = "macos"))) { + match watcher.unwatch(&tdir.mkpath("dir1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +#[cfg_attr(target_os = "windows", ignore)] +fn self_rename_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + sleep_windows(100); + + tdir.rename("file1", "file2"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive create event + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ),] + ); + } + + tdir.write("file2"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "windows") { + assert_eq!(actual, vec![]); + panic!("windows back-end should update file watch path"); + } else if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!(actual, vec![]); + } else if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // path doesn't get updated + ( + tdir.mkpath("file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), // path doesn't get updated + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // path doesn't get updated + ] + ); + } + + tdir.create("file1"); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!( + recv_events(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), // excessive rename event + ] + ); + } else { + assert_eq!(recv_events(&rx), vec![]); + } + + watcher + .unwatch(&tdir.mkpath("file1")) + .expect("failed to unwatch file"); // use old path to unwatch + + let result = watcher.unwatch(&tdir.mkpath("file1")); + match result { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } +} + +#[test] +fn self_rename_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.rename("dir1", "dir2"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "windows") { + assert_eq!(actual, vec![]); + } else if cfg!(target_os = "macos") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), // excessive create event + ( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ] + ); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ),] + ); + } + + tdir.create("dir2/file1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!(actual, vec![]); + } else if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // path doesn't get updated + ( + tdir.mkpath("dir1/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ) // path doesn't get updated + ] + ); + } + + tdir.create("dir1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("dir1"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), // excessive rename event + ] + ); + } else { + assert_eq!(actual, vec![]); + } + + watcher + .unwatch(&tdir.mkpath("dir1")) + .expect("failed to unwatch directory"); // use old path to unwatch + + let result = watcher.unwatch(&tdir.mkpath("dir1")); + if cfg!(target_os = "windows") { + match result { + Err(e) => panic!("{:?}", e), + Ok(()) => (), + } + } else { + match result { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +fn parent_rename_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1/file1"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("dir1/file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + sleep_windows(100); + + tdir.rename("dir1", "dir2"); + + assert_eq!(recv_events(&rx), vec![]); + + tdir.write("dir2/file1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!(actual, vec![]); + } else if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1/file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // path doesn't get updated + ( + tdir.mkpath("dir1/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ) + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1/file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ) // path doesn't get updated + ] + ); + } + + tdir.create("dir1/file1"); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!( + recv_events(&rx), + vec![( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + } else { + assert_eq!(recv_events(&rx), vec![]); + } + + watcher + .unwatch(&tdir.mkpath("dir1/file1")) + .expect("failed to unwatch file"); // use old path to unwatch + + let result = watcher.unwatch(&tdir.mkpath("dir1/file1")); + if cfg!(target_os = "windows") { + match result { + Err(e) => panic!("{:?}", e), + Ok(()) => (), + } + } else { + match result { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +#[cfg_attr(target_os = "windows", ignore)] +fn parent_rename_directory() { + // removing the parent directory doesn't work on windows + if cfg!(target_os = "windows") { + panic!("cannot remove parent directory on windows"); + } + + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["dir1/watch_dir"]); + + sleep_macos(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("dir1/watch_dir"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("dir1", "dir2"); + + assert_eq!(recv_events(&rx), vec![]); + + tdir.create("dir2/watch_dir/file1"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!(actual, vec![]); + } else if cfg!(target_os = "linux") { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1/watch_dir/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // path doesn't get updated + ( + tdir.mkpath("dir1/watch_dir/file1"), + EventKind::Access(event::AccessKind::Close(event::AccessMode::Write)), + None + ), // path doesn't get updated + ] + ); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("dir1/watch_dir/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // path doesn't get updated + ] + ); + } + + tdir.create("dir1/watch_dir"); + + let actual = recv_events(&rx); + + if cfg!(target_os = "macos") { + // macos doesn't watch files, but paths + assert_eq!( + actual, + vec![( + tdir.mkpath("dir1/watch_dir"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + } else { + assert_eq!(actual, vec![]); + } + + watcher + .unwatch(&tdir.mkpath("dir1/watch_dir")) + .expect("failed to unwatch directory"); // use old path to unwatch + + let result = watcher.unwatch(&tdir.mkpath("dir1/watch_dir")); + match result { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } +} From c4f0925bdd6679c8ea6f7f191b729f20bdea7d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 13:16:51 +1200 Subject: [PATCH 4/9] Copy manual linux test from v4 --- tests/manual_tests_linux.rs | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/manual_tests_linux.rs diff --git a/tests/manual_tests_linux.rs b/tests/manual_tests_linux.rs new file mode 100644 index 00000000..d9ca14dc --- /dev/null +++ b/tests/manual_tests_linux.rs @@ -0,0 +1,70 @@ +#![cfg(target_os = "linux")] +#![cfg(feature = "manual_tests")] + +use std::{ + fs::File, + io::prelude::*, + thread, + time::{Duration, Instant}, +}; + +use crossbeam_channel::{unbounded, TryRecvError}; +use notify::*; + +use utils::*; +mod utils; + +const TEMP_DIR: &str = "temp_dir"; + +#[test] +// Test preparation: +// 1. Run `sudo echo 10 > /proc/sys/fs/inotify/max_queued_events` +// 2. Uncomment the lines near "test inotify_queue_overflow" in inotify watcher +fn inotify_queue_overflow() { + let mut max_queued_events = String::new(); + let mut f = File::open("/proc/sys/fs/inotify/max_queued_events") + .expect("failed to open max_queued_events"); + f.read_to_string(&mut max_queued_events) + .expect("failed to read max_queued_events"); + assert_eq!(max_queued_events.trim(), "10"); + + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + for i in 0..20 { + let filename = format!("file{}", i); + tdir.create(&filename); + tdir.remove(&filename); + } + + sleep(100); + + let start = Instant::now(); + + let mut rescan_found = false; + while !rescan_found && start.elapsed().as_secs() < 5 { + match rx.try_recv() { + Ok(Err(Error { + // TRANSLATION: this may not be correct + kind: ErrorKind::MaxFilesWatch, + .. + })) => rescan_found = true, + Ok(Err(e)) => panic!("unexpected event err: {:?}", e), + Ok(Ok(_)) => (), + Err(TryRecvError::Empty) => (), + Err(e) => panic!("unexpected channel err: {:?}", e), + } + thread::sleep(Duration::from_millis(10)); + } + + assert!(rescan_found); +} From 6f820fc074ce92462ba880ff4f441427b8bb9e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 13:21:10 +1200 Subject: [PATCH 5/9] Copy windows network test from v4 --- Cargo.toml | 1 + tests/windows_network.rs | 129 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 tests/windows_network.rs diff --git a/Cargo.toml b/Cargo.toml index ab0f0de3..10b044ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ nix = "0.23.1" default = ["macos_fsevent"] timing_tests = [] manual_tests = [] +network_tests = [] macos_kqueue = ["kqueue", "mio"] macos_fsevent = ["fsevent-sys"] diff --git a/tests/windows_network.rs b/tests/windows_network.rs new file mode 100644 index 00000000..b02ce7ef --- /dev/null +++ b/tests/windows_network.rs @@ -0,0 +1,129 @@ +#![cfg(target_os = "windows")] +#![cfg(feature = "network_tests")] + +use notify::*; +use std::{env, path::Path, sync::mpsc}; +use tempfile::TempDir; + +use utils::*; +mod utils; + +const NETWORK_PATH: &str = ""; // eg.: \\\\MY-PC\\Users\\MyName + +#[test] +fn watch_relative_network_directory() { + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("dir1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, _) = mpsc::channel(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + watcher + .unwatch(Path::new("dir1")) + .expect("failed to unwatch directory"); + + if cfg!(not(target_os = "windows")) { + match watcher.unwatch(Path::new("dir1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +fn watch_relative_network_file() { + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("file1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, _) = mpsc::channel(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + watcher + .unwatch(Path::new("file1")) + .expect("failed to unwatch file"); + + if cfg!(not(target_os = "windows")) { + match watcher.unwatch(Path::new("file1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +fn watch_absolute_network_directory() { + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("dir1"); + + let (tx, _) = mpsc::channel(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + watcher + .unwatch(&tdir.mkpath("dir1")) + .expect("failed to unwatch directory"); + + if cfg!(not(target_os = "windows")) { + match watcher.unwatch(&tdir.mkpath("dir1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} + +#[test] +fn watch_absolute_network_file() { + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("file1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, _) = mpsc::channel(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + watcher + .unwatch(&tdir.mkpath("file1")) + .expect("failed to unwatch file"); + + if cfg!(not(target_os = "windows")) { + match watcher.unwatch(&tdir.mkpath("file1")) { + Err(Error { + kind: ErrorKind::WatchNotFound, + .. + }) => (), + Err(e) => panic!("{:?}", e), + Ok(o) => panic!("{:?}", o), + } + } +} From 458f0d49af80dc24aac06ce7e616ff2fd62ab2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 13:28:26 +1200 Subject: [PATCH 6/9] Copy windows network path test from v4 --- tests/windows_network_path.rs | 201 ++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 tests/windows_network_path.rs diff --git a/tests/windows_network_path.rs b/tests/windows_network_path.rs new file mode 100644 index 00000000..9be7e7ad --- /dev/null +++ b/tests/windows_network_path.rs @@ -0,0 +1,201 @@ +#![cfg(target_os = "windows")] +#![cfg(feature = "network_tests")] + +use std::{env, path::Path}; + +use crossbeam_channel::unbounded; +use notify::*; +use tempfile::TempDir; + +use utils::*; +mod utils; + +const NETWORK_PATH: &str = ""; // eg.: \\\\MY-PC\\Users\\MyName + +#[test] +fn watch_relative_network_directory() { + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("dir1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1/file1"); + + assert_eq!( + recv_events(&rx), + vec![( + tdir.path().join("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); +} + +#[test] +fn watch_relative_network_file() { + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("file1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + sleep_windows(100); + + tdir.write("file1"); + + assert_eq!( + recv_events(&rx), + vec![( + tdir.path().join("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); +} + +#[test] +fn watch_absolute_network_directory() { + if NETWORK_PATH.is_empty() { + return; + } + + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("dir1"); + + let watch_path = tdir.path().join("dir1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1/file1"); + + assert_eq!( + recv_events(&rx), + vec![( + watch_path.join("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); +} + +#[test] +fn watch_absolute_network_file() { + if NETWORK_PATH.is_empty() { + return; + } + + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("file1"); + + let watch_path = tdir.path().join("file1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.write("file1"); + + assert_eq!( + recv_events(&rx), + vec![( + watch_path, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); +} + +#[test] +fn watch_canonicalized_network_directory() { + if NETWORK_PATH.is_empty() { + return; + } + + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("dir1"); + + let watch_path = tdir + .path() + .canonicalize() + .expect("failed to canonicalize path") + .join("dir1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1/file1"); + + assert_eq!( + recv_events(&rx), + vec![( + watch_path.join("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); +} + +#[test] +fn watch_canonicalized_network_file() { + if NETWORK_PATH.is_empty() { + return; + } + + let tdir = TempDir::new_in(NETWORK_PATH).expect("failed to create temporary directory"); + tdir.create("file1"); + + let watch_path = tdir + .path() + .canonicalize() + .expect("failed to canonicalize path") + .join("file1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.write("file1"); + + assert_eq!( + recv_events(&rx), + vec![( + watch_path, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); +} From 3639269ba1ea6d0d3077e7f05b7cf3773a72d1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 13:36:47 +1200 Subject: [PATCH 7/9] Copy path test from v4 --- tests/event_path.rs | 292 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 tests/event_path.rs diff --git a/tests/event_path.rs b/tests/event_path.rs new file mode 100644 index 00000000..dcddf2ad --- /dev/null +++ b/tests/event_path.rs @@ -0,0 +1,292 @@ +use std::{ + env, + path::{Path, PathBuf}, +}; + +use crossbeam_channel::{unbounded, Receiver}; +use notify::*; + +use utils::*; +mod utils; + +const TEMP_DIR: &str = "temp_dir"; + +#[cfg(target_os = "windows")] +fn recv_events_simple(rx: &Receiver>) -> Vec<(PathBuf, EventKind, Option)> { + recv_events(&rx) +} + +#[cfg(target_os = "macos")] +fn recv_events_simple(rx: &Receiver>) -> Vec<(PathBuf, EventKind, Option)> { + let mut events = Vec::new(); + for (path, ev, cookie) in recv_events(&rx) { + if let EventKind::Create(_) | EventKind::Modify(event::ModifyKind::Data(_)) = ev { + events.push(( + path, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + cookie, + )); + } else { + events.push((path, ev, cookie)); + } + } + events +} + +#[cfg(target_os = "linux")] +fn recv_events_simple(rx: &Receiver>) -> Vec<(PathBuf, EventKind, Option)> { + let mut events = recv_events(rx); + events.retain(|(_, ref ev, _)| matches!(ev, EventKind::Access(_))); + events +} + +#[test] +fn watch_relative() { + // both of the following tests set the same environment variable, so they must not run in parallel + { + // watch_relative_directory + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("dir1"); + + sleep_macos(10); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("dir1"), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1/file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events_simple(&rx), + vec![ + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // fsevents always returns canonicalized paths + ] + ); + } else { + assert_eq!( + recv_events_simple(&rx), + vec![( + tdir.path().join("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + } + } + { + // watch_relative_file + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("file1"); + + env::set_current_dir(tdir.path()).expect("failed to change working directory"); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(Path::new("file1"), RecursiveMode::Recursive) + .expect("failed to watch file"); + + sleep_windows(100); + + tdir.write("file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events_simple(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // fsevents always returns canonicalized paths + ] + ); + } else { + assert_eq!( + recv_events_simple(&rx), + vec![( + tdir.path().join("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); + } + } +} + +#[test] +fn watch_absolute_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("dir1"); + + sleep_macos(10); + + let watch_path = tdir.path().join("dir1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1/file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events_simple(&rx), + vec![ + ( + tdir.mkpath("dir1/file1"), + EventKind::Create(event::CreateKind::Any), + None + ), // fsevents always returns canonicalized paths + ] + ); + } else { + assert_eq!( + recv_events_simple(&rx), + vec![( + watch_path.join("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + } +} + +#[test] +fn watch_absolute_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("file1"); + + let watch_path = tdir.path().join("file1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.write("file1"); + + if cfg!(target_os = "macos") { + assert_eq!( + recv_events_simple(&rx), + vec![ + ( + tdir.mkpath("file1"), + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ), // fsevents always returns canonicalized paths + ] + ); + } else { + assert_eq!( + recv_events_simple(&rx), + vec![( + watch_path, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); + } +} + +#[test] +fn watch_canonicalized_directory() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("dir1"); + + sleep_macos(10); + + let watch_path = tdir + .path() + .canonicalize() + .expect("failed to canonicalize path") + .join("dir1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.create("dir1/file1"); + + assert_eq!( + recv_events_simple(&rx), + vec![( + watch_path.join("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); +} + +#[test] +fn watch_canonicalized_file() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + tdir.create("file1"); + + let watch_path = tdir + .path() + .canonicalize() + .expect("failed to canonicalize path") + .join("file1"); + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&watch_path, RecursiveMode::Recursive) + .expect("failed to watch directory"); + + sleep_windows(100); + + tdir.write("file1"); + + assert_eq!( + recv_events_simple(&rx), + vec![( + watch_path, + EventKind::Modify(event::ModifyKind::Data(event::DataChange::Any)), + None + ),] + ); +} From 5d5cf56145fec8f75a0ab1947b2bc2a82c98b1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Wed, 8 Jun 2022 13:52:54 +1200 Subject: [PATCH 8/9] Copy macos timing test from v4 --- tests/timing_tests_macos.rs | 858 ++++++++++++++++++++++++++++++++++++ 1 file changed, 858 insertions(+) create mode 100644 tests/timing_tests_macos.rs diff --git a/tests/timing_tests_macos.rs b/tests/timing_tests_macos.rs new file mode 100644 index 00000000..4a41efe6 --- /dev/null +++ b/tests/timing_tests_macos.rs @@ -0,0 +1,858 @@ +#![cfg(target_os = "macos")] +#![cfg(feature = "timing_tests")] + +use std::time::Duration; + +use crossbeam_channel::unbounded; +use notify::*; + +use utils::*; +mod utils; + +const TEMP_DIR: &str = "temp_dir"; +const TIMEOUT: Duration = Duration::from_secs(5); + +#[test] +fn fsevents_create_delete_file_0() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("single CREATE | REMOVE event"); + } else { + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ), + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ), + ] + ); + } +} + +#[test] +fn fsevents_create_delete_file_1() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(1)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_create_delete_file_2() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(2)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_create_delete_file_4() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(4)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_create_delete_file_8() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(8)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_create_delete_file_16() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(16)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_create_delete_file_32() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(32)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_create_delete_file_64() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.create("file1"); + + assert_eq!( + recv_events_with_timeout(&rx, Duration::from_secs(64)), + vec![( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None + ),] + ); + + tdir.remove("file1"); + + let actual = recv_events_with_timeout(&rx, TIMEOUT); + if actual + == vec![ + ( + tdir.mkpath("file1"), + EventKind::Create(event::CreateKind::Any), + None, + ), + ( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None, + ), + ] + { + panic!("excessive CREATE event"); + } else { + assert_eq!( + actual, + vec![( + tdir.mkpath("file1"), + EventKind::Remove(event::RemoveKind::Any), + None + ),] + ); + } +} + +#[test] +fn fsevents_rename_rename_file_0() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + tdir.rename("file1b", "file1c"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + None + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + None + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ] + ); +} + +#[test] +fn fsevents_rename_rename_file_10() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + sleep(10); + tdir.rename("file1b", "file1c"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ] + ); +} + +#[test] +fn fsevents_rename_rename_file_20() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + sleep(20); + tdir.rename("file1b", "file1c"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1c"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ] + ); +} + +#[test] +fn fsevents_rename_rename_back_file_0() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + tdir.rename("file1b", "file1a"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 1); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ] + ); +} + +#[test] +fn fsevents_rename_rename_back_file_10() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + sleep(10); + tdir.rename("file1b", "file1a"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ] + ); +} + +#[test] +fn fsevents_rename_rename_back_file_20() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(10); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + sleep(20); + tdir.rename("file1b", "file1a"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Create(event::CreateKind::Any), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ] + ); +} + +#[test] +fn fsevents_rename_rename_back_file_sleep() { + let tdir = tempfile::Builder::new() + .prefix(TEMP_DIR) + .tempdir() + .expect("failed to create temporary directory"); + + tdir.create_all(vec!["file1a"]); + + sleep(40_000); + + let (tx, rx) = unbounded(); + let mut watcher: RecommendedWatcher = + Watcher::new(tx).expect("failed to create recommended watcher"); + watcher + .watch(&tdir.mkpath("."), RecursiveMode::Recursive) + .expect("failed to watch directory"); + + tdir.rename("file1a", "file1b"); + sleep(10); + tdir.rename("file1b", "file1a"); + + let actual = recv_events(&rx); + let cookies = extract_cookies(&actual); + assert_eq!(cookies.len(), 2); + assert_eq!( + actual, + vec![ + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[0]) + ), + ( + tdir.mkpath("file1b"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ( + tdir.mkpath("file1a"), + EventKind::Modify(event::ModifyKind::Name(event::RenameMode::Any)), + Some(cookies[1]) + ), + ] + ); +} From 4e43d85bf8aeca5a35d9730a1a9c82fb29d3d8dd Mon Sep 17 00:00:00 2001 From: Aron Heinecke Date: Wed, 8 Jun 2022 22:27:37 +0200 Subject: [PATCH 9/9] raise MSRV for async-global-executor --- .github/workflows/main.yml | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a8662bd..fbc02571 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: version: - - 1.56.0 # MSRV + - 1.59.0 # MSRV - stable - nightly os: [ubuntu-latest, macos-latest, windows-latest] diff --git a/Cargo.toml b/Cargo.toml index 10b044ae..f0bf8eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "notify" version = "5.0.0-pre.15" -rust-version = "1.56" +rust-version = "1.59" description = "Cross-platform filesystem notification library" documentation = "https://docs.rs/notify" homepage = "https://github.com/notify-rs/notify"