diff --git a/fish-rust/src/universal_notifier/inotify.rs b/fish-rust/src/universal_notifier/inotify.rs index 4c90986ec..afc52a46e 100644 --- a/fish-rust/src/universal_notifier/inotify.rs +++ b/fish-rust/src/universal_notifier/inotify.rs @@ -88,7 +88,7 @@ fn test_inotify_notifiers() { .iter() .map(|n| n as &dyn UniversalNotifier) .collect::>(); - super::test_notifiers(¬ifiers, Some(&fake_uvars_path)); + super::test_helpers::test_notifiers(¬ifiers, Some(&fake_uvars_path)); let _ = remove_dir_all(PathBuf::from(wcs2osstring(&fake_uvars_dir))); } diff --git a/fish-rust/src/universal_notifier/mod.rs b/fish-rust/src/universal_notifier/mod.rs index 56f52982e..1b4e811bb 100644 --- a/fish-rust/src/universal_notifier/mod.rs +++ b/fish-rust/src/universal_notifier/mod.rs @@ -7,6 +7,9 @@ #[cfg(any(target_os = "android", target_os = "linux"))] mod inotify; +#[cfg(test)] +mod test_helpers; + /// The "universal notifier" is an object responsible for broadcasting and receiving universal /// variable change notifications. These notifications do not contain the change, but merely /// indicate that the uvar file has changed. It is up to the uvar subsystem to re-read the file. @@ -68,91 +71,3 @@ pub fn create_notifier() -> Box { pub fn default_notifier() -> &'static dyn UniversalNotifier { DEFAULT_NOTIFIER.get_or_init(create_notifier).as_ref() } - -#[cfg(test)] -use crate::wchar::prelude::*; - -// Test a slice of notifiers. -// fish_variables_path is the path to the (simulated) fish_variables file. -#[cfg(test)] -pub fn test_notifiers(notifiers: &[&dyn UniversalNotifier], fish_variables_path: Option<&wstr>) { - let poll_notifier = |n: &dyn UniversalNotifier| -> bool { - let Some(fd) = n.notification_fd() else { - return false; - }; - if crate::fd_readable_set::poll_fd_readable(fd) { - n.notification_fd_became_readable(fd) - } else { - false - } - }; - - // Helper to simulate modifying a file, using the atomic rename() approach. - let modify_path = |path: &wstr| -> Result<(), std::io::Error> { - use crate::common::wcs2osstring; - use std::fs; - use std::io::Write; - let path = wcs2osstring(path); - let mut new_path = std::path::PathBuf::from(path.clone()); - new_path.set_extension("new"); - - let mut file = fs::File::create(&new_path)?; - file.write_all(b"Random text")?; - std::mem::drop(file); - fs::rename(new_path, path)?; - Ok(()) - }; - - // Nobody should poll yet. - for (idx, &n) in notifiers.iter().enumerate() { - assert!( - !poll_notifier(n), - "notifier {} polled before notification", - idx - ); - } - - // Tweak each notifier. Verify that others see it. - for (idx1, &n1) in notifiers.iter().enumerate() { - n1.post_notification(); - - // If we're using inotify, simulate modifying the file. - if let Some(path) = fish_variables_path { - modify_path(path).expect("failed to modify file"); - } - - // notifyd requires a round trip to the notifyd server, which means we have to wait a - // little bit to receive it. In practice 40 ms seems to be enough. - unsafe { libc::usleep(40000) }; - - for (idx2, &n2) in notifiers.iter().enumerate() { - let mut polled = poll_notifier(n2); - - // We aren't concerned with the one who posted, except we do need to poll to drain it. - if idx1 == idx2 { - continue; - } - assert!( - polled, - "notifier {} did not see notification from {}", - idx2, idx1 - ); - // It should not poll again immediately. - polled = poll_notifier(n2); - assert!( - !polled, - "notifier {} polled twice after notification from {}", - idx2, idx1 - ); - } - } - - // Nobody should poll now. - for (idx, &n) in notifiers.iter().enumerate() { - assert!( - !poll_notifier(n), - "notifier {} polled after all changes", - idx - ); - } -} diff --git a/fish-rust/src/universal_notifier/notifyd.rs b/fish-rust/src/universal_notifier/notifyd.rs index 87b05efae..063ee2c66 100644 --- a/fish-rust/src/universal_notifier/notifyd.rs +++ b/fish-rust/src/universal_notifier/notifyd.rs @@ -145,5 +145,5 @@ fn test_notifyd_notifiers() { .iter() .map(|n| n as &dyn UniversalNotifier) .collect::>(); - super::test_notifiers(¬ifiers, None); + super::test_helpers::test_notifiers(¬ifiers, None); } diff --git a/fish-rust/src/universal_notifier/test_helpers.rs b/fish-rust/src/universal_notifier/test_helpers.rs new file mode 100644 index 000000000..6ef397afb --- /dev/null +++ b/fish-rust/src/universal_notifier/test_helpers.rs @@ -0,0 +1,86 @@ +use super::UniversalNotifier; +use crate::wchar::prelude::*; + +// Helper to test a slice of notifiers. +// fish_variables_path is the path to the (simulated) fish_variables file, if using the inotify notifier. +pub fn test_notifiers(notifiers: &[&dyn UniversalNotifier], fish_variables_path: Option<&wstr>) { + let poll_notifier = |n: &dyn UniversalNotifier| -> bool { + let Some(fd) = n.notification_fd() else { + return false; + }; + if crate::fd_readable_set::poll_fd_readable(fd) { + n.notification_fd_became_readable(fd) + } else { + false + } + }; + + // Helper to simulate modifying a file, using the atomic rename() approach. + let modify_path = |path: &wstr| -> Result<(), std::io::Error> { + use crate::common::wcs2osstring; + use std::fs; + use std::io::Write; + let path = wcs2osstring(path); + let mut new_path = std::path::PathBuf::from(path.clone()); + new_path.set_extension("new"); + + let mut file = fs::File::create(&new_path)?; + file.write_all(b"Random text")?; + std::mem::drop(file); + fs::rename(new_path, path)?; + Ok(()) + }; + + // Nobody should poll yet. + for (idx, &n) in notifiers.iter().enumerate() { + assert!( + !poll_notifier(n), + "notifier {} polled before notification", + idx + ); + } + + // Tweak each notifier. Verify that others see it. + for (idx1, &n1) in notifiers.iter().enumerate() { + n1.post_notification(); + + // If we're using inotify, simulate modifying the file. + if let Some(path) = fish_variables_path { + modify_path(path).expect("failed to modify file"); + } + + // notifyd requires a round trip to the notifyd server, which means we have to wait a + // little bit to receive it. In practice 40 ms seems to be enough. + unsafe { libc::usleep(40000) }; + + for (idx2, &n2) in notifiers.iter().enumerate() { + let mut polled = poll_notifier(n2); + + // We aren't concerned with the one who posted, except we do need to poll to drain it. + if idx1 == idx2 { + continue; + } + assert!( + polled, + "notifier {} did not see notification from {}", + idx2, idx1 + ); + // It should not poll again immediately. + polled = poll_notifier(n2); + assert!( + !polled, + "notifier {} polled twice after notification from {}", + idx2, idx1 + ); + } + } + + // Nobody should poll now. + for (idx, &n) in notifiers.iter().enumerate() { + assert!( + !poll_notifier(n), + "notifier {} polled after all changes", + idx + ); + } +}