use crate::{
parsing::{parse_cargo_test, Stats},
prettify::{make_pretty, TestTree, ICON_NOTATION},
regex::re,
Result,
};
use colored::{control::set_override, Colorize};
use std::process::{Command, ExitCode, Output};
use termtree::Tree;
pub struct Emit {
output: Option<Output>,
no_parse: bool,
}
impl Emit {
pub fn run(self) -> ExitCode {
let Emit { output, no_parse } = self;
let Some(output) = output else {
return ExitCode::SUCCESS;
};
let raw_err = String::from_utf8_lossy(&output.stderr);
let raw_out = String::from_utf8_lossy(&output.stdout);
let stderr = strip_ansi_escapes::strip(&*raw_err);
let stdout = strip_ansi_escapes::strip(&*raw_out);
let stderr = String::from_utf8_lossy(&stderr);
let stdout = String::from_utf8_lossy(&stdout);
if no_parse {
println!(
"{phelp}\n{ICON_NOTATION}\n{sep}\n\n{help}",
phelp = "cargo pretty-test help:".blue().bold(),
sep = re().separator,
help = "cargo test help:".blue().bold()
);
eprintln!("{stderr}");
println!("{stdout}");
} else {
let (tree, stats) = match parse_cargo_test_output(&stderr, &stdout) {
Ok(res) => res,
Err(err) => {
println!(
"{}:\n{err}\n\n{}\n{raw_err}\n{raw_out}",
"Error from cargo-pretty-test".red().bold(),
"Error from cargo test:".red().bold()
);
return ExitCode::FAILURE;
}
};
println!("{tree}\n{stats}");
if !stats.ok {
return ExitCode::FAILURE;
}
}
ExitCode::SUCCESS
}
}
pub fn run() -> ExitCode {
cargo_test().run()
}
pub fn cargo_test() -> Emit {
let passin: Vec<_> = std::env::args().collect();
let forward = if passin
.get(..2)
.is_some_and(|v| v[0].ends_with("cargo-pretty-test") && v[1] == "pretty-test")
{
&passin[2..]
} else {
&passin[1..]
};
if forward.iter().any(|arg| arg == "--version" || arg == "-V") {
const VERSION: &str = env!("CARGO_PKG_VERSION");
println!("cargo-pretty-test version: {VERSION}");
return Emit {
output: None,
no_parse: true,
};
}
set_color(forward);
let no_parse = forward.iter().any(|arg| arg == "--help" || arg == "-h");
let args = forward.iter().filter(|&arg| arg != "--nocapture");
Emit {
output: Some(
Command::new("cargo")
.arg("test")
.args(args)
.output()
.expect("`cargo test` failed"),
),
no_parse,
}
}
fn set_color(forward: &[String]) {
fn detect_env() {
if let Some(set_color) = std::env::var_os("CARGO_TERM_COLOR") {
match set_color.to_str().map(str::to_ascii_lowercase).as_deref() {
Some("always") => set_override(true),
Some("never") => set_override(false),
Some("auto") => (),
_ => unreachable!("--color only accepts one of always,never,auto"),
}
}
}
if let Some(pos) = forward.iter().position(|arg| arg.starts_with("--color")) {
match (&*forward[pos], forward.get(pos + 1).map(|s| &**s)) {
("--color=always", _) | ("--color", Some("always")) => set_override(true),
("--color=never", _) | ("--color", Some("never")) => set_override(false),
("--color=auto", _) | ("--color", Some("auto")) => (),
_ => unreachable!("--color only accepts one of always,never,auto"),
}
} else {
detect_env();
}
}
pub fn parse_cargo_test_output<'s>(
stderr: &'s str,
stdout: &'s str,
) -> Result<(TestTree<'s>, Stats)> {
let mut tree = Tree::new("Generated by cargo-pretty-test".bold().to_string().into());
let mut stats = Stats::default();
for (pkg, data) in parse_cargo_test(stderr, stdout)?.pkgs {
stats += &data.stats;
let root = data.stats.root_string(pkg.unwrap_or("tests")).into();
tree.push(
Tree::new(root).with_leaves(data.inner.into_iter().filter_map(|data| {
let parsed = data.info.parsed;
let detail_without_stats = parsed.detail;
if !detail_without_stats.is_empty() {
eprintln!("{detail_without_stats}\n\n{}\n", re().separator);
}
let root = data.info.stats.subroot_string(data.runner.src.src_path);
make_pretty(root, parsed.tree.into_iter())
})),
);
}
Ok((tree, stats))
}