#![allow(missing_docs)]
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs::{self, File};
use std::io::Write;
use tempfile::TempDir;
fn ruchy_cmd() -> Command {
assert_cmd::cargo::cargo_bin_cmd!("ruchy")
}
fn create_test_tree() -> TempDir {
let temp = TempDir::new().expect("Failed to create temp dir");
let root = temp.path();
fs::create_dir(root.join("dir1")).unwrap();
fs::create_dir(root.join("dir2")).unwrap();
fs::create_dir(root.join("dir1/subdir")).unwrap();
File::create(root.join("file1.txt")).unwrap();
File::create(root.join("file2.log")).unwrap();
File::create(root.join("dir1/file3.txt")).unwrap();
File::create(root.join("dir1/subdir/file4.txt")).unwrap();
File::create(root.join("dir2/file5.log")).unwrap();
temp
}
fn create_test_files_with_content() -> TempDir {
let temp = TempDir::new().expect("Failed to create temp dir");
let root = temp.path();
let mut file1 = File::create(root.join("test1.txt")).unwrap();
writeln!(file1, "This is a test file").unwrap();
writeln!(file1, "It contains an error message").unwrap();
writeln!(file1, "And some other content").unwrap();
let mut file2 = File::create(root.join("test2.log")).unwrap();
writeln!(file2, "Log file contents").unwrap();
writeln!(file2, "ERROR: Something went wrong").unwrap();
writeln!(file2, "INFO: Normal operation").unwrap();
temp
}
#[test]
fn test_stdlib005_walk_basic() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
assert(entries.len() > 0, "Should find at least one entry")
println("Found {{}} entries", entries.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_walk_returns_array() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
// Verify it's an array by checking it has length and can be indexed
assert(entries.len() >= 0, "walk() should return an array with length")
println("Type check passed")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Type check passed"));
}
#[test]
fn test_stdlib005_walk_file_entries_have_fields() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
let first = entries[0]
// FileEntry should have required fields
assert(first.path != nil, "Should have path field")
assert(first.name != nil, "Should have name field")
assert(first.is_file != nil || first.is_dir != nil, "Should have type fields")
println("Field check passed")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Field check passed"));
}
#[test]
fn test_stdlib005_walk_filter_files() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
let files = entries.filter(|e| e.is_file)
assert(files.len() > 0, "Should find at least one file")
println("Found {{}} files", files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_walk_filter_directories() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
let dirs = entries.filter(|e| e.is_dir)
assert(dirs.len() > 0, "Should find at least one directory")
println("Found {{}} directories", dirs.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_walk_recursive() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
// Should find files in subdirectories (recursive)
// We created file4.txt in dir1/subdir/
let deep_files = entries.filter(|e| e.path.contains("subdir"))
assert(deep_files.len() > 0, "Should find files in subdirectories")
println("Recursive walk successful")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Recursive walk successful"));
}
#[test]
fn test_stdlib005_walk_depth_field() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
// Check that depth field exists and has valid values
let depths = entries.map(|e| e.depth)
let has_zero = depths.contains(0) // Root level
assert(has_zero, "Should have depth 0 entries")
println("Depth field verified")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Depth field verified"));
}
#[test]
fn test_stdlib005_walk_filter_by_extension() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
let txt_files = entries.filter(|e| e.path.ends_with(".txt"))
assert(txt_files.len() > 0, "Should find .txt files")
println("Found {{}} .txt files", txt_files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_walk_empty_directory() {
let temp = TempDir::new().unwrap();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk("{path}")
// Empty directory should return at least the directory itself
assert(entries.len() >= 0, "Should handle empty directory")
println("Empty directory handled correctly")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains(
"Empty directory handled correctly",
));
}
#[test]
fn test_stdlib005_walk_nonexistent_path_error() {
let code = r#"
let entries = walk("/nonexistent/path/that/does/not/exist")
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.code(predicate::in_iter([0, 1])); }
#[test]
fn test_stdlib005_glob_basic_pattern() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let pattern = "{path}/*.txt"
let files = glob(pattern)
assert(files.len() > 0, "Should find .txt files")
println("Found {{}} .txt files", files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_glob_recursive_pattern() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let pattern = "{path}/**/*.txt"
let files = glob(pattern)
assert(files.len() >= 3, "Should find all .txt files recursively")
println("Found {{}} .txt files recursively", files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_glob_returns_string_array() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let pattern = "{path}/*.txt"
let files = glob(pattern)
let first = files[0]
// String paths should be printable
println("First file: {{}}", first)
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("First file:"));
}
#[test]
fn test_stdlib005_glob_filter_by_extension() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let txt_files = glob("{path}/**/*.txt")
let log_files = glob("{path}/**/*.log")
assert(txt_files.len() > 0, "Should find .txt files")
assert(log_files.len() > 0, "Should find .log files")
println("Found {{}} .txt and {{}} .log files", txt_files.len(), log_files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_glob_no_matches() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let files = glob("{path}/**/*.nonexistent")
assert(files.len() == 0, "Should return empty array for no matches")
println("No matches found (expected)")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("No matches found"));
}
#[test]
fn test_stdlib005_glob_absolute_paths() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let files = glob("{path}/**/*.txt")
// Results should be absolute paths
assert(files[0].starts_with("/") || files[0].starts_with("C:"), "Should return absolute paths")
println("Absolute paths verified")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Absolute paths verified"));
}
#[test]
fn test_stdlib005_find_library_function() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
// Library function: find() delegates to walk().filter()
fn find(path, predicate) {{
walk(path).filter(predicate)
}}
// Use the library function
let files = find("{path}", |e| e.is_file)
assert(files.len() > 0, "Should find files")
println("Found {{}} files via library function", files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_find_txt_files() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
fn find(path, predicate) {{ walk(path).filter(predicate) }}
let txt_files = find("{path}", |e| e.is_file && e.path.ends_with(".txt"))
assert(txt_files.len() >= 3, "Should find .txt files")
println("Found {{}} .txt files", txt_files.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_find_demonstrates_composability() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
// Demonstrates architectural layering:
// 1. walk() is a Rust builtin (low-level, efficient)
// 2. filter() is a Rust builtin (higher-order function)
// 3. find() is a Ruchy library function (convenience wrapper)
let dirs = walk("{path}").filter(|e| e.is_dir)
assert(dirs.len() >= 3, "Should find directories")
println("Composability: {{}} directories found", dirs.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Composability"));
}
#[test]
fn test_stdlib005_search_basic() {
let temp = create_test_files_with_content();
let path = temp.path().display().to_string();
let code = format!(
r#"
let matches = search("error", "{path}")
assert(matches.len() > 0, "Should find matches for 'error'")
println("Found {{}} matches", matches.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_search_returns_array() {
let temp = create_test_files_with_content();
let path = temp.path().display().to_string();
let code = format!(
r#"
let matches = search("test", "{path}")
assert(matches.len() >= 0, "Should return an array")
println("Search returned array with {{}} matches", matches.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Search returned array"));
}
#[test]
fn test_stdlib005_search_match_has_fields() {
let temp = create_test_files_with_content();
let path = temp.path().display().to_string();
let code = format!(
r#"
let matches = search("error", "{path}")
let first = matches[0]
// SearchMatch should have required fields
assert(first.path != nil, "Should have path field")
assert(first.line_num > 0, "Should have line_num field")
assert(first.line != nil, "Should have line field")
println("SearchMatch fields validated")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("SearchMatch fields validated"));
}
#[test]
fn test_stdlib005_search_case_insensitive() {
let temp = create_test_files_with_content();
let path = temp.path().display().to_string();
let code = format!(
r#"
// Should find both "error" and "ERROR"
let matches = search("ERROR", "{path}", {{
case_insensitive: true
}})
assert(matches.len() > 0, "Should find case-insensitive matches")
println("Found {{}} case-insensitive matches", matches.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_search_no_matches() {
let temp = create_test_files_with_content();
let path = temp.path().display().to_string();
let code = format!(
r#"
let matches = search("nonexistentpattern12345", "{path}")
assert(matches.len() == 0, "Should return empty array for no matches")
println("No matches found (expected)")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("No matches found"));
}
#[test]
fn test_stdlib005_search_multiple_files() {
let temp = create_test_files_with_content();
let path = temp.path().display().to_string();
let code = format!(
r#"
let matches = search("test", "{path}")
// Should find matches in multiple files (test1.txt and test2.log)
assert(matches.len() > 0, "Should find matches across multiple files")
println("Found {{}} matches across files", matches.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Found"));
}
#[test]
fn test_stdlib005_walk_with_options_max_depth() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk_with_options("{path}", {{
max_depth: 1
}})
// Should only include root and first level, not subdir/file4.txt
let paths = entries.map(|e| e.path)
let deep_file = paths.filter(|p| p.contains("file4.txt"))
assert(deep_file.len() == 0, "Should not include files deeper than max_depth")
println("Max depth test passed")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Max depth test passed"));
}
#[test]
fn test_stdlib005_walk_with_options_min_depth() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk_with_options("{path}", {{
min_depth: 1
}})
// Should not include the root directory itself
let paths = entries.map(|e| e.path)
let root_entries = paths.filter(|p| p == "{path}")
assert(root_entries.len() == 0, "Should not include root directory with min_depth: 1")
println("Min depth test passed")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Min depth test passed"));
}
#[test]
fn test_stdlib005_walk_with_options_empty_options() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk_with_options("{path}", {{}})
// Should return FileEntry array like walk()
assert(entries.len() > 0, "Should return entries with empty options")
assert(entries[0].path != nil, "Entries should have path field")
println("Empty options test passed")
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Empty options test passed"));
}
#[test]
fn test_stdlib005_walk_with_options_combined() {
let temp = create_test_tree();
let path = temp.path().display().to_string();
let code = format!(
r#"
let entries = walk_with_options("{path}", {{
max_depth: 2,
min_depth: 1
}})
// Should only include depth 1 and 2, not root (0) or deeper (3+)
assert(entries.len() > 0, "Should have some entries")
println("Combined options test passed: {{}} entries", entries.len())
"#
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success()
.stdout(predicate::str::contains("Combined options test passed"));
}