walkdir_minimal 1.2.0

Fast, minimalist, and POSIX-compliant recursive directory traverser with zero external dependencies.
Documentation
use super::*;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::os::unix::fs::symlink;
use std::path::PathBuf;

fn create_temp_dir(name: &str) -> PathBuf {
    let tmp = env::temp_dir().join(name);
    let _ = fs::remove_dir_all(&tmp);
    fs::create_dir_all(&tmp).unwrap();
    tmp
}

#[test]
fn walkdir_filter_works() {
    println!("\nFilter Works:");

    let tmp = create_temp_dir("walkdir_minimal_filter");
    fs::create_dir_all(tmp.join("a")).unwrap();
    fs::create_dir_all(tmp.join("b_ignore")).unwrap();
    File::create(tmp.join("a/file1.txt")).unwrap();
    File::create(tmp.join("b_ignore/file2.txt")).unwrap();

    let walker = WalkDir::new(&tmp)
        .unwrap()
        .filter_entry(|e| !e.path().to_string_lossy().contains("ignore"));

    let mut files = Vec::new();
    for entry in walker {
        let e = entry.unwrap();
        println!("{}", e.path().display());
        files.push(e.path().to_path_buf());
    }

    assert!(files.iter().any(|p| p.ends_with("file1.txt")));
    assert!(!files.iter().any(|p| p.ends_with("file2.txt")));
}

#[test]
fn walkdir_follow_symlinks() {
    println!("\nFollow symlinks:");

    let tmp = create_temp_dir("walkdir_minimal_symlinks");
    fs::create_dir_all(tmp.join("target")).unwrap();
    File::create(tmp.join("target/file.txt")).unwrap();

    let link_path = tmp.join("link_to_target");
    symlink(tmp.join("target"), &link_path).unwrap();

    let walker = WalkDir::new(&tmp)
        .unwrap()
        .follow_links(true)
        .detect_loops(false);

    let mut paths = Vec::new();
    for entry in walker {
        let e = entry.unwrap();
        println!("{}", e.path().display());
        paths.push(e.path().to_path_buf());
    }

    assert!(paths.iter().any(|p| p.ends_with("file.txt")));
}

#[test]
fn walkdir_loop_detection() {
    println!("\nLoop detection:");

    let tmp = create_temp_dir("walkdir_minimal_loops");
    fs::create_dir_all(tmp.join("a")).unwrap();
    fs::create_dir_all(tmp.join("a/b")).unwrap();

    symlink(tmp.join("a"), tmp.join("a/b/link_back")).unwrap();

    let walker = WalkDir::new(&tmp)
        .unwrap()
        .follow_links(true)
        .detect_loops(true);

    let mut visited = 0;
    let mut loop_detected = false;
    for entry in walker {
        if let Err(WalkError::LoopDetected(_)) = entry {
            println!("Loop detected");
            loop_detected = true;
            break;
        }
        println!("visited {}", entry.unwrap().path().display());
        visited += 1;
    }

    assert!(visited < 10);
    assert!(
        loop_detected,
        "Deveria ter detectado um loop infinito de links simbólicos"
    );
}

#[test]
fn walkdir_handles_large_dir() {
    println!("\nHandle Large Dir:");

    let tmp = create_temp_dir("walkdir_minimal_large");
    for i in 0..50 {
        let mut f = File::create(tmp.join(format!("file_{i}.txt"))).unwrap();
        writeln!(f, "conteúdo {i}").unwrap();
    }

    let count = WalkDir::new(&tmp).unwrap().count();
    println!("Found {} files", count);
    assert_eq!(count, 50);
}

#[test]
fn walkdir_ignores_broken_symlinks() {
    println!("\nBroken and valid symlinks test:");

    let tmp = create_temp_dir("walkdir_minimal_broken_link");
    let real_file = tmp.join("file.txt");
    let real_dir = tmp.join("dir");
    fs::write(&real_file, "hello").unwrap();
    fs::create_dir(&real_dir).unwrap();
    let valid_file_link = tmp.join("link_to_file");
    let _ = symlink(&real_file, &valid_file_link);
    let valid_dir_link = tmp.join("link_to_dir");
    let _ = symlink(&real_dir, &valid_dir_link);
    let broken_link = tmp.join("broken_link");
    let _ = symlink("/nonexistent/path", &broken_link);

    let walker = WalkDir::new(&tmp).unwrap().follow_links(true);

    let mut found_valid_links = 0;
    let mut found_broken_links = 0;

    for item in walker {
        match item {
            Ok(e) => {
                let path = e.path();
                println!("Found: {}", path.display());
                assert_ne!(path, &broken_link);

                if path == valid_file_link || path == valid_dir_link {
                    found_valid_links += 1;
                }
            }
            Err(err) => match err {
                WalkError::Io(io_err, ..) => {
                    println!("IO error: {:?}", io_err);
                    found_broken_links += 1;
                }
                _ => {}
            },
        }
    }

    println!("Valid links: {found_valid_links}, Broken links: {found_broken_links}");
}

#[test]
fn walkdir_follow_symlinks_no_loop_detection() {
    println!("\nFollow symlinks no loop detection:");

    let tmp = env::temp_dir().join("walkdir_minimal_symlink_noloop_test");
    let _ = fs::remove_dir_all(&tmp);
    fs::create_dir_all(&tmp).unwrap();

    let dir_a = tmp.join("a");
    let dir_b = tmp.join("b");
    fs::create_dir(&dir_a).unwrap();
    fs::create_dir(&dir_b).unwrap();
    File::create(dir_a.join("file_a.txt")).unwrap();

    symlink(&dir_a, dir_b.join("link_to_a")).unwrap();
    symlink(&dir_b, dir_a.join("link_to_b")).unwrap();

    let walker = WalkDir::new(&tmp)
        .unwrap()
        .follow_links(true)
        .detect_loops(false)
        .max_depth(5);

    let mut count = 0;
    for entry in walker {
        let e = entry.unwrap();
        println!("Visited: {}", e.path().display());
        count += 1;
    }

    assert!(count > 2, "Expected to visit multiple paths when following symbolic links");
}