use anyhow::{Context, Result};
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
use std::path::PathBuf;
use std::process::{Command, Stdio};
fn find_project_root() -> Result<PathBuf> {
let mut current = std::env::current_dir()?;
loop {
let cargo_toml = current.join("Cargo.toml");
let bridgerust_toml = current.join("bridgerust.toml");
if cargo_toml.exists() || bridgerust_toml.exists() {
return Ok(current);
}
match current.parent() {
Some(parent) => current = parent.to_path_buf(),
None => anyhow::bail!("Could not find project root (Cargo.toml or bridgerust.toml)"),
}
}
}
pub async fn handle(target: String) -> Result<()> {
println!("{}", style("๐งช Running tests...").bold().cyan());
let project_root = find_project_root()?;
println!(" Project root: {}", project_root.display());
let targets: Vec<&str> = match target.as_str() {
"all" => vec!["rust", "python", "nodejs"],
"python" => vec!["python"],
"nodejs" => vec!["nodejs"],
"rust" => vec!["rust"],
_ => anyhow::bail!(
"Invalid target: {}. Use 'python', 'nodejs', 'rust', or 'all'",
target
),
};
let mut failed = Vec::new();
for target in &targets {
match test_target(target, &project_root).await {
Ok(_) => {}
Err(e) => {
failed.push((*target, e));
}
}
}
if !failed.is_empty() {
eprintln!("\n{}", style("โ Some tests failed:").bold().red());
for (target, error) in &failed {
eprintln!(" {} {}: {}", style("โ").red(), target, error);
}
anyhow::bail!("Tests failed");
}
println!("\n{}", style("โ
All tests passed!").bold().green());
Ok(())
}
async fn test_target(target: &str, project_root: &PathBuf) -> Result<()> {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.unwrap(),
);
pb.set_message(format!("Running {} tests...", target));
pb.enable_steady_tick(std::time::Duration::from_millis(100));
match target {
"rust" => {
pb.finish_and_clear();
let mut cmd = Command::new("cargo");
cmd.current_dir(project_root);
cmd.arg("test");
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let status = cmd.status().context("Failed to run cargo test")?;
if !status.success() {
anyhow::bail!("Rust tests failed");
}
println!(" {} Rust tests passed", style("โ").green());
}
"python" => {
let pytest_check = Command::new("python")
.arg("-m")
.arg("pytest")
.arg("--version")
.output();
if pytest_check.is_err() {
let pytest3_check = Command::new("python3")
.arg("-m")
.arg("pytest")
.arg("--version")
.output();
if pytest3_check.is_err() {
anyhow::bail!("pytest is not installed. Install it with: pip install pytest");
}
}
let test_dirs = vec![
project_root.join("tests"),
project_root.join("python").join("tests"),
project_root.join("python"),
];
let mut test_files = Vec::new();
for test_dir in &test_dirs {
if test_dir.exists()
&& let Ok(entries) = std::fs::read_dir(test_dir)
{
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map(|e| e == "py").unwrap_or(false)
&& path
.file_name()
.map(|n| n.to_string_lossy().starts_with("test_"))
.unwrap_or(false)
{
test_files.push(path);
}
}
}
}
pb.finish_and_clear();
if test_files.is_empty() {
println!(" {} No Python test files found", style("โ ").yellow());
return Ok(());
}
let mut cmd = Command::new("python");
cmd.arg("-m").arg("pytest");
for test_file in &test_files {
cmd.arg(test_file);
}
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let status = cmd.status().context("Failed to run pytest")?;
if !status.success() {
anyhow::bail!("Python tests failed");
}
println!(" {} Python tests passed", style("โ").green());
}
"nodejs" => {
let nodejs_dir = project_root.join("nodejs");
let package_json = nodejs_dir.join("package.json");
let has_test_script = if package_json.exists() {
if let Ok(content) = std::fs::read_to_string(&package_json) {
content.contains("\"test\"") || content.contains("\"tests\"")
} else {
false
}
} else {
false
};
pb.finish_and_clear();
if has_test_script {
let mut cmd = Command::new("npm");
cmd.current_dir(&nodejs_dir);
cmd.arg("test");
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let status = cmd.status().context("Failed to run npm test")?;
if !status.success() {
anyhow::bail!("Node.js tests failed");
}
println!(" {} Node.js tests passed", style("โ").green());
} else {
let test_files = vec![
nodejs_dir.join("test_bindings.js"),
nodejs_dir.join("tests").join("test_bindings.js"),
nodejs_dir.join("test").join("test_bindings.js"),
];
let mut found_test = None;
for test_file in &test_files {
if test_file.exists() {
found_test = Some(test_file.clone());
break;
}
}
if let Some(test_file) = found_test {
let mut cmd = Command::new("node");
cmd.arg(&test_file);
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let status = cmd.status().context("Failed to run Node.js tests")?;
if !status.success() {
anyhow::bail!("Node.js tests failed");
}
println!(" {} Node.js tests passed", style("โ").green());
} else {
println!(" {} No Node.js test files found", style("โ ").yellow());
}
}
}
_ => unreachable!(),
}
Ok(())
}