use anyhow::Result;
use lumin::traverse::{TraverseOptions, traverse_directory};
use serial_test::serial;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
#[cfg(test)]
mod traverse_glob_tests {
use super::*;
#[test]
fn test_traverse_with_star_wildcard() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
assert!(result.file_path.to_string_lossy().ends_with(".txt"));
}
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("sample.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_double_star_recursive() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/level*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
let found_level1 = results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"));
let found_level2 = results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"));
assert!(found_level1);
assert!(found_level2);
Ok(())
}
#[test]
fn test_traverse_with_question_mark_wildcard() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/level?.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_multiple_question_marks() -> Result<()> {
let directory = Path::new("tests/fixtures");
let test_file_path = PathBuf::from("tests/fixtures/nested/abc123.txt");
std::fs::write(&test_file_path, "Test file with specific length filename")?;
let _cleanup = defer::defer(|| {
let _ = std::fs::remove_file(&test_file_path);
});
let options = TraverseOptions {
pattern: Some("**/???.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(results.is_empty());
let options = TraverseOptions {
pattern: Some("**/??????.txt".to_string()), ..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("abc123.txt"))
);
for result in &results {
let filename = result.file_path.file_name().unwrap().to_string_lossy();
assert_eq!(
filename.len(),
10,
"Filename should be 6 chars + .txt (10 total)"
);
assert_eq!(&filename[filename.len() - 4..], ".txt");
}
Ok(())
}
#[test]
fn test_traverse_with_mixed_wildcards() -> Result<()> {
let directory = Path::new("tests/fixtures");
let test_file_path1 = PathBuf::from("tests/fixtures/nested/config_123.txt");
let test_file_path2 = PathBuf::from("tests/fixtures/nested/config_abc.txt");
std::fs::write(&test_file_path1, "Test file with digits in name")?;
std::fs::write(&test_file_path2, "Test file with letters in name")?;
let _cleanup = defer::defer(|| {
let _ = std::fs::remove_file(&test_file_path1);
let _ = std::fs::remove_file(&test_file_path2);
});
let options = TraverseOptions {
pattern: Some("**/config_?.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(results.is_empty());
let options = TraverseOptions {
pattern: Some("**/config_*[0-9]*.txt".to_string()), ..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("config_123.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("config_abc.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_character_class() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/level[1].txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_digit_character_class() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/level[0-9].txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
let paths: Vec<_> = results
.iter()
.map(|r| r.file_path.to_string_lossy().to_string())
.collect();
assert!(
paths.iter().any(|p| p.contains("level1.txt")),
"Should find level1.txt"
);
assert!(
paths.iter().any(|p| p.contains("level2.txt")),
"Should find level2.txt"
);
for path in paths {
assert!(path.contains("level") && path.ends_with(".txt"));
let filename = std::path::Path::new(&path)
.file_name()
.unwrap()
.to_string_lossy();
let digit_char = filename.chars().nth(filename.len() - 5).unwrap();
assert!(
digit_char.is_digit(10),
"Character before .txt should be a digit: {}",
digit_char
);
}
Ok(())
}
#[test]
fn test_traverse_with_letter_character_class() -> Result<()> {
let directory = Path::new("tests/fixtures");
let test_file_path = PathBuf::from("tests/fixtures/nested/levelA.txt");
if !test_file_path.parent().unwrap().exists() {
fs::create_dir_all(test_file_path.parent().unwrap())?;
}
let write_result = std::fs::write(
&test_file_path,
"This is a test file with letter after level.",
);
if write_result.is_err() {
println!("Skipping letter character class test - could not create test file");
return Ok(());
}
defer::defer(|| {
let _ = std::fs::remove_file(&test_file_path);
});
let basic_options = TraverseOptions::default();
let check_results = traverse_directory(directory, &basic_options)?;
if !check_results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("levelA.txt"))
{
println!("Skipping letter character class test - test file not found in listing");
return Ok(());
}
let options = TraverseOptions {
pattern: Some("**/level[a-zA-Z].txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should find files with letter character class"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("levelA.txt")),
"Should find levelA.txt"
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_combined_character_classes() -> Result<()> {
let directory = Path::new("tests/fixtures");
let test_file_path = PathBuf::from("tests/fixtures/nested/levelA.txt");
if !test_file_path.parent().unwrap().exists() {
fs::create_dir_all(test_file_path.parent().unwrap())?;
}
let write_result = std::fs::write(
&test_file_path,
"This is a test file with letter after level.",
);
defer::defer(|| {
let _ = std::fs::remove_file(&test_file_path);
});
if write_result.is_err() {
println!("Skipping combined character class test - could not create test file");
return Ok(());
}
let basic_options = TraverseOptions::default();
let check_results = traverse_directory(directory, &basic_options)?;
let has_level_a = check_results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("levelA.txt"));
let has_level_1 = check_results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"));
let has_level_2 = check_results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"));
if !has_level_a || !has_level_1 || !has_level_2 {
println!("Skipping combined character class test - missing required test files");
println!(
" levelA.txt: {}, level1.txt: {}, level2.txt: {}",
has_level_a, has_level_1, has_level_2
);
return Ok(());
}
let options = TraverseOptions {
pattern: Some("**/level[a-zA-Z0-9].txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
let found_a = results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("levelA.txt"));
let found_1 = results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"));
let found_2 = results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"));
assert!(
!results.is_empty(),
"Should find files with combined character class"
);
println!(
"Found levelA.txt: {}, level1.txt: {}, level2.txt: {}",
found_a, found_1, found_2
);
assert!(
found_a || found_1 || found_2,
"Should find at least one of the test files"
);
Ok(())
}
#[test]
fn test_traverse_with_negated_character_class() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/[!0-9]*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("sample.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_braces() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("*.{txt,md}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
let has_txt = results
.iter()
.any(|r| r.file_path.to_string_lossy().ends_with(".txt"));
let has_md = results
.iter()
.any(|r| r.file_path.to_string_lossy().ends_with(".md"));
assert!(has_txt);
assert!(has_md);
Ok(())
}
#[test]
fn test_traverse_with_multiple_braces() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/{text_files,nested}/*.{txt,md,rs}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
let text_files_count = results
.iter()
.filter(|r| r.file_path.to_string_lossy().contains("text_files"))
.count();
let nested_files_count = results
.iter()
.filter(|r| {
r.file_path.to_string_lossy().contains("nested")
&& !r.file_path.to_string_lossy().contains("nested/level")
})
.count();
assert!(
text_files_count > 0,
"Should find files in text_files directory"
);
assert!(
nested_files_count > 0,
"Should find files in nested directory"
);
for result in &results {
let path = result.file_path.to_string_lossy();
assert!(
path.ends_with(".txt") || path.ends_with(".md") || path.ends_with(".rs"),
"Found file with unexpected extension: {}",
path
);
}
Ok(())
}
#[test]
fn test_traverse_with_extension_match() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/*.md".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
assert!(result.file_path.to_string_lossy().ends_with(".md"));
}
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("markdown.md"))
);
Ok(())
}
#[test]
fn test_traverse_with_complex_pattern() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/level*/*.{txt,md}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(results.iter().any(|r| {
r.file_path
.to_string_lossy()
.contains("level1/level2/level2.txt")
}));
Ok(())
}
#[test]
fn test_traverse_with_alternation_patterns() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/{sample,config,regex_patterns}.{txt,toml}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("sample.txt")),
"Should find sample.txt"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("config.toml")),
"Should find config.toml"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("regex_patterns.txt")),
"Should find regex_patterns.txt"
);
Ok(())
}
#[test]
fn test_traverse_with_nested_star_patterns() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/nested/**/*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
let path = result.file_path.to_string_lossy();
assert!(
path.contains("nested") && path.ends_with(".txt"),
"Found unexpected file: {}",
path
);
}
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_directory_specific_pattern() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/text_files/*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
assert!(result.file_path.to_string_lossy().contains("text_files"));
assert!(result.file_path.to_string_lossy().ends_with(".txt"));
}
Ok(())
}
#[test]
fn test_traverse_with_filename_prefix() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("**/sample*".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
let filename = result.file_path.file_name().unwrap().to_string_lossy();
assert!(filename.starts_with("sample"));
}
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("sample.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_substring_pattern() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("level".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
assert!(result.file_path.to_string_lossy().contains("level"));
}
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_case_sensitive_substring() -> Result<()> {
let directory = Path::new("tests/fixtures");
let test_file_path1 = PathBuf::from("tests/fixtures/nested/CONFIG_upper.txt");
let test_file_path2 = PathBuf::from("tests/fixtures/nested/config_lower.txt");
std::fs::write(&test_file_path1, "Test file with uppercase name")?;
std::fs::write(&test_file_path2, "Test file with lowercase name")?;
let _cleanup = defer::defer(|| {
let _ = std::fs::remove_file(&test_file_path1);
let _ = std::fs::remove_file(&test_file_path2);
});
let options = TraverseOptions {
pattern: Some("CONFIG".to_string()),
case_sensitive: true,
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("CONFIG_upper.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("config_lower.txt"))
);
let options = TraverseOptions {
pattern: Some("config".to_string()),
case_sensitive: false,
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("CONFIG_upper.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("config_lower.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_partial_substring() -> Result<()> {
let directory = Path::new("tests/fixtures");
let options = TraverseOptions {
pattern: Some("xt_fi".to_string()), ..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
for result in &results {
assert!(result.file_path.to_string_lossy().contains("text_files"));
}
let options = TraverseOptions {
pattern: Some("mple.t".to_string()), ..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("sample.txt"))
);
Ok(())
}
#[test]
fn test_traverse_with_special_character_substring() -> Result<()> {
let directory = Path::new("tests/fixtures");
let test_file_path = PathBuf::from("tests/fixtures/nested/test-with-hyphens.txt");
std::fs::write(&test_file_path, "Test file with hyphens in name")?;
let _cleanup = defer::defer(|| {
let _ = std::fs::remove_file(&test_file_path);
});
let options = TraverseOptions {
pattern: Some("with-hyp".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty());
assert!(results.iter().any(|r| {
r.file_path
.to_string_lossy()
.contains("test-with-hyphens.txt")
}));
let options = TraverseOptions {
pattern: Some("with*hyp".to_string()), ..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(results.is_empty());
Ok(())
}
#[test]
#[serial]
fn test_traverse_with_anchored_patterns() -> Result<()> {
let directory = Path::new("tests/fixtures");
let prefix_file_path = PathBuf::from("tests/fixtures/nested/markdown-file.txt");
let suffix_file_path = PathBuf::from("tests/fixtures/nested/file-markdown.txt");
std::fs::write(&prefix_file_path, "File with markdown in prefix")?;
std::fs::write(&suffix_file_path, "File with markdown in suffix")?;
let _cleanup = defer::defer(|| {
let _ = std::fs::remove_file(&prefix_file_path);
let _ = std::fs::remove_file(&suffix_file_path);
});
let options = TraverseOptions {
pattern: Some("**/markdown*".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files starting with 'markdown'"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("markdown.md"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("markdown-file.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("file-markdown.txt"))
);
let options = TraverseOptions {
pattern: Some("**/*markdown.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files ending with 'markdown.txt'"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("file-markdown.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("markdown-file.txt"))
);
Ok(())
}
#[test]
#[serial]
fn test_traverse_with_mixed_glob_features() -> Result<()> {
let directory = Path::new("tests/fixtures");
let complex_dir = PathBuf::from("tests/fixtures/complex");
let nested_dir = complex_dir.join("nested123");
let deeply_nested = nested_dir.join("level-a").join("level-b");
fs::create_dir_all(&complex_dir)?;
fs::create_dir_all(&nested_dir)?;
fs::create_dir_all(&deeply_nested)?;
let files = vec![
(complex_dir.join("config-1.json"), "Config JSON file"),
(complex_dir.join("config-2.toml"), "Config TOML file"),
(complex_dir.join("data-10.csv"), "Data CSV file"),
(nested_dir.join("test-a.txt"), "Test A file"),
(nested_dir.join("test-b.md"), "Test B file"),
(nested_dir.join("dev-note.txt"), "Development note"),
(deeply_nested.join("deep-1.txt"), "Deep level 1"),
(deeply_nested.join("deep-2.md"), "Deep level 2"),
];
for (path, content) in files {
let mut file = File::create(&path)?;
write!(file, "{}", content)?;
}
let _cleanup = defer::defer(|| {
let _ = fs::remove_dir_all(&complex_dir);
});
let options = TraverseOptions {
pattern: Some("**/complex/**/[a-z]*-[0-9].{txt,md,json}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files with the complex pattern"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("config-1.json"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("deep-1.txt"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().ends_with(".toml")),
"Should not match .toml files"
);
let options = TraverseOptions {
pattern: Some("**/complex/**/[!0-9]*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match text files not starting with digits"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("test-a.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("dev-note.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("deep-1.txt"))
);
let options = TraverseOptions {
pattern: Some("**/complex/{nested123,level-?}/*-{a,b}.{txt,md}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files with complex brace expansion"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("test-a.txt"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("test-b.md"))
);
Ok(())
}
#[test]
#[serial]
fn test_traverse_with_extreme_patterns() -> Result<()> {
let directory = Path::new("tests/fixtures");
let extreme_dir = PathBuf::from("tests/fixtures/extreme");
fs::create_dir_all(&extreme_dir)?;
let levels = vec![
"level1",
"level2[abc]",
"level3{xyz}",
"level4(123)",
"level5-special",
];
let mut current_path = extreme_dir.clone();
for level in &levels {
current_path = current_path.join(level);
fs::create_dir_all(¤t_path)?;
}
let files = vec![
(extreme_dir.join("file1.txt"), "Top level file"),
(
extreme_dir.join("level1").join("level1-file.md"),
"Level 1 file",
),
(
extreme_dir
.join("level1")
.join("level2[abc]")
.join("level2-file.rs"),
"Level 2 file",
),
(
extreme_dir
.join("level1")
.join("level2[abc]")
.join("level3{xyz}")
.join("level3-file.toml"),
"Level 3 file",
),
(
extreme_dir
.join("level1")
.join("level2[abc]")
.join("level3{xyz}")
.join("level4(123)")
.join("level4-file.json"),
"Level 4 file",
),
(current_path.join("final-file.yaml"), "Final nested file"),
];
for (path, content) in files {
let mut file = File::create(&path)?;
write!(file, "{}", content)?;
}
let _cleanup = defer::defer(|| {
let _ = fs::remove_dir_all(&extreme_dir);
});
let options = TraverseOptions {
pattern: Some("**/extreme/**/level[0-9]*/*-file.{md,rs,toml}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files in the extreme nested structure"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1-file.md"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2-file.rs"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level3-file.toml"))
);
assert!(
!results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level4-file.json"))
);
let options = TraverseOptions {
pattern: Some("**/extreme/**/level5-special/final-file.yaml".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert_eq!(results.len(), 1, "Should match exactly one file");
assert!(
results[0]
.file_path
.to_string_lossy()
.contains("final-file.yaml")
);
let options = TraverseOptions {
pattern: Some("**/extreme/**/level[1-3]{*,*/*}/*{-file,}.{md,rs,toml}".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files with the extreme pattern"
);
let file_paths: Vec<_> = results
.iter()
.map(|r| r.file_path.to_string_lossy().to_string())
.collect();
println!("Found files: {:?}", file_paths);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level1-file.md"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level2-file.rs"))
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("level3-file.toml"))
);
Ok(())
}
#[test]
#[serial]
fn test_traverse_boundary_conditions() -> Result<()> {
let directory = Path::new("tests/fixtures");
let boundary_dir = PathBuf::from("tests/fixtures/boundary");
fs::create_dir_all(&boundary_dir)?;
let files = vec![
(boundary_dir.join(".txt"), "File with empty name"),
(boundary_dir.join("very_long_filename_with_many_characters_to_test_edge_cases_in_pattern_matching_implementation.txt"), "Very long filename"),
(boundary_dir.join("!@#$%.txt"), "Special characters only"),
(boundary_dir.join("unicode_カタカナ_😊_file.txt"), "Unicode characters"),
(boundary_dir.join("no_extension"), "File without extension"),
(boundary_dir.join("multiple.dots.in.filename.txt"), "Multiple dots"),
(boundary_dir.join(".hidden.txt"), "Hidden with extension"),
];
for (path, content) in files {
let mut file = File::create(&path)?;
write!(file, "{}", content)?;
}
let _cleanup = defer::defer(|| {
let _ = fs::remove_dir_all(&boundary_dir);
});
let options = TraverseOptions {
pattern: Some("**/boundary/*".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should find files in boundary directory"
);
let file_exists = results
.iter()
.any(|r| r.file_path.file_name().unwrap().to_string_lossy() == ".txt");
if file_exists {
let options = TraverseOptions {
pattern: Some("**/boundary/.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty(), "Should match file with empty name");
assert!(
results
.iter()
.any(|r| r.file_path.file_name().unwrap().to_string_lossy() == ".txt")
);
} else {
println!("Skipping .txt file test - file could not be created");
}
let options = TraverseOptions {
pattern: Some("**/boundary/very_*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty(), "Should match very long filename");
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("very_long_filename"))
);
let options = TraverseOptions {
pattern: Some("**/boundary/!@#$%.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match filename with special characters"
);
assert!(
results
.iter()
.any(|r| r.file_path.file_name().unwrap().to_string_lossy() == "!@#$%.txt")
);
let options = TraverseOptions {
pattern: Some("**/boundary/*カタカナ*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match filename with Unicode characters"
);
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("カタカナ"))
);
let options = TraverseOptions {
pattern: Some("**/boundary/no_extension".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty(), "Should match file with no extension");
assert!(
results
.iter()
.any(|r| r.file_path.file_name().unwrap().to_string_lossy() == "no_extension")
);
let options = TraverseOptions {
pattern: Some("**/boundary/*.dots.*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(!results.is_empty(), "Should match file with multiple dots");
assert!(
results
.iter()
.any(|r| r.file_path.file_name().unwrap().to_string_lossy()
== "multiple.dots.in.filename.txt")
);
let options = TraverseOptions {
pattern: Some("**/boundary/*".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
let special_files = results
.iter()
.filter(|r| {
let name = r.file_path.file_name().unwrap().to_string_lossy();
name.starts_with(".") || name.starts_with("!") || name.contains("カタカナ")
})
.count();
println!(
"Found {} special files in boundary directory",
special_files
);
if special_files >= 2 {
let options = TraverseOptions {
pattern: Some("**/boundary/{.*,!*,*カタカナ*}*".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
assert!(
!results.is_empty(),
"Should match files with non-standard naming"
);
println!("Matched {} files with non-standard naming", results.len());
} else {
println!("Skipping non-standard naming test - not enough special files created");
}
let options = TraverseOptions {
pattern: Some("**/boundary/*😊*.txt".to_string()),
..TraverseOptions::default()
};
let results = traverse_directory(directory, &options)?;
if !results.is_empty() {
assert!(
results
.iter()
.any(|r| r.file_path.to_string_lossy().contains("😊"))
);
}
Ok(())
}
}