use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
fn unique_path(prefix: &str) -> std::path::PathBuf {
static SEQ: AtomicU64 = AtomicU64::new(0);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"cljrs_fmt_{}_{nanos}_{seq}_{}.cljrs",
prefix,
std::process::id()
))
}
fn run_interp(src: &str) -> String {
let path = unique_path("interp");
std::fs::write(&path, src).expect("write script");
let output = Command::new(env!("CARGO_BIN_EXE_cljrs"))
.args(["run"])
.arg(&path)
.output()
.expect("spawn cljrs");
let _ = std::fs::remove_file(&path);
assert!(
output.status.success(),
"cljrs exited {:?}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).expect("utf8 stdout")
}
fn run_jit(src: &str) -> String {
let path = unique_path("jit");
std::fs::write(&path, src).expect("write script");
let output = Command::new(env!("CARGO_BIN_EXE_cljrs"))
.args(["--jit-threshold", "50", "run"])
.arg(&path)
.env("CLJRS_EAGER_LOWER", "1")
.output()
.expect("spawn cljrs");
let _ = std::fs::remove_file(&path);
assert!(
output.status.success(),
"cljrs (jit) exited {:?}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).expect("utf8 stdout")
}
const FMT_SCRIPT: &str = r#"
(println (format "%-5s" "ab"))
(println (format "%-5s|" "ab"))
(println (format "%-15s%s" "a" "b"))
(println (format "%s %s" "a" "b"))
(println (format "%d" 5))
(println (format "%5d" 42))
(println (format "%-5d" 42))
(println (format "%05d" 42))
(println (format "%+d" 42))
(println (format "%5s" "ab"))
"#;
fn check_output(out: &str, label: &str) {
let lines: Vec<&str> = out.lines().collect();
assert_eq!(
lines.len(),
10,
"{label}: expected 10 output lines, got:\n{out}"
);
assert_eq!(lines[0], "ab ", "{label}: %-5s wrong");
assert_eq!(lines[1], "ab |", "{label}: %-5s| wrong");
assert_eq!(
lines[2], "a b",
"{label}: %-15s%s wrong — got {:?}",
lines[2]
);
assert_eq!(lines[3], "a b", "{label}: %s %s wrong");
assert_eq!(lines[4], "5", "{label}: %d wrong");
assert_eq!(lines[5], " 42", "{label}: %5d wrong");
assert_eq!(lines[6], "42 ", "{label}: %-5d wrong");
assert_eq!(lines[7], "00042", "{label}: %05d wrong");
assert_eq!(lines[8], "+42", "{label}: %+d wrong");
assert_eq!(lines[9], " ab", "{label}: %5s wrong");
}
#[test]
fn format_width_flags_interpreter() {
let out = run_interp(FMT_SCRIPT);
check_output(&out, "interpreter");
}
#[test]
fn format_width_flags_jit() {
let src = r#"
(defn check-format [i]
(let [r1 (format "%-5s" "ab")
r2 (format "%-5s|" "ab")
r3 (format "%-15s%s" "a" "b")
r4 (format "%05d" 42)
r5 (format "%5s" "ab")]
(when (or (not= r1 "ab ")
(not= r2 "ab |")
(not= r3 "a b")
(not= r4 "00042")
(not= r5 " ab"))
(println "FAIL at i=" i
"r1=" r1 "r2=" r2 "r3=" r3 "r4=" r4 "r5=" r5))))
(dotimes [i 10000]
(check-format i))
(println "done")
"#;
let out = run_jit(src);
assert!(
!out.contains("FAIL"),
"format width/flags produced wrong results under JIT:\n{out}"
);
assert!(
out.trim_end().ends_with("done"),
"expected 'done' at end of JIT output:\n{out}"
);
}