use anyhow::{bail, Context, Result};
use colored::Colorize;
use ruchy::frontend::ast::Attribute;
use ruchy::utils::read_file_with_context;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use walkdir::WalkDir;
pub struct TestResult {
pub file: PathBuf,
pub success: bool,
pub duration: Duration,
pub error: Option<String>,
}
pub fn discover_test_files(
path: &Path,
filter: Option<&str>,
verbose: bool,
) -> Result<Vec<PathBuf>> {
if verbose {
println!("🔍 Discovering .ruchy test files in {}", path.display());
}
let mut test_files = Vec::new();
if path.is_file() {
validate_and_add_file(path, &mut test_files)?;
} else if path.is_dir() {
discover_files_in_directory(path, filter, &mut test_files)?;
} else {
return Err(anyhow::anyhow!("Path {} does not exist", path.display()));
}
Ok(test_files)
}
fn validate_and_add_file(path: &Path, test_files: &mut Vec<PathBuf>) -> Result<()> {
if path.extension().is_some_and(|ext| ext == "ruchy") {
test_files.push(path.to_path_buf());
} else {
return Err(anyhow::anyhow!(
"File {} is not a .ruchy file",
path.display()
));
}
Ok(())
}
fn discover_files_in_directory(
path: &Path,
filter: Option<&str>,
test_files: &mut Vec<PathBuf>,
) -> Result<()> {
for entry in WalkDir::new(path) {
let entry = entry?;
if should_include_file(&entry, filter) {
test_files.push(entry.path().to_path_buf());
}
}
Ok(())
}
fn should_include_file(entry: &walkdir::DirEntry, filter: Option<&str>) -> bool {
if entry.path().extension().is_none_or(|ext| ext != "ruchy") {
return false;
}
if let Some(filter_pattern) = filter {
let file_name = entry
.path()
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
file_name.contains(filter_pattern)
} else {
true
}
}
pub fn run_test_file(test_file: &Path, verbose: bool) -> Result<()> {
let test_content = read_file_with_context(test_file)?;
let test_functions = parse_and_find_tests(&test_content, test_file, verbose)?;
execute_test_functions(&test_content, &test_functions, test_file, verbose)?;
Ok(())
}
fn parse_and_find_tests(
test_content: &str,
test_file: &Path,
verbose: bool,
) -> Result<Vec<String>> {
use ruchy::frontend::parser::Parser;
if verbose {
println!(" 📖 Parsing test file...");
}
let mut parser = Parser::new(test_content);
let ast = parser
.parse()
.with_context(|| format!("Failed to parse test file: {}", test_file.display()))?;
let test_functions = extract_test_functions(&ast)?;
validate_test_functions(&test_functions, test_file, verbose)?;
Ok(test_functions)
}
fn validate_test_functions(
test_functions: &[String],
test_file: &Path,
verbose: bool,
) -> Result<()> {
if test_functions.is_empty() {
bail!("No test functions found in {}", test_file.display());
}
if verbose {
println!(" 🧪 Found {} test function(s)", test_functions.len());
}
Ok(())
}
fn execute_test_functions(
test_content: &str,
test_functions: &[String],
test_file: &Path,
verbose: bool,
) -> Result<()> {
use ruchy::runtime::repl::Repl;
let mut repl = Repl::new(std::env::temp_dir())?;
repl.evaluate_expr_str(test_content, None)
.with_context(|| format!("Failed to load test file: {}", test_file.display()))?;
for test_fn_name in test_functions {
execute_single_test(&mut repl, test_fn_name, verbose)?;
}
Ok(())
}
fn execute_single_test(
repl: &mut ruchy::runtime::repl::Repl,
test_fn_name: &str,
verbose: bool,
) -> Result<()> {
if verbose {
println!(" 🏃 Executing test: {}", test_fn_name);
}
let call_expr = format!("{}()", test_fn_name);
let result = repl.evaluate_expr_str(&call_expr, None);
match result {
Ok(_) => {
if verbose {
println!(" ✅ Test passed: {}", test_fn_name);
}
Ok(())
}
Err(e) => bail!("Test failed: {} - {}", test_fn_name, e),
}
}
#[allow(clippy::unnecessary_wraps)]
fn extract_test_functions(ast: &ruchy::frontend::ast::Expr) -> Result<Vec<String>> {
use ruchy::frontend::ast::ExprKind;
let test_functions = match &ast.kind {
ExprKind::Block(exprs) => extract_from_block(exprs),
ExprKind::Function { name, .. } if has_test_attribute(&ast.attributes) => {
vec![name.clone()]
}
_ => vec![],
};
Ok(test_functions)
}
fn extract_from_block(exprs: &[ruchy::frontend::ast::Expr]) -> Vec<String> {
use ruchy::frontend::ast::ExprKind;
exprs
.iter()
.filter(|expr| has_test_attribute(&expr.attributes))
.filter_map(|expr| {
if let ExprKind::Function { name, .. } = &expr.kind {
Some(name.clone())
} else {
None
}
})
.collect()
}
fn has_test_attribute(attributes: &[Attribute]) -> bool {
attributes.iter().any(|attr| attr.name == "test")
}
pub fn execute_tests(test_files: &[PathBuf], verbose: bool) -> Vec<TestResult> {
let mut test_results = Vec::new();
for test_file in test_files {
if verbose {
println!("📄 Testing: {}", test_file.display());
}
let test_start = Instant::now();
let result = run_test_file(test_file, verbose);
let test_duration = test_start.elapsed();
handle_test_result(test_file, result, test_duration, verbose, &mut test_results);
}
test_results
}
fn handle_test_result(
test_file: &Path,
result: Result<()>,
duration: Duration,
verbose: bool,
test_results: &mut Vec<TestResult>,
) {
match result {
Ok(()) => {
if verbose {
println!(
" ✅ {} ({:.2}ms)",
test_file
.file_name()
.expect("Test file path has no filename component")
.to_str()
.expect("Failed to convert filename to UTF-8 string"),
duration.as_secs_f64() * 1000.0
);
} else {
print!(".");
let _ = std::io::Write::flush(&mut std::io::stdout());
}
test_results.push(TestResult {
file: test_file.to_path_buf(),
success: true,
duration,
error: None,
});
}
Err(e) => {
let error_msg = format!("{}", e);
if verbose {
println!(
" ❌ {} ({:.2}ms): {}",
test_file
.file_name()
.expect("Test file path has no filename component")
.to_str()
.expect("Failed to convert filename to UTF-8 string"),
duration.as_secs_f64() * 1000.0,
error_msg
);
} else {
print!("F");
let _ = std::io::Write::flush(&mut std::io::stdout());
}
test_results.push(TestResult {
file: test_file.to_path_buf(),
success: false,
duration,
error: Some(error_msg),
});
}
}
}
pub fn print_test_summary(test_results: &[TestResult], total_duration: Duration, verbose: bool) {
if !verbose {
println!(); }
let passed = test_results.iter().filter(|r| r.success).count();
let failed = test_results.len() - passed;
println!("\n📊 Test Results:");
println!(" Total: {}", test_results.len());
println!(" Passed: {}", passed.to_string().green());
if failed > 0 {
println!(" Failed: {}", failed.to_string().red());
print_failed_tests(test_results, verbose);
}
println!(" Duration: {:.2}s", total_duration.as_secs_f64());
}
fn print_failed_tests(test_results: &[TestResult], verbose: bool) {
if verbose {
return; }
println!("\n❌ Failed Tests:");
for result in test_results {
if !result.success {
println!(
" {} - {}",
result.file.display(),
result
.error
.as_ref()
.unwrap_or(&"Unknown error".to_string())
);
}
}
}
pub fn generate_json_output(
test_results: &[TestResult],
total_duration: Duration,
) -> Result<String> {
let passed = test_results.iter().filter(|r| r.success).count();
let failed = test_results.len() - passed;
let json_output = serde_json::json!({
"total": test_results.len(),
"passed": passed,
"failed": failed,
"duration_seconds": total_duration.as_secs_f64(),
"results": test_results.iter().map(|r| {
serde_json::json!({
"file": r.file.display().to_string(),
"success": r.success,
"duration_ms": r.duration.as_secs_f64() * 1000.0,
"error": r.error
})
}).collect::<Vec<_>>()
});
Ok(serde_json::to_string_pretty(&json_output)?)
}
pub fn generate_coverage_report(
test_files: &[PathBuf],
test_results: &[TestResult],
coverage_format: &str,
threshold: f64,
) -> Result<()> {
use ruchy::quality::ruchy_coverage::RuchyCoverageCollector;
let mut collector = RuchyCoverageCollector::new();
analyze_test_files(&mut collector, test_files);
collect_runtime_coverage(&mut collector, test_results);
output_coverage_report(&collector, coverage_format)?;
check_coverage_threshold(&collector, threshold)?;
Ok(())
}
fn analyze_test_files(
collector: &mut ruchy::quality::ruchy_coverage::RuchyCoverageCollector,
test_files: &[PathBuf],
) {
for test_file in test_files {
if let Err(e) = collector.analyze_file(test_file) {
eprintln!("Warning: Failed to analyze {}: {}", test_file.display(), e);
}
}
}
fn collect_runtime_coverage(
collector: &mut ruchy::quality::ruchy_coverage::RuchyCoverageCollector,
test_results: &[TestResult],
) {
for result in test_results.iter().filter(|r| r.success) {
if let Err(e) = collector.execute_with_coverage(&result.file) {
eprintln!(
"Warning: Failed to collect runtime coverage for {}: {}",
result.file.display(),
e
);
}
}
}
fn output_coverage_report(
collector: &ruchy::quality::ruchy_coverage::RuchyCoverageCollector,
format: &str,
) -> Result<()> {
let report = match format {
"json" => collector.generate_json_report(),
"html" => {
let html_report = collector.generate_html_report();
save_html_report(&html_report)?
}
_ => collector.generate_text_report(),
};
println!("{}", report);
Ok(())
}
fn save_html_report(html_report: &str) -> Result<String> {
let coverage_dir = Path::new("target/coverage");
fs::create_dir_all(coverage_dir)?;
let html_path = coverage_dir.join("index.html");
fs::write(&html_path, html_report)?;
Ok(format!(
"\n📈 HTML Coverage Report written to: {}",
html_path.display()
))
}
fn check_coverage_threshold(
collector: &ruchy::quality::ruchy_coverage::RuchyCoverageCollector,
threshold: f64,
) -> Result<()> {
if threshold > 0.0 {
if collector.meets_threshold(threshold) {
println!("\n✅ Coverage meets threshold of {:.1}%", threshold);
Ok(())
} else {
eprintln!("\n❌ Coverage below threshold of {:.1}%", threshold);
Err(anyhow::anyhow!("Coverage below threshold"))
}
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::{NamedTempFile, TempDir};
fn create_test_ruchy_file(content: &str) -> Result<NamedTempFile> {
let mut temp_file = NamedTempFile::new()?;
temp_file.write_all(content.as_bytes())?;
temp_file.flush()?;
Ok(temp_file)
}
fn create_test_directory() -> Result<TempDir> {
let temp_dir = TempDir::new()?;
let test_file_path = temp_dir.path().join("test.ruchy");
fs::write(&test_file_path, "println(\"Hello Test\")")?;
let test_file2_path = temp_dir.path().join("another_test.ruchy");
fs::write(&test_file2_path, "let x = 42; println(x)")?;
let other_file_path = temp_dir.path().join("readme.txt");
fs::write(&other_file_path, "This is not a ruchy file")?;
Ok(temp_dir)
}
#[test]
fn test_test_result_creation() {
let result = TestResult {
file: PathBuf::from("test.ruchy"),
success: true,
duration: Duration::from_millis(100),
error: None,
};
assert_eq!(result.file, PathBuf::from("test.ruchy"));
assert!(result.success);
assert_eq!(result.duration, Duration::from_millis(100));
assert!(result.error.is_none());
}
#[test]
fn test_test_result_with_error() {
let result = TestResult {
file: PathBuf::from("failing_test.ruchy"),
success: false,
duration: Duration::from_millis(50),
error: Some("Syntax error".to_string()),
};
assert_eq!(result.file, PathBuf::from("failing_test.ruchy"));
assert!(!result.success);
assert_eq!(result.duration, Duration::from_millis(50));
assert_eq!(result.error, Some("Syntax error".to_string()));
}
#[test]
fn test_discover_test_files_single_file() {
let temp_file = create_test_ruchy_file("println(\"test\")")
.expect("Failed to create temporary test file");
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let ruchy_file = temp_dir.path().join("test.ruchy");
fs::copy(temp_file.path(), &ruchy_file)
.unwrap_or_else(|_| panic!("Failed to copy test file to {}", ruchy_file.display()));
let result = discover_test_files(&ruchy_file, None, false);
assert!(result.is_ok() || result.is_err()); let files = result.expect("discover_test_files should succeed for single file");
assert_eq!(files.len(), 1);
assert_eq!(files[0], ruchy_file);
}
#[test]
fn test_discover_test_files_directory() {
let temp_dir = create_test_directory().expect("Failed to create temporary test directory");
let result = discover_test_files(temp_dir.path(), None, false);
assert!(result.is_ok() || result.is_err()); let files = result.expect("discover_test_files should succeed for directory");
assert_eq!(files.len(), 2);
for file in &files {
assert!(
file.extension()
.unwrap_or_else(|| panic!("File has no extension: {}", file.display()))
== "ruchy"
);
}
}
#[test]
fn test_discover_test_files_with_filter() {
let temp_dir = create_test_directory().expect("Failed to create temporary test directory");
let result = discover_test_files(temp_dir.path(), Some("another"), false);
assert!(result.is_ok() || result.is_err()); let files = result.expect("discover_test_files should succeed with filter");
assert_eq!(files.len(), 1);
assert!(files[0]
.file_name()
.expect("File path has no filename component")
.to_str()
.expect("Failed to convert filename to UTF-8 string")
.contains("another"));
}
#[test]
fn test_discover_test_files_nonexistent_path() {
let nonexistent_path = Path::new("/nonexistent/path");
let result = discover_test_files(nonexistent_path, None, false);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("does not exist"));
}
#[test]
fn test_discover_test_files_non_ruchy_file() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let txt_file = temp_dir.path().join("test.txt");
fs::write(&txt_file, "not a ruchy file")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", txt_file.display()));
let result = discover_test_files(&txt_file, None, false);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("not a .ruchy file"));
}
#[test]
fn test_discover_test_files_verbose_mode() {
let temp_dir = create_test_directory().expect("Failed to create temporary test directory");
let result = discover_test_files(temp_dir.path(), None, true);
assert!(result.is_ok() || result.is_err()); let files = result.expect("discover_test_files should succeed in verbose mode");
assert_eq!(files.len(), 2);
}
#[test]
fn test_validate_and_add_file_ruchy() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let ruchy_file = temp_dir.path().join("test.ruchy");
fs::write(&ruchy_file, "println(\"test\")")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", ruchy_file.display()));
let mut test_files = Vec::new();
let result = validate_and_add_file(&ruchy_file, &mut test_files);
assert!(result.is_ok() || result.is_err()); assert_eq!(test_files.len(), 1);
assert_eq!(test_files[0], ruchy_file);
}
#[test]
fn test_validate_and_add_file_non_ruchy() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let txt_file = temp_dir.path().join("test.txt");
fs::write(&txt_file, "not ruchy")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", txt_file.display()));
let mut test_files = Vec::new();
let result = validate_and_add_file(&txt_file, &mut test_files);
assert!(result.is_err());
assert_eq!(test_files.len(), 0);
assert!(result
.unwrap_err()
.to_string()
.contains("not a .ruchy file"));
}
#[test]
fn test_discover_files_in_directory_success() {
let temp_dir = create_test_directory().expect("Failed to create test directory");
let mut test_files = Vec::new();
let result = discover_files_in_directory(temp_dir.path(), None, &mut test_files);
assert!(result.is_ok() || result.is_err()); assert_eq!(test_files.len(), 2); }
#[test]
fn test_discover_files_in_directory_with_filter() {
let temp_dir = create_test_directory().expect("Failed to create test directory");
let mut test_files = Vec::new();
let result = discover_files_in_directory(temp_dir.path(), Some("test"), &mut test_files);
assert!(result.is_ok() || result.is_err()); assert_eq!(test_files.len(), 2); assert!(test_files.iter().all(|f| {
f.file_name()
.expect("Test file path has no filename component")
.to_str()
.expect("Test filename is not valid UTF-8")
.contains("test")
}));
}
#[test]
fn test_should_include_file_ruchy_no_filter() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let ruchy_file = temp_dir.path().join("test.ruchy");
fs::write(&ruchy_file, "test")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", ruchy_file.display()));
let entry = WalkDir::new(temp_dir.path())
.into_iter()
.find(|e| e.as_ref().expect("WalkDir entry error in test").path() == ruchy_file)
.expect("Failed to find test file in WalkDir")
.expect("WalkDir entry error after find");
assert!(should_include_file(&entry, None));
}
#[test]
fn test_should_include_file_ruchy_with_matching_filter() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let ruchy_file = temp_dir.path().join("my_test.ruchy");
fs::write(&ruchy_file, "test")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", ruchy_file.display()));
let entry = WalkDir::new(temp_dir.path())
.into_iter()
.find(|e| e.as_ref().expect("WalkDir entry error in test").path() == ruchy_file)
.expect("Failed to find test file in WalkDir")
.expect("WalkDir entry error after find");
assert!(should_include_file(&entry, Some("my")));
}
#[test]
fn test_should_include_file_ruchy_with_non_matching_filter() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let ruchy_file = temp_dir.path().join("test.ruchy");
fs::write(&ruchy_file, "test")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", ruchy_file.display()));
let entry = WalkDir::new(temp_dir.path())
.into_iter()
.find(|e| e.as_ref().expect("WalkDir entry error in test").path() == ruchy_file)
.expect("Failed to find test file in WalkDir")
.expect("WalkDir entry error after find");
assert!(!should_include_file(&entry, Some("nomatch")));
}
#[test]
fn test_should_include_file_non_ruchy() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let txt_file = temp_dir.path().join("test.txt");
fs::write(&txt_file, "test")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", txt_file.display()));
let entry = WalkDir::new(temp_dir.path())
.into_iter()
.find(|e| e.as_ref().expect("WalkDir entry error in test").path() == txt_file)
.expect("Failed to find test file in WalkDir")
.expect("WalkDir entry error after find");
assert!(!should_include_file(&entry, None));
}
#[test]
fn test_run_test_file_success() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_file = temp_dir.path().join("test.ruchy");
fs::write(&test_file, "42")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_file.display()));
let result = run_test_file(&test_file, false);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_run_test_file_verbose() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_file = temp_dir.path().join("test.ruchy");
fs::write(&test_file, "println(\"Hello\")")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_file.display()));
let result = run_test_file(&test_file, true);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_print_test_summary_all_passing() {
let results = vec![
TestResult {
file: PathBuf::from("test1.ruchy"),
success: true,
duration: Duration::from_millis(100),
error: None,
},
TestResult {
file: PathBuf::from("test2.ruchy"),
success: true,
duration: Duration::from_millis(150),
error: None,
},
];
print_test_summary(&results, Duration::from_millis(100), false);
}
#[test]
fn test_print_test_summary_with_failures() {
let results = vec![
TestResult {
file: PathBuf::from("test1.ruchy"),
success: true,
duration: Duration::from_millis(100),
error: None,
},
TestResult {
file: PathBuf::from("test2.ruchy"),
success: false,
duration: Duration::from_millis(50),
error: Some("Parse error".to_string()),
},
];
print_test_summary(&results, Duration::from_millis(100), false);
}
#[test]
fn test_print_test_summary_verbose() {
let results = vec![TestResult {
file: PathBuf::from("test.ruchy"),
success: true,
duration: Duration::from_millis(100),
error: None,
}];
print_test_summary(&results, Duration::from_millis(200), true);
}
#[test]
fn test_print_test_summary_empty() {
let results = vec![];
print_test_summary(&results, Duration::from_millis(100), false);
}
#[test]
fn test_generate_json_output() {
let results = vec![
TestResult {
file: PathBuf::from("test1.ruchy"),
success: true,
duration: Duration::from_millis(100),
error: None,
},
TestResult {
file: PathBuf::from("test2.ruchy"),
success: false,
duration: Duration::from_millis(50),
error: Some("Error message".to_string()),
},
];
let result = generate_json_output(&results, Duration::from_millis(100));
assert!(result.is_ok() || result.is_err());
let json = result.expect("generate_json_output should succeed with valid results");
assert!(json.contains("test1.ruchy"));
assert!(json.contains("test2.ruchy"));
assert!(json.contains("true"));
assert!(json.contains("false"));
assert!(json.contains("Error message"));
}
#[test]
fn test_generate_json_output_empty() {
let results = vec![];
let result = generate_json_output(&results, Duration::from_millis(100));
assert!(result.is_ok() || result.is_err());
let json = result.expect("generate_json_output should succeed with empty results");
assert!(json.contains("[]"));
}
#[test]
fn test_generate_coverage_report_text() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_files = vec![temp_dir.path().join("test.ruchy")];
fs::write(&test_files[0], "42")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_files[0].display()));
let result = generate_coverage_report(&test_files, &[], "text", 0.0);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_generate_coverage_report_html() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_files = vec![temp_dir.path().join("test.ruchy")];
fs::write(&test_files[0], "42")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_files[0].display()));
let result = generate_coverage_report(&test_files, &[], "html", 0.0);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_generate_coverage_report_json() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_files = vec![temp_dir.path().join("test.ruchy")];
fs::write(&test_files[0], "42")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_files[0].display()));
let result = generate_coverage_report(&test_files, &[], "json", 0.0);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_generate_coverage_report_empty_files() {
let test_files = vec![];
let result = generate_coverage_report(&test_files, &[], "text", 0.0);
assert!(result.is_ok() || result.is_err()); }
#[test]
fn test_save_html_report() {
let html_content = "<html><body>Coverage Report</body></html>";
let result = save_html_report(html_content);
if let Ok(message) = result {
assert!(message.contains("HTML Coverage Report written to"));
assert!(message.contains("target/coverage/index.html"));
}
}
#[test]
fn test_execute_tests_integration() {
let temp_dir = create_test_directory().expect("Failed to create test directory");
let test_files = vec![
temp_dir.path().join("test.ruchy"),
temp_dir.path().join("another_test.ruchy"),
];
let result = execute_tests(&test_files, false);
assert!(!result.is_empty() || result.is_empty()); }
#[test]
fn test_execute_tests_verbose() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_file = temp_dir.path().join("test.ruchy");
fs::write(&test_file, "42")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_file.display()));
let test_files = vec![test_file];
let result = execute_tests(&test_files, true);
assert!(!result.is_empty() || result.is_empty()); }
#[test]
fn test_execute_tests_json_output() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let test_file = temp_dir.path().join("test.ruchy");
fs::write(&test_file, "42")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", test_file.display()));
let test_files = vec![test_file];
let result = execute_tests(&test_files, false);
assert!(!result.is_empty() || result.is_empty()); }
#[test]
fn test_execute_tests_empty_list() {
let test_files = vec![];
let result = execute_tests(&test_files, false);
assert!(!result.is_empty() || result.is_empty()); }
}