use super::test_helpers::{
discover_test_files, execute_tests, generate_coverage_report, generate_json_output,
print_test_summary, TestResult,
};
use anyhow::Result;
use std::path::{Path, PathBuf};
use std::time::Instant;
pub fn handle_test_command(
path: Option<PathBuf>,
watch: bool,
verbose: bool,
filter: Option<&str>,
coverage: bool,
coverage_format: &str,
parallel: usize,
threshold: f64,
format: &str,
) -> Result<()> {
let test_path = path.unwrap_or_else(|| PathBuf::from("."));
if watch {
handle_watch_mode(&test_path, verbose, filter)
} else {
run_tests(
&test_path,
verbose,
filter,
coverage,
coverage_format,
parallel,
threshold,
format,
)
}
}
fn run_tests(
path: &Path,
verbose: bool,
filter: Option<&str>,
coverage: bool,
coverage_format: &str,
_parallel: usize, threshold: f64,
format: &str,
) -> Result<()> {
let test_files = discover_test_files(path, filter, verbose)?;
if test_files.is_empty() {
anyhow::bail!("No .ruchy test files found in {}", path.display());
}
println!("🧪 Running {} .ruchy test files...\n", test_files.len());
let total_start = Instant::now();
let test_results = execute_tests(&test_files, verbose);
let total_duration = total_start.elapsed();
print_test_summary(&test_results, total_duration, verbose);
if format == "json" {
let json = generate_json_output(&test_results, total_duration)?;
println!("\n{}", json);
}
if coverage {
generate_coverage_report(&test_files, &test_results, coverage_format, threshold)?;
}
check_test_failures(&test_results)?;
println!("\n✅ All tests passed!");
Ok(())
}
pub(crate) fn start_watch_mode(path: &Path, verbose: bool, filter: Option<&str>) {
use colored::Colorize;
println!(
"{} Watching {} for test changes...",
"👁".bright_cyan(),
path.display()
);
println!("Press Ctrl+C to stop watching\n");
let _ = run_tests(path, verbose, filter, false, "text", 1, 0.0, "text");
}
pub(crate) fn run_watch_iteration(
path: &Path,
verbose: bool,
filter: Option<&str>,
last_modified: &mut std::time::SystemTime,
) -> bool {
use colored::Colorize;
let current_modified = get_latest_modification(path);
if current_modified <= *last_modified {
return false;
}
*last_modified = current_modified;
println!("\n{} Files changed, running tests...", "→".bright_cyan());
let _ = run_tests(path, verbose, filter, false, "text", 1, 0.0, "text");
true
}
fn handle_watch_mode(path: &Path, verbose: bool, filter: Option<&str>) -> Result<()> {
use std::thread;
use std::time::Duration;
start_watch_mode(path, verbose, filter);
let mut last_modified = get_latest_modification(path);
loop {
thread::sleep(Duration::from_millis(1000));
run_watch_iteration(path, verbose, filter, &mut last_modified);
}
}
fn ruchy_file_mtime(entry: &std::fs::DirEntry) -> Option<std::time::SystemTime> {
let path = entry.path().canonicalize().ok()?;
if path.extension().and_then(|ext| ext.to_str()) != Some("ruchy") {
return None;
}
std::fs::metadata(&path).ok()?.modified().ok()
}
fn get_latest_modification(path: &Path) -> std::time::SystemTime {
let mut latest = std::time::SystemTime::now();
if let Ok(entries) = std::fs::read_dir(path) {
for modified in entries.flatten().filter_map(|e| ruchy_file_mtime(&e)) {
if modified > latest {
latest = modified;
}
}
}
latest
}
fn check_test_failures(test_results: &[TestResult]) -> Result<()> {
let failed = test_results.iter().filter(|r| !r.success).count();
if failed > 0 {
Err(anyhow::anyhow!(
"Test failures detected: {} tests failed",
failed
))
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::time::Duration;
use tempfile::TempDir;
fn create_test_directory_with_files() -> Result<TempDir> {
let temp_dir = TempDir::new()?;
let test_file1 = temp_dir.path().join("passing_test.ruchy");
fs::write(&test_file1, "println(\"Hello Test\")")?;
let test_file2 = temp_dir.path().join("another_test.ruchy");
fs::write(&test_file2, "let x = 42; println(x)")?;
let readme_file = temp_dir.path().join("README.txt");
fs::write(&readme_file, "This is documentation")?;
Ok(temp_dir)
}
fn create_test_result(
file: &str,
success: bool,
duration_ms: u64,
error: Option<&str>,
) -> TestResult {
TestResult {
file: PathBuf::from(file),
success,
duration: Duration::from_millis(duration_ms),
error: error.map(std::string::ToString::to_string),
}
}
#[test]
fn test_handle_test_command_default_path() {
let _cwd = crate::handlers::test_support::cwd_lock();
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let original_dir = std::env::current_dir().expect("Failed to get current directory");
std::env::set_current_dir(temp_dir.path())
.expect("Failed to set current directory to temp path");
let result = handle_test_command(
None, false, false, None, false, "text", 1, 0.0, "text", );
std::env::set_current_dir(original_dir).expect("Failed to restore original directory");
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_handle_test_command_with_path() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, true, None, false, "text", 1, 0.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_handle_test_command_with_filter() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, false, Some("passing"), false, "text", 1, 0.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_handle_test_command_with_coverage() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, false, None, true, "html", 1, 80.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_handle_test_command_json_output() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, false, None, false, "text", 1, 0.0, "json", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_run_tests_empty_directory() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let result = run_tests(
temp_dir.path(),
false, None, false, "text", 1, 0.0, "text", );
assert!(result.is_err());
}
#[test]
fn test_run_tests_with_files() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = run_tests(
temp_dir.path(),
true, None, false, "text", 1, 0.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_run_tests_with_filter() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = run_tests(
temp_dir.path(),
false, Some("another"), false, "text", 1, 0.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_run_tests_json_format() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = run_tests(
temp_dir.path(),
false, None, false, "text", 1, 0.0, "json", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_run_tests_with_coverage() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = run_tests(
temp_dir.path(),
false, None, true, "html", 1, 75.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_get_latest_modification_empty_directory() {
let temp_dir = TempDir::new().expect("Failed to create temporary test directory");
let modification_time = get_latest_modification(temp_dir.path());
assert!(modification_time <= std::time::SystemTime::now());
}
#[test]
fn test_get_latest_modification_with_ruchy_files() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let modification_time = get_latest_modification(temp_dir.path());
assert!(modification_time <= std::time::SystemTime::now());
}
#[test]
fn test_get_latest_modification_with_mixed_files() {
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 other_file = temp_dir.path().join("other.txt");
fs::write(&other_file, "not ruchy")
.unwrap_or_else(|_| panic!("Failed to write test file: {}", other_file.display()));
let modification_time = get_latest_modification(temp_dir.path());
assert!(modification_time <= std::time::SystemTime::now());
}
#[test]
fn test_get_latest_modification_nonexistent_directory() {
let nonexistent_path = Path::new("/nonexistent/directory");
let modification_time = get_latest_modification(nonexistent_path);
assert!(modification_time <= std::time::SystemTime::now());
}
#[test]
fn test_check_test_failures_all_passing() {
let test_results = vec![
create_test_result("test1.ruchy", true, 100, None),
create_test_result("test2.ruchy", true, 150, None),
create_test_result("test3.ruchy", true, 200, None),
];
assert!(check_test_failures(&test_results).is_ok());
}
#[test]
fn test_check_test_failures_with_failures() {
let test_results = vec![
create_test_result("test1.ruchy", true, 100, None),
create_test_result("test2.ruchy", false, 50, Some("Parse error")),
create_test_result("test3.ruchy", true, 200, None),
];
let result = check_test_failures(&test_results);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("1 tests failed"));
}
#[test]
fn test_check_test_failures_all_failing() {
let test_results = vec![
create_test_result("test1.ruchy", false, 50, Some("Error 1")),
create_test_result("test2.ruchy", false, 60, Some("Error 2")),
create_test_result("test3.ruchy", false, 70, Some("Error 3")),
];
let result = check_test_failures(&test_results);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("3 tests failed"));
}
#[test]
fn test_check_test_failures_empty_results() {
let test_results: Vec<TestResult> = vec![];
assert!(check_test_failures(&test_results).is_ok());
}
#[test]
fn test_integration_complete_workflow() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, true, None, false, "text", 1, 0.0, "text", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_integration_with_all_options() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, true, Some("test"), true, "json", 2, 50.0, "json", );
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_error_handling_invalid_path() {
let invalid_path = PathBuf::from("/absolutely/nonexistent/path/that/should/not/exist");
let result = handle_test_command(
Some(invalid_path),
false, false, None, false, "text", 1, 0.0, "text", );
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_parameter_validation() {
let temp_dir =
create_test_directory_with_files().expect("Failed to create test directory with files");
let test_cases = vec![
(0, 0.0, "text"), (1, 50.0, "json"), (10, 100.0, "html"), ];
for (parallel, threshold, format) in test_cases {
let result = handle_test_command(
Some(temp_dir.path().to_path_buf()),
false, false, None, false, "text", parallel,
threshold,
format,
);
assert!(result.is_ok() || result.is_err());
}
}
}