#![cfg_attr(feature = "walk", allow(deprecated))]
#[cfg(feature = "walk")]
use std::collections::HashSet;
#[cfg(feature = "walk")]
use std::fs;
#[cfg(feature = "walk")]
use std::os::unix::fs::symlink;
#[cfg(feature = "walk")]
use tree_type::GenericDir;
#[cfg(feature = "walk")]
use tree_type::GenericPath;
#[cfg(feature = "walk")]
use tree_type::dir_type;
#[cfg(feature = "walk")]
use tree_type::tree_type;
#[cfg(feature = "walk")]
#[test]
fn test_generic_dir_walk_basic() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::create_dir(root.join("subdir")).unwrap();
fs::write(root.join("file1.txt"), "content1").unwrap();
fs::write(root.join("subdir/file2.txt"), "content2").unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let paths: Result<Vec<_>, _> = generic_dir.walk().collect();
let paths = paths.unwrap();
assert_eq!(paths.len(), 4);
let mut files = 0;
let mut dirs = 0;
for path in paths {
match path {
GenericPath::File(_) => files += 1,
GenericPath::Dir(_) => dirs += 1,
}
}
assert_eq!(files, 2);
assert_eq!(dirs, 2); }
#[cfg(feature = "walk")]
#[test]
fn test_dir_type_walk() {
dir_type!(TestDir);
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::create_dir(root.join("nested")).unwrap();
fs::write(root.join("test.txt"), "data").unwrap();
fs::write(root.join("nested/deep.txt"), "deep data").unwrap();
let test_dir = TestDir::new(root).unwrap();
let paths: Result<Vec<_>, _> = test_dir.walk().collect();
let paths = paths.unwrap();
assert!(paths.len() >= 3); }
#[cfg(feature = "walk")]
#[test]
fn test_tree_type_walk() {
tree_type! {
TestRoot {
docs/
src/
}
}
let temp = tempfile::tempdir().unwrap();
let root = TestRoot::new(temp.path()).unwrap();
root.setup().unwrap();
let paths: Result<Vec<_>, _> = root.walk().collect();
let paths = paths.unwrap();
assert!(paths.len() >= 3);
let src_paths: Result<Vec<_>, _> = root.src().walk().collect();
let src_paths = src_paths.unwrap();
assert!(!src_paths.is_empty());
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_follows_symlinks() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::write(root.join("target.txt"), "target content").unwrap();
symlink(root.join("target.txt"), root.join("link.txt")).unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let paths: Result<Vec<_>, _> = generic_dir.walk().collect();
let paths = paths.unwrap();
let mut found_target = false;
for path in paths {
if let GenericPath::File(file) = path {
if file.as_ref().file_name().unwrap() == "target.txt" {
found_target = true;
}
}
}
assert!(
found_target,
"Should find target file through symlink resolution"
);
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_detects_symlink_loops() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
symlink("link2", root.join("link1")).unwrap();
symlink("link1", root.join("link2")).unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let results: Vec<_> = generic_dir.walk().collect();
let has_error = results.iter().any(|r| r.is_err());
assert!(has_error, "Should detect symlink loop and return error");
let error_result = results.into_iter().find(|r| r.is_err()).unwrap();
let error = error_result.err().unwrap();
let error_msg = error.to_string();
assert!(
error_msg.contains("symlink loop") || error_msg.contains("unsupported path type"),
"Expected symlink loop error, got: {}",
error_msg
);
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_symlink_to_directory() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::create_dir(root.join("target_dir")).unwrap();
fs::write(root.join("target_dir/file.txt"), "content").unwrap();
symlink(root.join("target_dir"), root.join("dir_link")).unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let paths: Result<Vec<_>, _> = generic_dir.walk().collect();
let paths = paths.unwrap();
let mut found_target_dir = false;
let mut found_file_in_target = false;
for path in paths {
match path {
GenericPath::Dir(dir) => {
if dir.as_ref().file_name().unwrap() == "target_dir" {
found_target_dir = true;
}
}
GenericPath::File(file) => {
if file.as_ref().file_name().unwrap() == "file.txt" {
found_file_in_target = true;
}
}
}
}
assert!(found_target_dir, "Should find target directory");
assert!(found_file_in_target, "Should find file in target directory");
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_complex_symlink_chain() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::write(root.join("target.txt"), "final content").unwrap();
symlink("target.txt", root.join("link2")).unwrap();
symlink("link2", root.join("link1")).unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let results: Vec<_> = generic_dir.walk().collect();
let has_target_or_error = results.iter().any(|r| {
match r {
Ok(GenericPath::File(file)) => file.as_ref().file_name().unwrap() == "target.txt",
Err(_) => true, _ => false,
}
});
assert!(
has_target_or_error,
"Should either resolve symlink chain or return error"
);
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_empty_directory() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
let generic_dir = GenericDir::new(root).unwrap();
let paths: Result<Vec<_>, _> = generic_dir.walk().collect();
let paths = paths.unwrap();
assert_eq!(paths.len(), 1);
assert!(matches!(paths[0], GenericPath::Dir(_)));
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_deep_nesting() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
let deep_path = root.join("a/b/c/d");
fs::create_dir_all(&deep_path).unwrap();
fs::write(deep_path.join("file.txt"), "deep content").unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let paths: Result<Vec<_>, _> = generic_dir.walk().collect();
let paths = paths.unwrap();
assert!(paths.len() >= 6);
let found_deep_file = paths.iter().any(
|path| matches!(path, GenericPath::File(file) if file.as_ref().file_name().unwrap() == "file.txt"),
);
assert!(found_deep_file, "Should find deeply nested file");
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_mixed_content() {
tree_type! {
MixedRoot {
regular_dir/
}
}
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::write(root.join("external.txt"), "external content").unwrap();
let mixed_root = MixedRoot::new(root.join("project")).unwrap();
mixed_root.setup().unwrap();
symlink(
root.join("external.txt"),
mixed_root.as_ref().join("link_to_external"),
)
.unwrap();
let paths: Result<Vec<_>, _> = mixed_root.walk().collect();
let paths = paths.unwrap();
let mut dirs = 0;
for path in paths {
match path {
GenericPath::File(_) => {}
GenericPath::Dir(_) => dirs += 1,
}
}
assert!(dirs >= 2, "Should find root and regular_dir");
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_iterator_is_lazy() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
for i in 0..100 {
fs::write(
root.join(format!("file_{}.txt", i)),
format!("content {}", i),
)
.unwrap();
}
let generic_dir = GenericDir::new(root).unwrap();
let walker = generic_dir.walk();
let first_few: Result<Vec<_>, _> = walker.take(5).collect();
let first_few = first_few.unwrap();
assert_eq!(first_few.len(), 5);
}
#[cfg(feature = "walk")]
#[test]
fn test_walk_path_uniqueness() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path();
fs::create_dir(root.join("dir1")).unwrap();
fs::write(root.join("dir1/file.txt"), "content").unwrap();
symlink(root.join("dir1"), root.join("dir_link")).unwrap();
let generic_dir = GenericDir::new(root).unwrap();
let results: Vec<_> = generic_dir.walk().collect();
let successful_results: Vec<_> = results.into_iter().filter_map(|r| r.ok()).collect();
let path_strings: HashSet<String> = successful_results
.iter()
.map(|p| match p {
GenericPath::File(f) => f.as_ref().to_string_lossy().to_string(),
GenericPath::Dir(d) => d.as_ref().to_string_lossy().to_string(),
})
.collect();
assert!(
path_strings.len() >= 3,
"Should find at least root, dir1, and file.txt"
);
}