use std::path::PathBuf;
use std::process::{Command, Output};
const BIN: &str = env!("CARGO_BIN_EXE_aprender-explain");
const UNKNOWN_SUB: &[&str] = &["no-such-subcommand"];
const SUBCOMMANDS: &[&str] = &["ptx", "tui", "simd", "wgpu", "compare", "diff", "bugs"];
fn run(args: &[&str]) -> Output {
Command::new(BIN)
.args(args)
.output()
.expect("spawn the binary under test")
}
fn help_section(header: &str) -> Vec<String> {
let out = run(&["-h"]);
assert!(out.status.success(), "-h must exit 0");
String::from_utf8_lossy(&out.stdout)
.lines()
.skip_while(|l| !l.starts_with(header))
.skip(1)
.take_while(|l| !l.is_empty() && !l.ends_with(':'))
.map(str::to_owned)
.collect()
}
fn advertised() -> Vec<String> {
help_section("Commands:")
.iter()
.filter_map(|l| l.split_whitespace().next().map(str::to_owned))
.filter(|c| c != "help")
.collect()
}
fn unusable_inputs() -> Vec<String> {
let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("aprender-explain-surface-gate");
std::fs::create_dir_all(&dir).expect("create fixture dir");
let empty = dir.join("empty.bin");
let garbage = dir.join("garbage.bin");
std::fs::write(&empty, b"").expect("write empty fixture");
std::fs::write(&garbage, b"not a model {{{\x00\x01\xff").expect("write garbage fixture");
vec![
dir.join("does-not-exist").display().to_string(),
empty.display().to_string(),
garbage.display().to_string(),
]
}
fn assert_rejected(args: &[&str]) {
let out = run(args);
let code = out.status.code();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
matches!(code, Some(c) if c != 0 && c != 101) && !stderr.contains("panicked"),
"`aprender-explain {}` must reject its input with a clean non-zero exit, got {code:?}\nstderr: {stderr}",
args.join(" ")
);
}
fn assert_usage_error(args: &[&str]) {
let out = run(args);
assert_eq!(
out.status.code(),
Some(2),
"`aprender-explain {}` must be a usage error (exit 2)\nstderr: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn version_reports_the_crate_version() {
let out = run(&["--version"]);
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
let want = format!("aprender-explain {}", env!("CARGO_PKG_VERSION"));
assert!(
stdout.starts_with(&want),
"--version printed {stdout:?}, not {want:?}"
);
}
#[test]
fn advertised_subcommands_are_the_pinned_surface() {
assert_eq!(advertised(), SUBCOMMANDS);
}
#[test]
fn every_advertised_subcommand_is_reachable() {
let cmds = advertised();
assert!(
!cmds.is_empty(),
"--help advertised no subcommands; the parser is broken"
);
for cmd in &cmds {
let out = run(&[cmd, "--help"]);
assert!(
out.status.success(),
"`aprender-explain {cmd} --help` exited {:?}",
out.status.code()
);
}
}
#[test]
fn unknown_subcommand_is_a_usage_error() {
assert_usage_error(UNKNOWN_SUB);
}
#[test]
fn unknown_flag_is_a_usage_error() {
assert_usage_error(&["--no-such-flag"]);
}
#[test]
fn malformed_arguments_are_usage_errors() {
let bad = unusable_inputs();
let b = &bad[0];
assert_usage_error(&["ptx", "-K", "nosuch"]);
assert_usage_error(&["ptx", b.as_str()]);
assert_usage_error(&["diff", "-K", "nosuch", "--baseline", b.as_str()]);
}
#[test]
fn unusable_input_is_rejected() {
for b in &unusable_inputs() {
assert_rejected(&["diff", "-K", "vector_add", "--baseline", b.as_str()]);
}
}