use std::path::PathBuf;
use std::process::Command;
use bwipp::Symbology;
fn binary_path() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_bwipp"))
}
fn run_cli(args: &[&str]) -> (String, String, i32, Vec<u8>) {
let tmp = tempfile_path();
let mut full: Vec<&str> = args.to_vec();
if args.len() == 3 {
full.push(tmp.to_str().expect("utf8 tmp path"));
}
let out = Command::new(binary_path())
.args(&full)
.output()
.expect("failed to spawn bwipp binary");
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
let code = out.status.code().unwrap_or(-1);
let bytes = std::fs::read(&tmp).unwrap_or_default();
let _ = std::fs::remove_file(&tmp);
(stdout, stderr, code, bytes)
}
fn tempfile_path() -> PathBuf {
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
let mut path = std::env::temp_dir();
path.push(format!("bwipp-cli-test-{pid}-{n}.bin"));
path
}
#[test]
fn cli_no_args_prints_usage_with_exit_2() {
let out = Command::new(binary_path())
.output()
.expect("failed to spawn bwipp binary");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("usage: bwipp"),
"stderr should contain usage line; got {stderr:?}",
);
assert!(
stderr.contains("known symbologies:"),
"stderr should list known symbologies; got {stderr:?}",
);
assert_eq!(out.status.code(), Some(2));
}
#[test]
fn cli_unknown_symbology_exits_with_code_2() {
let (_, stderr, code, _) = run_cli(&["not-a-real-symbology", "data", "svg"]);
assert_eq!(code, 2, "unknown symbology should exit with code 2");
assert!(
stderr.contains("unknown symbology"),
"stderr should contain `unknown symbology`; got {stderr:?}",
);
}
#[test]
fn cli_unknown_format_exits_with_code_2() {
let (_, stderr, code, _) = run_cli(&["code128", "Hello", "tiff"]);
assert_eq!(code, 2, "unknown format should exit with code 2");
assert!(
stderr.contains("unknown format"),
"stderr should contain `unknown format`; got {stderr:?}",
);
}
#[test]
fn cli_invalid_payload_exits_with_code_1() {
let (_, stderr, code, _) = run_cli(&["ean13", "abc", "svg"]);
assert_eq!(code, 1, "invalid payload should exit with code 1");
assert!(
stderr.contains("render error:"),
"stderr should start with `render error:`; got {stderr:?}",
);
}
#[test]
fn cli_writes_non_empty_svg_for_canonical_payload() {
let (stdout, stderr, code, bytes) = run_cli(&["code128", "Hello", "svg"]);
assert_eq!(code, 0, "expected exit 0; stderr={stderr:?}");
assert!(stdout.contains("wrote"), "stdout was {stdout:?}");
assert!(!bytes.is_empty(), "output SVG was empty");
let svg = String::from_utf8_lossy(&bytes);
assert!(
svg.starts_with("<svg"),
"output doesn't look like SVG: {}",
&svg[..svg.len().min(64)],
);
}
#[test]
fn cli_writes_non_empty_png_for_canonical_payload() {
let (_, stderr, code, bytes) = run_cli(&["code128", "Hello", "png"]);
assert_eq!(code, 0, "expected exit 0; stderr={stderr:?}");
assert!(bytes.len() >= 8, "PNG too small: {} bytes", bytes.len());
let magic = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];
assert_eq!(&bytes[..8], &magic, "output is not a PNG");
}
#[test]
fn cli_svg_output_is_deterministic() {
let (_, _, c1, b1) = run_cli(&["code128", "Hello, world!", "svg"]);
let (_, _, c2, b2) = run_cli(&["code128", "Hello, world!", "svg"]);
assert_eq!(c1, 0);
assert_eq!(c2, 0);
assert_eq!(b1, b2, "SVG output drifts between runs");
}
#[test]
fn cli_png_output_is_deterministic() {
let (_, _, c1, b1) = run_cli(&["code128", "Hello, world!", "png"]);
let (_, _, c2, b2) = run_cli(&["code128", "Hello, world!", "png"]);
assert_eq!(c1, 0);
assert_eq!(c2, 0);
assert_eq!(b1, b2, "PNG output drifts between runs");
}
#[test]
fn cli_every_symbology_returns_a_clean_exit_code() {
let payloads = &[
"Hello",
"12345",
"0123456789012", "012345678905", "(01)12345678901231", "(01)24012345678905", "(01)15012345678907", "A123BJC5D6E71", "SN34RD1A", "https://example.com/01/09521234543213", "Hello, world!",
];
let mut covered = 0;
for &sym in Symbology::all() {
let id = sym.id();
let mut any_ok = false;
for payload in payloads {
let (_, _, code, _) = run_cli(&[id, payload, "svg"]);
assert!(
code == 0 || code == 1,
"{id} returned exit code {code} (expected 0 or 1) for payload {payload:?}",
);
if code == 0 {
any_ok = true;
break;
}
}
if any_ok {
covered += 1;
}
}
assert!(
covered >= 25,
"expected at least 25 symbologies to accept one of our generic \
payloads via the CLI; got {covered}",
);
}