use super::*;
use std::sync::{Arc, Mutex};
use weavatrix_scan::{WalkControl, WalkEvent};
#[test]
fn continue_policy_yields_a_local_error_and_keeps_walking() {
let fixture = Fixture::new("weavatrix-walker-errors");
fixture.write("keep/file.rs", "fn keep() {}\n");
fixture.write("vanish/file.rs", "fn vanish() {}\n");
let mut walker = Walker::with_options(
&fixture.root,
WalkOptions::default().with_error_policy(ErrorPolicy::Continue),
)
.unwrap();
let mut visited = Vec::new();
let local_error = loop {
let item = walker.next().expect("vanish directory must be visible");
match item {
Ok(entry) if entry.relative_path() == Path::new("vanish") => {
fs::remove_dir_all(entry.path()).unwrap();
break walker
.next()
.expect("deleted pending directory must yield an error")
.unwrap_err();
}
Ok(entry) => visited.push(entry.relative_path().to_path_buf()),
Err(error) => panic!("unexpected early error: {error}"),
}
};
assert_eq!(local_error.path().file_name().unwrap(), "vanish");
for item in walker {
visited.push(item.unwrap().relative_path().to_path_buf());
}
assert!(visited.contains(&PathBuf::from("keep/file.rs")));
}
#[test]
fn abort_policy_terminates_after_the_first_local_error() {
let fixture = Fixture::new("weavatrix-walker-abort");
fixture.write("vanish/file.rs", "fn vanish() {}\n");
let mut walker = Walker::with_options(
&fixture.root,
WalkOptions::default().with_error_policy(ErrorPolicy::Abort),
)
.unwrap();
loop {
let entry = walker.next().unwrap().unwrap();
if entry.relative_path() == Path::new("vanish") {
fs::remove_dir_all(entry.path()).unwrap();
break;
}
}
assert!(walker.next().unwrap().is_err());
assert!(walker.next().is_none());
}
#[test]
fn parallel_walker_matches_serial_paths_on_a_wide_tree() {
let fixture = Fixture::new("weavatrix-parallel-walker");
for directory in 0..24 {
for file in 0..8 {
fixture.write(
&format!("module_{directory:02}/file_{file:02}.rs"),
"fn run() {}\n",
);
}
}
let options = WalkOptions::default().with_max_open(2);
let serial = Walker::with_options(&fixture.root, options)
.unwrap()
.map(|item| item.unwrap().relative_path().to_path_buf())
.collect::<BTreeSet<_>>();
let parallel = ParallelWalker::new(&fixture.root)
.options(options)
.with_parallelism(8)
.walk()
.unwrap();
assert!(parallel.errors.is_empty());
let parallel = parallel
.entries
.iter()
.map(|entry| entry.relative_path().to_path_buf())
.collect::<BTreeSet<_>>();
assert_eq!(serial, parallel);
}
#[test]
fn parallel_report_order_does_not_depend_on_worker_completion() {
let fixture = Fixture::new("weavatrix-parallel-order");
for file in 0..400 {
fixture.write(&format!("module_00/file_{file:03}.rs"), "fn run() {}\n");
}
for directory in 1..8 {
fixture.write(&format!("module_{directory:02}/file.rs"), "fn run() {}\n");
}
let report = ParallelWalker::new(&fixture.root)
.with_parallelism(4)
.walk()
.unwrap();
let top_level = report
.entries
.iter()
.filter(|entry| entry.depth() == 1 && entry.is_dir())
.map(|entry| entry.file_name().to_os_string())
.collect::<Vec<_>>();
let mut descendant_groups = Vec::new();
for entry in report.entries.iter().filter(|entry| entry.depth() > 1) {
let group = entry
.relative_path()
.components()
.next()
.expect("descendant has a top-level component")
.as_os_str()
.to_os_string();
if descendant_groups.last() != Some(&group) {
descendant_groups.push(group);
}
}
assert_eq!(top_level, descendant_groups);
}
#[test]
fn dynamic_frontier_uses_multiple_workers_below_one_root_directory() {
if std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) < 2 {
return;
}
let fixture = Fixture::new("weavatrix-parallel-dynamic");
for directory in 0..64 {
for file in 0..16 {
fixture.write(
&format!("single/module_{directory:02}/file_{file:02}.rs"),
"fn run() {}\n",
);
}
}
let workers = Arc::new(Mutex::new(BTreeSet::new()));
let visitor_workers = Arc::clone(&workers);
let report = ParallelWalker::new(&fixture.root)
.with_parallelism(4)
.visit(move |event| {
if matches!(event, WalkEvent::Entry(entry) if entry.is_file()) {
visitor_workers.lock().unwrap().insert(
std::thread::current()
.name()
.unwrap_or("unnamed")
.to_owned(),
);
}
WalkControl::Continue
})
.unwrap();
assert!(report.errors.is_empty());
assert!(workers.lock().unwrap().len() > 1);
}