use notify::*;
use tempfile::TempDir;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process;
use std::sync::{
atomic::{AtomicBool, Ordering::SeqCst},
mpsc::{Receiver, TryRecvError},
Arc,
};
use std::thread;
use std::time::{Duration, Instant};
#[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;
pub fn recv_events_with_timeout(
rx: &Receiver<RawEvent>,
timeout: Duration,
) -> Vec<(PathBuf, Op, Option<u32>)> {
let start = Instant::now();
let mut evs = Vec::new();
while start.elapsed() < timeout {
match rx.try_recv() {
Ok(RawEvent {
path: Some(path),
op: Ok(op),
cookie,
}) => {
evs.push((path, op, cookie));
}
Ok(RawEvent { path: None, .. }) => (),
Ok(RawEvent { op: Err(e), .. }) => panic!("unexpected event err: {:?}", e),
Err(TryRecvError::Empty) => (),
Err(e) => panic!("unexpected channel err: {:?}", e),
}
thread::sleep(Duration::from_millis(1));
}
evs
}
pub fn fail_after(test_name: &'static str, duration: Duration) -> impl Drop {
struct SuccessOnDrop(Arc<AtomicBool>);
impl Drop for SuccessOnDrop {
fn drop(&mut self) {
self.0.store(true, SeqCst)
}
}
let finished = SuccessOnDrop(Arc::new(AtomicBool::new(false)));
{
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<RawEvent>) -> Vec<(PathBuf, Op, Option<u32>)> {
recv_events_with_timeout(rx, Duration::from_millis(TIMEOUT_MS))
}
pub fn inflate_events(input: Vec<(PathBuf, Op, Option<u32>)>) -> Vec<(PathBuf, Op, Option<u32>)> {
let mut output = Vec::new();
let mut path = None;
let mut ops = Op::empty();
let mut cookie = None;
for (e_p, e_o, e_c) in input {
let p = match path {
Some(p) => p,
None => e_p.clone(),
};
let c = match cookie {
Some(c) => Some(c),
None => e_c,
};
if p == e_p && c == e_c {
ops |= e_o;
} else {
output.push((p, ops, cookie));
ops = e_o;
}
path = Some(e_p);
cookie = e_c;
}
if let Some(p) = path {
output.push((p, ops, cookie));
}
output
}
pub fn extract_cookies(events: &[(PathBuf, Op, Option<u32>)]) -> Vec<u32> {
let mut cookies = Vec::new();
for &(_, _, e_c) in events {
if let Some(cookie) = e_c {
if !cookies.contains(&cookie) {
cookies.push(cookie);
}
}
}
cookies
}
pub fn sleep(duration: u64) {
thread::sleep(Duration::from_millis(duration));
}
pub fn sleep_macos(duration: u64) {
if cfg!(target_os = "macos") {
thread::sleep(Duration::from_millis(duration));
}
}
pub fn sleep_windows(duration: u64) {
if cfg!(target_os = "windows") {
thread::sleep(Duration::from_millis(duration));
}
}
pub trait TestHelpers {
fn mkpath(&self, p: &str) -> PathBuf;
fn create(&self, p: &str);
fn create_all(&self, paths: Vec<&str>);
fn rename(&self, a: &str, b: &str);
fn chmod(&self, p: &str);
fn write(&self, p: &str);
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::<Vec<_>>() {
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)
}
}
}
})
}