use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
#[cfg(feature = "cranelift")]
const ENGINES_ALL: &[&str] = &["--run-tree", "--run-vm", "--run-cranelift"];
#[cfg(not(feature = "cranelift"))]
const ENGINES_ALL: &[&str] = &["--run-tree", "--run-vm"];
fn run_ok(engine: &str, src: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} {src:?} {args:?} failed: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8_lossy(&out.stdout);
s.trim_end_matches('\n').to_string()
}
fn run_err(engine: &str, src: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
!out.status.success(),
"ilo {engine} {src:?} {args:?} unexpectedly succeeded: stdout={}",
String::from_utf8_lossy(&out.stdout)
);
String::from_utf8_lossy(&out.stderr).to_string()
}
const PADL_SRC: &str = "f s:t w:n>t;padl s w";
const PADR_SRC: &str = "f s:t w:n>t;padr s w";
#[test]
fn padl_basic_cross_engine() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADL_SRC, &["f", "hi", "5"]);
assert_eq!(out, " hi", "{engine}: padl basic");
}
}
#[test]
fn padr_basic_cross_engine() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADR_SRC, &["f", "hi", "5"]);
assert_eq!(out, "hi ", "{engine}: padr basic");
}
}
#[test]
fn padl_exact_width_no_op() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADL_SRC, &["f", "exact", "5"]);
assert_eq!(out, "exact", "{engine}: padl exact width");
}
}
#[test]
fn padr_exact_width_no_op() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADR_SRC, &["f", "exact", "5"]);
assert_eq!(out, "exact", "{engine}: padr exact width");
}
}
#[test]
fn padl_already_wider_no_op() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADL_SRC, &["f", "already-wider", "4"]);
assert_eq!(out, "already-wider", "{engine}: padl already wider");
}
}
#[test]
fn padr_already_wider_no_op() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADR_SRC, &["f", "already-wider", "4"]);
assert_eq!(out, "already-wider", "{engine}: padr already wider");
}
}
#[test]
fn pad_width_zero_returns_input() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL_SRC, &["f", "abc", "0"]);
let r = run_ok(engine, PADR_SRC, &["f", "abc", "0"]);
assert_eq!(l, "abc", "{engine}: padl w=0");
assert_eq!(r, "abc", "{engine}: padr w=0");
}
}
#[test]
fn pad_empty_string_pads_to_width() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL_SRC, &["f", "", "3"]);
let r = run_ok(engine, PADR_SRC, &["f", "", "3"]);
assert_eq!(l, " ", "{engine}: padl empty");
assert_eq!(r, " ", "{engine}: padr empty");
}
}
#[test]
fn pad_negative_width_errors() {
for engine in &["--run-tree", "--run-vm"] {
let _ = run_err(engine, PADL_SRC, &["f", "hi", "-1"]);
let _ = run_err(engine, PADR_SRC, &["f", "hi", "-1"]);
}
}
const PADL3_SRC: &str = "f s:t w:n p:t>t;padl s w p";
const PADR3_SRC: &str = "f s:t w:n p:t>t;padr s w p";
#[test]
fn padl_with_zero_pads_numeric_cross_engine() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADL3_SRC, &["f", "42", "5", "0"]);
assert_eq!(out, "00042", "{engine}: padl zero-pad");
}
}
#[test]
fn padr_with_dot_pads_text_cross_engine() {
for engine in ENGINES_ALL {
let out = run_ok(engine, PADR3_SRC, &["f", "x", "4", "."]);
assert_eq!(out, "x...", "{engine}: padr dot-pad");
}
}
#[test]
fn pad_char_already_wide_no_op_cross_engine() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL3_SRC, &["f", "already-wider", "4", "0"]);
let r = run_ok(engine, PADR3_SRC, &["f", "already-wider", "4", "."]);
assert_eq!(l, "already-wider", "{engine}: padl already-wider");
assert_eq!(r, "already-wider", "{engine}: padr already-wider");
}
}
#[test]
fn pad_char_unicode_scalar_cross_engine() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL3_SRC, &["f", "hi", "5", "·"]);
assert_eq!(l, "···hi", "{engine}: padl unicode pad");
}
}
#[test]
fn pad_char_multichar_errors_tree_vm() {
for engine in &["--run-tree", "--run-vm"] {
let _ = run_err(engine, PADL3_SRC, &["f", "x", "5", "ab"]);
let _ = run_err(engine, PADR3_SRC, &["f", "x", "5", "ab"]);
}
}
#[test]
fn pad_char_empty_errors_tree_vm() {
for engine in &["--run-tree", "--run-vm"] {
let _ = run_err(engine, PADL3_SRC, &["f", "x", "5", ""]);
let _ = run_err(engine, PADR3_SRC, &["f", "x", "5", ""]);
}
}
#[test]
fn pad_two_arg_form_still_pads_with_space_cross_engine() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL_SRC, &["f", "42", "5"]);
let r = run_ok(engine, PADR_SRC, &["f", "42", "5"]);
assert_eq!(l, " 42", "{engine}: padl 2-arg space default");
assert_eq!(r, "42 ", "{engine}: padr 2-arg space default");
}
}
#[test]
fn pad_char_non_text_rejected_at_verify() {
let src = "f s:t w:n>t;padl s w 7"; let err = run_err("--run-tree", src, &["f", "x", "5"]);
assert!(
err.contains("ILO-T013") || err.contains("expects t"),
"expected ILO-T013 for non-text pad char, got: {err}"
);
}
#[test]
fn pad_arity_overload_rejects_four_args() {
let src = "f s:t w:n p:t q:t>t;padl s w p q";
let err = run_err("--run-tree", src, &["f", "x", "5", "0", "0"]);
assert!(
err.contains("ILO-T006") || err.contains("arity") || err.contains("2 or 3"),
"expected ILO-T006 arity mismatch, got: {err}"
);
}
#[test]
fn pad_zero_width_passes_through_with_pad_char() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL3_SRC, &["f", "abc", "0", "0"]);
let r = run_ok(engine, PADR3_SRC, &["f", "abc", "0", "."]);
assert_eq!(l, "abc", "{engine}: padl 3-arg w=0");
assert_eq!(r, "abc", "{engine}: padr 3-arg w=0");
}
}
#[test]
fn pad_empty_string_pads_to_width_with_pad_char() {
for engine in ENGINES_ALL {
let l = run_ok(engine, PADL3_SRC, &["f", "", "4", "0"]);
let r = run_ok(engine, PADR3_SRC, &["f", "", "4", "."]);
assert_eq!(l, "0000", "{engine}: padl empty + zero pad");
assert_eq!(r, "....", "{engine}: padr empty + dot pad");
}
}