Files
polyscribe/tests/continue_on_error.rs

212 lines
5.9 KiB
Rust

use std::ffi::OsStr;
use std::process::{Command, Stdio};
use std::thread;
use std::time::{Duration, Instant};
fn bin() -> &'static str {
env!("CARGO_BIN_EXE_polyscribe")
}
fn manifest_path(rel: &str) -> std::path::PathBuf {
let mut p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push(rel);
p
}
fn run_polyscribe<I, S>(args: I, timeout: Duration) -> std::io::Result<std::process::Output>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut child = Command::new(bin())
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env_clear()
.env("CI", "1")
.env("NO_COLOR", "1")
.spawn()?;
let start = Instant::now();
loop {
if let Some(status) = child.try_wait()? {
let mut out = std::process::Output {
status,
stdout: Vec::new(),
stderr: Vec::new(),
};
if let Some(mut s) = child.stdout.take() {
use std::io::Read;
let _ = std::io::copy(&mut s, &mut out.stdout);
}
if let Some(mut s) = child.stderr.take() {
use std::io::Read;
let _ = std::io::copy(&mut s, &mut out.stderr);
}
return Ok(out);
}
if start.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait();
return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"polyscribe timed out",
));
}
thread::sleep(Duration::from_millis(10))
}
}
fn strip_ansi(s: &str) -> std::borrow::Cow<'_, str> {
// Minimal stripper for ESC [ ... letter sequence
if !s.as_bytes().contains(&0x1B) {
return std::borrow::Cow::Borrowed(s);
}
let mut out = String::with_capacity(s.len());
let mut bytes = s.as_bytes().iter().copied().peekable();
while let Some(b) = bytes.next() {
if b == 0x1B {
// Try to consume CSI sequence: ESC '[' ... cmd
if matches!(bytes.peek(), Some(b'[')) {
let _ = bytes.next(); // skip '['
// Skip params/intermediates until a final byte in 0x40..=0x77E
while let Some(&c) = bytes.peek() {
if (0x40..=0x7E).contains(&c) {
let _ = bytes.next();
break;
}
let _ = bytes.next();
}
continue;
}
// Skip single-char ESC sequences
let _ = bytes.next();
continue;
}
out.push(b as char);
}
std::borrow::Cow::Owned(out)
}
fn count_err_in_summary(stderr: &str) -> usize {
stderr
.lines()
.map(|l| strip_ansi(l))
// Drop trailing CR (Windows) and whitespace
.map(|l| l.trim_end_matches('\r').trim_end().to_string())
.filter(|l| match l.split_whitespace().last() {
Some(tok) if tok == "ERR" => true,
Some(tok)
if tok.strip_suffix(":").is_some() && tok.strip_suffix(":") == Some("ERR") =>
{
true
}
Some(tok)
if tok.strip_suffix(",").is_some() && tok.strip_suffix(",") == Some("ERR") =>
{
true
}
_ => false,
})
.count()
}
#[test]
fn continue_on_error_all_ok() {
let input1 = manifest_path("input/1-s0wlz.json");
let input2 = manifest_path("input/2-vikingowl.json");
// Avoid temporaries: use &'static OsStr for flags.
let out = run_polyscribe(
&[
input1.as_os_str(),
input2.as_os_str(),
OsStr::new("--continue-on-error"),
OsStr::new("-m"),
],
Duration::from_secs(30),
)
.expect("failed to run polyscribe");
assert!(
out.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
// Should not contain any ERR rows in summary
assert_eq!(
count_err_in_summary(&stderr),
0,
"unexpected ERR rows: {}",
stderr
);
}
#[test]
fn continue_on_error_some_fail() {
let input1 = manifest_path("input/1-s0wlz.json");
let missing = manifest_path("input/does_not_exist.json");
let out = run_polyscribe(
&[
input1.as_os_str(),
missing.as_os_str(),
OsStr::new("--continue-on-error"),
OsStr::new("-m"),
],
Duration::from_secs(30),
)
.expect("failed to run polyscribe");
assert!(
!out.status.success(),
"expected failure exit, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
// Expect at least one ERR row due to the missing file
assert!(
count_err_in_summary(&stderr) >= 1,
"expected ERR rows in summary, stderr: {}",
stderr
);
}
#[test]
fn continue_on_error_all_fail() {
let missing1 = manifest_path("input/does_not_exist_a.json");
let missing2 = manifest_path("input/does_not_exist_b.json");
let out = run_polyscribe(
&[
missing1.as_os_str(),
missing2.as_os_str(),
OsStr::new("--continue-on-error"),
OsStr::new("-m"),
],
Duration::from_secs(30),
)
.expect("failed to run polyscribe");
assert!(
!out.status.success(),
"expected failure exit, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
// Expect two ERR rows due to both files missing
assert!(
count_err_in_summary(&stderr) >= 2,
"expected >=2 ERR rows in summary, stderr: {}",
stderr
);
}