weavatrix-scan 0.4.1

Deterministic, safe repository scanner for code intelligence
Documentation
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);
}