weavatrix-scan 0.4.6

Deterministic, safe repository scanner for code intelligence
Documentation
use crate::support::Fixture;
use std::path::Path;
use std::process::{Command, Stdio};
use weavatrix_scan::{ParallelMultiWalker, ParallelWalker, WalkBuilder, WalkControl, WalkEvent};

#[test]
fn skip_stdout_excludes_redirected_output_file() {
    const CHILD_ROOT: &str = "WEAVATRIX_SKIP_STDOUT_CHILD_ROOT";
    if let Some(root) = std::env::var_os(CHILD_ROOT) {
        let root = std::path::PathBuf::from(root);
        let entries = WalkBuilder::new(&root)
            .skip_stdout(true)
            .sort_by_file_name()
            .build()
            .collect::<Result<Vec<_>, _>>()
            .unwrap();
        assert!(
            entries
                .iter()
                .all(|entry| entry.file_name() != "redirected.txt")
        );
        assert!(entries.iter().any(|entry| entry.file_name() == "keep.rs"));

        let parallel = ParallelWalker::new(&root).skip_stdout(true).walk().unwrap();
        assert!(
            parallel
                .entries
                .iter()
                .all(|entry| entry.file_name() != "redirected.txt")
        );

        let redirected_seen = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
        let visitor_seen = std::sync::Arc::clone(&redirected_seen);
        ParallelWalker::new(&root)
            .skip_stdout(true)
            .visit(move |event| {
                if matches!(
                    event,
                    WalkEvent::Entry(entry) if entry.file_name() == "redirected.txt"
                ) {
                    visitor_seen.store(true, std::sync::atomic::Ordering::Relaxed);
                }
                WalkControl::Continue
            })
            .unwrap();
        assert!(!redirected_seen.load(std::sync::atomic::Ordering::Relaxed));

        for entries in [
            ParallelWalker::new(&root)
                .skip_stdout(true)
                .into_iter_bounded(8)
                .collect::<Result<Vec<_>, _>>()
                .unwrap(),
            ParallelWalker::new(&root)
                .skip_stdout(true)
                .into_iter_ordered_bounded(8)
                .collect::<Result<Vec<_>, _>>()
                .unwrap(),
        ] {
            assert!(
                entries
                    .iter()
                    .all(|entry| entry.file_name() != "redirected.txt")
            );
        }

        let multi = ParallelMultiWalker::new(&root)
            .skip_stdout(true)
            .walk()
            .unwrap();
        assert!(
            multi.reports[0]
                .entries
                .iter()
                .all(|entry| entry.file_name() != "redirected.txt")
        );
        return;
    }

    let fixture = Fixture::new("scan-skip-stdout");
    fixture.write("keep.rs", "fn keep() {}\n");
    let output = std::fs::File::create(fixture.root.join("redirected.txt")).unwrap();
    let status = Command::new(std::env::current_exe().unwrap())
        .args([
            "--exact",
            "skip_stdout_excludes_redirected_output_file",
            "--nocapture",
        ])
        .env(CHILD_ROOT, &fixture.root)
        .stdout(Stdio::from(output))
        .stderr(Stdio::null())
        .status()
        .unwrap();
    assert!(status.success());
}

#[cfg(feature = "notify")]
#[test]
fn notify_events_map_directly_to_safe_watch_plans() {
    use notify::event::{CreateKind, ModifyKind, RenameMode};
    use notify::{Event, EventKind};
    use weavatrix_scan::WatcherEventAdapter;

    let fixture = Fixture::new("scan-notify-adapter");
    let adapter = WatcherEventAdapter::new(&fixture.root, [".gitignore"]).unwrap();
    let rename = Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::Both)))
        .add_path(fixture.root.join("old.rs"))
        .add_path(fixture.root.join("new.rs"));
    let plan = adapter.plan_notify([rename]);
    assert_eq!(plan.removed, ["old.rs"]);
    assert_eq!(plan.changed, ["new.rs"]);
    assert!(!plan.full_rescan);

    std::fs::create_dir(fixture.root.join("new-directory")).unwrap();
    let directory = Event::new(EventKind::Create(CreateKind::Folder))
        .add_path(fixture.root.join("new-directory"));
    assert!(adapter.plan_notify([directory]).full_rescan);
}

#[test]
fn complete_entry_sorter_can_use_type_and_path() {
    let fixture = Fixture::new("scan-full-entry-sort");
    fixture.write("z.rs", "fn z() {}\n");
    fixture.write("a/value.rs", "fn a() {}\n");
    let entries = WalkBuilder::new(&fixture.root)
        .sort_by(|left, right| {
            let left_file = left.file_type().is_ok_and(|kind| kind.is_file());
            let right_file = right.file_type().is_ok_and(|kind| kind.is_file());
            left_file
                .cmp(&right_file)
                .then_with(|| left.path().cmp(&right.path()))
        })
        .build()
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    assert_eq!(entries[1].relative_path(), Path::new("a"));
}