use anyhow::{Context, Result};
use std::collections::HashMap;
use std::process::{Command, Stdio};
use crate::cli::TestFilterArgs;
use crate::discovery::{TestFunction, TestTarget, TestType};
pub struct TestRunner<'a> {
args: &'a TestFilterArgs,
}
impl<'a> TestRunner<'a> {
pub fn new(args: &'a TestFilterArgs) -> Self {
Self { args }
}
pub fn list_test_functions(&self, functions: &[TestFunction]) {
if functions.is_empty() {
println!("No tests match the filter criteria.");
return;
}
println!("Found {} test function(s):\n", functions.len());
let mut integration_tests: HashMap<String, Vec<&TestFunction>> = HashMap::new();
let mut unit_tests: Vec<&TestFunction> = Vec::new();
for func in functions {
match func.test_type {
TestType::Integration => {
integration_tests
.entry(func.target_name.clone())
.or_default()
.push(func);
}
TestType::Unit => {
unit_tests.push(func);
}
TestType::Doc => {}
}
}
if !integration_tests.is_empty() {
println!("Integration Tests:");
for (target, funcs) in &integration_tests {
println!(" tests/{}.rs:", target);
for func in funcs {
if func.tags.is_empty() {
println!(" - {}", func.name);
} else {
println!(" - {} [{}]", func.name, func.tags.join(", "));
}
}
}
println!();
}
if !unit_tests.is_empty() {
println!("Unit Tests:");
for func in &unit_tests {
if func.tags.is_empty() {
println!(" - {}", func.name);
} else {
println!(" - {} [{}]", func.name, func.tags.join(", "));
}
}
println!();
}
}
pub fn run_test_functions(&self, functions: &[TestFunction]) -> Result<()> {
if functions.is_empty() {
println!("No tests match the filter criteria.");
return Ok(());
}
println!("Running {} test function(s)...", functions.len());
if self.args.verbose {
for func in functions {
println!(" - {}::{} (tags: {:?})", func.target_name, func.name, func.tags);
}
println!();
}
let mut integration_tests: HashMap<String, Vec<&TestFunction>> = HashMap::new();
let mut unit_tests: Vec<&TestFunction> = Vec::new();
for func in functions {
match func.test_type {
TestType::Integration => {
integration_tests
.entry(func.target_name.clone())
.or_default()
.push(func);
}
TestType::Unit => {
unit_tests.push(func);
}
TestType::Doc => {
}
}
}
for (target_name, funcs) in &integration_tests {
self.run_integration_test_functions(target_name, funcs)?;
}
if !unit_tests.is_empty() {
self.run_unit_test_functions(&unit_tests)?;
}
Ok(())
}
fn run_integration_test_functions(&self, target_name: &str, functions: &[&TestFunction]) -> Result<()> {
for func in functions {
let mut cmd = Command::new("cargo");
cmd.arg("test");
cmd.arg("--test");
cmd.arg(target_name);
cmd.arg("--");
cmd.arg("--exact");
cmd.arg(&func.name);
if self.args.verbose {
cmd.arg("--nocapture");
}
for arg in &self.args.test_args {
cmd.arg(arg);
}
if self.args.verbose {
println!("Executing: {:?}\n", cmd);
}
let status = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to execute cargo test")?;
if !status.success() {
anyhow::bail!("Tests failed with exit code: {:?}", status.code());
}
}
Ok(())
}
fn run_unit_test_functions(&self, functions: &[&TestFunction]) -> Result<()> {
for func in functions {
let mut cmd = Command::new("cargo");
cmd.arg("test");
cmd.arg("--lib");
cmd.arg(format!("::{}", func.name));
cmd.arg("--");
if self.args.verbose {
cmd.arg("--nocapture");
}
for arg in &self.args.test_args {
cmd.arg(arg);
}
if self.args.verbose {
println!("Executing: {:?}\n", cmd);
}
let status = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to execute cargo test")?;
if !status.success() {
anyhow::bail!("Tests failed with exit code: {:?}", status.code());
}
}
Ok(())
}
pub fn run_tests(&self, targets: &[TestTarget]) -> Result<()> {
if targets.is_empty() {
println!("No tests match the filter criteria.");
return Ok(());
}
println!("Running {} test target(s)...", targets.len());
if self.args.verbose {
for target in targets {
println!(" - {} ({:?})", target.name, target.test_type);
}
}
if self.args.integration {
self.run_integration_tests(targets)
} else if self.args.unit {
self.run_unit_tests(targets)
} else {
self.run_general_tests(targets)
}
}
fn run_integration_tests(&self, targets: &[TestTarget]) -> Result<()> {
for target in targets {
let mut cmd = Command::new("cargo");
cmd.arg("test");
cmd.arg("--test");
cmd.arg(&target.name);
if let Some(ref name) = self.args.name {
cmd.arg(name);
}
self.add_test_args(&mut cmd);
if self.args.verbose {
println!("\nExecuting: {:?}\n", cmd);
}
let status = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to execute cargo test")?;
if !status.success() {
anyhow::bail!("Tests failed with exit code: {:?}", status.code());
}
}
Ok(())
}
fn run_unit_tests(&self, _targets: &[TestTarget]) -> Result<()> {
let mut cmd = Command::new("cargo");
cmd.arg("test");
cmd.arg("--lib");
if let Some(ref name) = self.args.name {
cmd.arg(name);
}
self.add_test_args(&mut cmd);
if self.args.verbose {
println!("\nExecuting: {:?}\n", cmd);
}
let status = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to execute cargo test")?;
if !status.success() {
anyhow::bail!("Tests failed with exit code: {:?}", status.code());
}
Ok(())
}
fn run_general_tests(&self, _targets: &[TestTarget]) -> Result<()> {
let mut cmd = Command::new("cargo");
cmd.arg("test");
if let Some(ref name) = self.args.name {
cmd.arg(name);
}
self.add_test_args(&mut cmd);
if self.args.verbose {
println!("\nExecuting: {:?}\n", cmd);
}
let status = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to execute cargo test")?;
if !status.success() {
anyhow::bail!("Tests failed with exit code: {:?}", status.code());
}
Ok(())
}
fn add_test_args(&self, cmd: &mut Command) {
let mut needs_separator = true;
if self.args.verbose {
if needs_separator {
cmd.arg("--");
needs_separator = false;
}
cmd.arg("--nocapture");
}
if !self.args.test_args.is_empty() {
if needs_separator {
cmd.arg("--");
}
for arg in &self.args.test_args {
cmd.arg(arg);
}
}
}
pub fn run_all_tests(&self) -> Result<()> {
println!("Running all tests...");
let mut cmd = Command::new("cargo");
cmd.arg("test");
self.add_test_args(&mut cmd);
let status = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to execute cargo test")?;
if !status.success() {
anyhow::bail!("Tests failed with exit code: {:?}", status.code());
}
Ok(())
}
}