use anyhow::Context;
use std::process::Command;
#[must_use]
pub fn get_cargo_test_output(extra_args: Vec<String>) -> String {
let mut cargo = Command::new("cargo");
cargo.arg("test");
if !extra_args.is_empty() {
cargo.arg("--");
cargo.args(extra_args);
}
let raw_output = cargo
.output()
.context(format!("{cargo:?}"))
.expect("executing command should succeed")
.stdout;
String::from_utf8_lossy(&raw_output).to_string()
}
#[must_use]
pub fn parse_test_results(test_output: &str) -> Vec<TestResult> {
test_output.lines().filter_map(parse_line).collect()
}
pub fn parse_line<S: AsRef<str>>(line: S) -> Option<TestResult> {
let line = line.as_ref().strip_prefix("test ")?;
if line.starts_with("result") {
return None;
}
let line = line.strip_prefix("tests::").unwrap_or(line);
let splits: Vec<_> = line.split(" ... ").collect();
let (name, result) = (splits[0], splits[1]);
Some(TestResult {
name: prettify(name),
status: match result {
"ok" => Status::Pass,
"FAILED" => Status::Fail,
"ignored" => Status::Ignored,
_ => todo!("unhandled test status {:?}", result),
},
})
}
#[must_use]
pub fn prettify<S: AsRef<str>>(input: S) -> String {
input.as_ref().replace('_', " ")
}
#[derive(Debug, PartialEq)]
pub struct TestResult {
pub name: String,
pub status: Status,
}
impl std::fmt::Display for TestResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let status = match self.status {
Status::Pass => '✔',
Status::Fail => 'x',
Status::Ignored => '?',
};
write!(f, " {status} {}", self.name)
}
}
#[derive(Debug, PartialEq)]
pub enum Status {
Pass,
Fail,
Ignored,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prettify_returns_expected_results() {
struct Case {
input: &'static str,
want: String,
}
let cases = Vec::from([
Case {
input: "anagrams_must_use_all_letters_exactly_once",
want: "anagrams must use all letters exactly once".into(),
},
Case {
input: "no_matches",
want: "no matches".into(),
},
Case {
input: "single",
want: "single".into(),
},
]);
for case in cases {
assert_eq!(case.want, prettify(case.input));
}
}
#[test]
fn parse_line_returns_expected_result() {
struct Case {
line: &'static str,
want: Option<TestResult>,
}
let cases = Vec::from([Case {
line: "test foo ... ok",
want: Some(TestResult {
name: "foo".into(),
status: Status::Pass,
}),
}]);
for case in cases {
assert_eq!(case.want, parse_line(case.line));
}
}
}