use fast_fs::{read_dir_stream, TraversalOptions};
use futures::StreamExt;
use std::collections::HashSet;
use tempfile::TempDir;
use tokio::fs;
#[tokio::test]
async fn test_stream_yields_entries() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::write(root.join("file1.txt"), "content1").await.unwrap();
fs::write(root.join("file2.txt"), "content2").await.unwrap();
fs::create_dir(root.join("subdir")).await.unwrap();
fs::write(root.join("subdir/file3.txt"), "content3")
.await
.unwrap();
let options = TraversalOptions::default().with_max_depth(10);
let mut stream = Box::pin(read_dir_stream(root, options));
let mut entries = Vec::new();
while let Some(result) = stream.next().await {
entries.push(result.unwrap());
}
assert_eq!(entries.len(), 4);
let paths: HashSet<String> = entries
.iter()
.map(|e| e.name.clone())
.collect();
assert!(paths.contains("file1.txt"));
assert!(paths.contains("file2.txt"));
assert!(paths.contains("subdir"));
assert!(paths.contains("file3.txt"));
}
#[tokio::test]
async fn test_stream_respects_options() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::write(root.join("file.rs"), "rust").await.unwrap();
fs::write(root.join("file.py"), "python").await.unwrap();
fs::write(root.join("file.txt"), "text").await.unwrap();
fs::write(root.join(".hidden"), "secret").await.unwrap();
let options = TraversalOptions::default()
.with_extensions(&["rs"])
.with_max_depth(10);
let mut stream = Box::pin(read_dir_stream(root, options));
let mut entries = Vec::new();
while let Some(result) = stream.next().await {
entries.push(result.unwrap());
}
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].name, "file.rs");
}
#[tokio::test]
async fn test_stream_matches_batch() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::write(root.join("a.txt"), "a").await.unwrap();
fs::create_dir(root.join("dir1")).await.unwrap();
fs::write(root.join("dir1/b.txt"), "b").await.unwrap();
fs::create_dir(root.join("dir1/dir2")).await.unwrap();
fs::write(root.join("dir1/dir2/c.txt"), "c").await.unwrap();
let options = TraversalOptions::default().with_max_depth(10);
let mut stream = Box::pin(read_dir_stream(root, options.clone()));
let mut stream_entries = Vec::new();
while let Some(result) = stream.next().await {
stream_entries.push(result.unwrap());
}
use fast_fs::read_dir_recursive;
let options = TraversalOptions::default()
.with_max_depth(10)
.with_gitignore(false);
let batch_entries = read_dir_recursive(root, options).await.unwrap();
let stream_paths: HashSet<String> = stream_entries
.iter()
.map(|e| e.path.to_string_lossy().to_string())
.collect();
let batch_paths: HashSet<String> = batch_entries
.iter()
.map(|e| e.path.to_string_lossy().to_string())
.collect();
assert_eq!(stream_paths, batch_paths);
assert_eq!(stream_entries.len(), batch_entries.len());
}
#[tokio::test]
async fn test_stream_empty_directory() {
let temp = TempDir::new().unwrap();
let root = temp.path();
let options = TraversalOptions::default().with_max_depth(10);
let mut stream = Box::pin(read_dir_stream(root, options));
let mut count = 0;
while let Some(result) = stream.next().await {
result.unwrap();
count += 1;
}
assert_eq!(count, 0);
}
#[tokio::test]
async fn test_stream_respects_max_depth() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::create_dir(root.join("level1")).await.unwrap();
fs::write(root.join("level1/file1.txt"), "1").await.unwrap();
fs::create_dir(root.join("level1/level2")).await.unwrap();
fs::write(root.join("level1/level2/file2.txt"), "2")
.await
.unwrap();
fs::create_dir(root.join("level1/level2/level3"))
.await
.unwrap();
fs::write(root.join("level1/level2/level3/file3.txt"), "3")
.await
.unwrap();
let options = TraversalOptions::default().with_max_depth(1);
let mut stream = Box::pin(read_dir_stream(root, options));
let mut entries = Vec::new();
while let Some(result) = stream.next().await {
entries.push(result.unwrap());
}
assert_eq!(entries.len(), 3);
let names: HashSet<String> = entries.iter().map(|e| e.name.clone()).collect();
assert!(names.contains("level1"));
assert!(names.contains("file1.txt"));
assert!(names.contains("level2"));
assert!(!names.contains("file2.txt"));
assert!(!names.contains("file3.txt"));
}
#[tokio::test]
async fn test_stream_gitignore_filtering() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::write(root.join(".gitignore"), "*.log\nignored/\n")
.await
.unwrap();
fs::write(root.join("keep.txt"), "keep").await.unwrap();
fs::write(root.join("remove.log"), "remove").await.unwrap();
fs::create_dir(root.join("ignored")).await.unwrap();
fs::write(root.join("ignored/file.txt"), "ignored")
.await
.unwrap();
let options = TraversalOptions::default()
.with_gitignore(true)
.with_max_depth(10);
let mut stream = Box::pin(read_dir_stream(root, options));
let mut entries = Vec::new();
while let Some(result) = stream.next().await {
entries.push(result.unwrap());
}
let names: HashSet<String> = entries.iter().map(|e| e.name.clone()).collect();
assert!(names.contains(".gitignore") || !names.contains(".gitignore")); assert!(names.contains("keep.txt"));
assert!(!names.contains("remove.log"));
assert!(!names.contains("ignored"));
assert!(!names.contains("file.txt"));
}
#[tokio::test]
async fn test_stream_backpressure() {
let temp = TempDir::new().unwrap();
let root = temp.path();
for i in 0..100 {
fs::write(root.join(format!("file{}.txt", i)), format!("content{}", i))
.await
.unwrap();
}
let options = TraversalOptions::default().with_max_depth(10);
let mut stream = Box::pin(read_dir_stream(root, options));
let mut count = 0;
while let Some(result) = stream.next().await {
result.unwrap();
count += 1;
tokio::time::sleep(tokio::time::Duration::from_micros(10)).await;
}
assert_eq!(count, 100);
}