use std::process::Command;
const PV: &str = env!("CARGO_BIN_EXE_pv");
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn run(flag: &str) -> (String, String, i32) {
let out = Command::new(PV)
.arg(flag)
.output()
.unwrap_or_else(|e| panic!("failed to spawn {PV} {flag}: {e}"));
(
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
out.status.code().unwrap_or(-1),
)
}
fn is_bare_name_and_semver(line: &str) -> bool {
let mut fields = line.split_whitespace();
let (Some(name), Some(ver), None) = (fields.next(), fields.next(), fields.next()) else {
return false;
};
name == "pv" && ver.split('.').count() == 3
}
#[test]
fn version_flag_exits_zero_on_stdout() {
let (stdout, _stderr, rc) = run("--version");
assert_eq!(rc, 0, "pv --version must exit 0, got {rc}");
assert!(
!stdout.trim().is_empty(),
"pv --version must print to stdout"
);
}
#[test]
fn long_version_is_not_the_bare_name_and_semver() {
let (stdout, _, _) = run("--version");
let first = stdout.lines().next().unwrap_or_default();
assert!(
!is_bare_name_and_semver(first),
"pv --version first line is `{first}` — that is indistinguishable from \
pv(1) the pipe viewer. It must name this tool."
);
}
#[test]
fn long_version_names_what_this_tool_is() {
let (stdout, _, _) = run("--version");
let lower = stdout.to_lowercase();
assert!(
lower.contains("provable-contracts"),
"pv --version must say this is the provable-contracts verifier; got:\n{stdout}"
);
assert!(
lower.contains("aprender"),
"pv --version must name the project it belongs to; got:\n{stdout}"
);
}
#[test]
fn long_version_names_the_shipping_crate() {
let (stdout, _, _) = run("--version");
assert!(
stdout.contains("aprender-contracts-cli"),
"pv --version must name the crate that ships it; got:\n{stdout}"
);
}
#[test]
fn long_version_disclaims_the_pipe_viewer() {
let (stdout, _, _) = run("--version");
let lower = stdout.to_lowercase();
assert!(
lower.contains("pipe viewer"),
"pv --version must explicitly disclaim pv(1), the pipe viewer, because \
a user with both installed cannot otherwise tell them apart; got:\n{stdout}"
);
}
#[test]
fn short_version_is_one_unambiguous_line() {
let (stdout, _, rc) = run("-V");
assert_eq!(rc, 0, "pv -V must exit 0");
let lines: Vec<&str> = stdout.trim_end().lines().collect();
assert_eq!(
lines.len(),
1,
"pv -V must stay a single line, got {}:\n{stdout}",
lines.len()
);
assert!(
!is_bare_name_and_semver(lines[0]),
"pv -V is `{}` — bare name plus semver, same as pv(1)",
lines[0]
);
assert!(
lines[0].to_lowercase().contains("provable-contracts"),
"pv -V must name the tool; got `{}`",
lines[0]
);
}
#[test]
fn semver_stays_the_second_field_of_the_first_line() {
let (stdout, _, _) = run("--version");
let first = stdout.lines().next().unwrap_or_default();
let mut fields = first.split_whitespace();
assert_eq!(
fields.next(),
Some("pv"),
"first field must be the tool name"
);
assert_eq!(
fields.next(),
Some(VERSION),
"second field of `pv --version` must be the bare declared semver \
(scripts/pv_bin.sh parses it positionally); got line `{first}`"
);
}
#[test]
fn short_and_long_version_agree_on_the_first_line() {
let (long, _, _) = run("--version");
let (short, _, _) = run("-V");
assert_eq!(
long.lines().next(),
short.lines().next(),
"pv -V and pv --version must share a first line"
);
}