use anyhow::{bail, Context, Result};
use clap::Args as ClapArgs;
use console::style;
use std::process::Command;
use std::time::Instant;
use stout_state::{InstalledPackages, Paths};
#[derive(ClapArgs)]
pub struct Args {
pub formulas: Vec<String>,
#[arg(long, short)]
pub verbose: bool,
#[arg(long)]
pub all: bool,
}
pub async fn run(args: Args) -> Result<()> {
let start = Instant::now();
let paths = Paths::default();
let installed = InstalledPackages::load(&paths)?;
let packages: Vec<String> = if args.all {
installed.names().cloned().collect()
} else if args.formulas.is_empty() {
bail!("No formulas specified. Use --all to test all installed packages.");
} else {
args.formulas.clone()
};
if packages.is_empty() {
println!("{}", style("No packages to test.").dim());
return Ok(());
}
println!(
"\n{} {} packages...\n",
style("Testing").cyan().bold(),
packages.len()
);
let mut success_count = 0;
let mut failure_count = 0;
let mut skipped_count = 0;
for name in &packages {
if !installed.is_installed(name) {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(not installed)").dim()
);
skipped_count += 1;
continue;
}
let pkg_info = installed
.get(name)
.with_context(|| format!("package '{}' is installed but not found in state", name))?;
let install_path = paths.cellar.join(name).join(&pkg_info.version);
if !install_path.exists() {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(installation not found)").dim()
);
skipped_count += 1;
continue;
}
let bin_dir = install_path.join("bin");
if !bin_dir.exists() {
if args.verbose {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(no binaries)").dim()
);
}
skipped_count += 1;
continue;
}
let mut tested = false;
let mut all_passed = true;
if let Ok(entries) = std::fs::read_dir(&bin_dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_file() {
continue;
}
let binary_name = path.file_name().unwrap().to_string_lossy().to_string();
if binary_name.ends_with(".sh")
|| binary_name.ends_with(".py")
|| binary_name.ends_with(".rb")
{
continue;
}
let result =
test_binary(&paths.prefix.join("bin").join(&binary_name), args.verbose);
if let Err(e) = result {
tested = true;
all_passed = false;
if args.verbose {
println!(" {} {} - {}", style("✗").red(), binary_name, e);
}
} else {
tested = true;
if args.verbose {
println!(" {} {}", style("✓").green(), binary_name);
}
}
}
}
if !tested {
skipped_count += 1;
if args.verbose {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(no testable binaries)").dim()
);
}
} else if all_passed {
success_count += 1;
println!(
" {} {} {}",
style("✓").green(),
name,
style(&pkg_info.version).dim()
);
} else {
failure_count += 1;
println!(
" {} {} {}",
style("✗").red(),
name,
style(&pkg_info.version).dim()
);
}
}
let elapsed = start.elapsed();
println!();
if failure_count == 0 {
println!(
"{} {} passed, {} skipped in {:.1}s",
style("✓").green().bold(),
success_count,
skipped_count,
elapsed.as_secs_f64()
);
} else {
println!(
"{} {} passed, {} failed, {} skipped in {:.1}s",
style("!").yellow().bold(),
success_count,
failure_count,
skipped_count,
elapsed.as_secs_f64()
);
}
if failure_count > 0 {
std::process::exit(1);
}
Ok(())
}
fn test_binary(path: &std::path::Path, verbose: bool) -> Result<(), String> {
if !path.exists() {
return Err("binary not found".to_string());
}
let version_result = Command::new(path).arg("--version").output();
if let Ok(output) = version_result {
if output.status.success() {
if verbose {
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.is_empty() {
if let Some(_line) = stdout.lines().next() {
return Ok(());
}
}
}
return Ok(());
}
}
let help_result = Command::new(path).arg("--help").output();
if let Ok(output) = help_result {
if output.status.success()
|| output.status.code() == Some(0)
|| output.status.code() == Some(1)
{
return Ok(());
}
}
let bare_result = Command::new(path).output();
if let Ok(output) = bare_result {
if output.status.success() || output.status.code().is_some() {
return Ok(());
}
}
Err("failed to execute".to_string())
}