use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn engines() -> Vec<&'static str> {
let mut v = vec!["tree", "--run-vm"];
if cfg!(feature = "cranelift") {
v.push("--jit");
}
v
}
const NOW: &str = "1705276800";
fn run_ok(engine: &str, src: &str, fn_name: &str) -> String {
let out = match engine {
"tree" => ilo()
.args(["run", src, fn_name])
.output()
.expect("failed to run ilo"),
_ => ilo()
.args([src, engine, fn_name])
.output()
.expect("failed to run ilo"),
};
assert!(
out.status.success(),
"ilo {engine} failed for fn={fn_name}: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
fn run_result(engine: &str, src: &str, fn_name: &str) -> (bool, String, String) {
let out = match engine {
"tree" => ilo()
.args(["run", src, fn_name])
.output()
.expect("failed to run ilo"),
_ => ilo()
.args([src, engine, fn_name])
.output()
.expect("failed to run ilo"),
};
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).trim().to_string(),
String::from_utf8_lossy(&out.stderr).to_string(),
)
}
fn mk(phrase: &str) -> String {
format!("f>n;dtparse-rel!! \"{phrase}\" {NOW}")
}
fn run_epoch(engine: &str, phrase: &str) -> i64 {
let src = mk(phrase);
let s = run_ok(engine, &src, "f");
s.trim()
.parse::<f64>()
.unwrap_or_else(|_| panic!("expected number epoch for '{phrase}', got: {s:?}")) as i64
}
#[test]
fn today() {
for e in engines() {
assert_eq!(run_epoch(e, "today"), 1705276800, "engine={e}");
}
}
#[test]
fn yesterday() {
for e in engines() {
assert_eq!(run_epoch(e, "yesterday"), 1705190400, "engine={e}");
}
}
#[test]
fn tomorrow() {
for e in engines() {
assert_eq!(run_epoch(e, "tomorrow"), 1705363200, "engine={e}");
}
}
#[test]
fn n_days_ago() {
for e in engines() {
assert_eq!(run_epoch(e, "3 days ago"), 1705017600, "engine={e}");
assert_eq!(run_epoch(e, "0 days ago"), 1705276800, "engine={e}");
}
}
#[test]
fn in_n_days() {
for e in engines() {
assert_eq!(run_epoch(e, "in 5 days"), 1705708800, "engine={e}");
assert_eq!(run_epoch(e, "in 0 days"), 1705276800, "engine={e}");
}
}
#[test]
fn n_weeks_ago() {
for e in engines() {
assert_eq!(run_epoch(e, "2 weeks ago"), 1704067200, "engine={e}");
}
}
#[test]
fn in_n_weeks() {
for e in engines() {
assert_eq!(run_epoch(e, "in 1 week"), 1705881600, "engine={e}");
}
}
#[test]
fn n_months_ago() {
for e in engines() {
assert_eq!(run_epoch(e, "1 month ago"), 1702598400, "engine={e}");
}
}
#[test]
fn in_n_months() {
for e in engines() {
assert_eq!(run_epoch(e, "in 2 months"), 1710460800, "engine={e}");
}
}
#[test]
fn month_end_clamp() {
let src = "f>n;dtparse-rel!! \"in 1 months\" 1706659200";
for e in engines() {
let epoch = run_ok(e, src, "f")
.trim()
.parse::<f64>()
.unwrap_or_else(|_| panic!("expected epoch, engine={e}")) as i64;
assert_eq!(epoch, 1709164800, "engine={e} (expected 2024-02-29)");
}
}
#[test]
fn last_weekday_long_form() {
for e in engines() {
assert_eq!(run_epoch(e, "last friday"), 1705017600, "engine={e}");
assert_eq!(run_epoch(e, "last sunday"), 1705190400, "engine={e}");
}
}
#[test]
fn last_weekday_is_never_today() {
for e in engines() {
assert_eq!(run_epoch(e, "last monday"), 1704672000, "engine={e}");
}
}
#[test]
fn next_weekday_long_form() {
for e in engines() {
assert_eq!(run_epoch(e, "next friday"), 1705622400, "engine={e}");
}
}
#[test]
fn next_weekday_is_never_today() {
for e in engines() {
assert_eq!(run_epoch(e, "next monday"), 1705881600, "engine={e}");
}
}
#[test]
fn this_weekday() {
for e in engines() {
assert_eq!(run_epoch(e, "this wednesday"), 1705449600, "engine={e}");
assert_eq!(run_epoch(e, "this monday"), 1705276800, "engine={e}");
}
}
#[test]
fn short_weekday_names() {
for e in engines() {
assert_eq!(
run_epoch(e, "last fri"),
run_epoch(e, "last friday"),
"engine={e}"
);
assert_eq!(run_epoch(e, "next sat"), 1705708800, "engine={e}");
assert_eq!(run_epoch(e, "this tue"), 1705363200, "engine={e}");
}
}
#[test]
fn iso_passthrough() {
for e in engines() {
assert_eq!(run_epoch(e, "2023-12-25"), 1703462400, "engine={e}");
assert_eq!(run_epoch(e, "1970-01-01"), 0, "engine={e}");
}
}
#[test]
fn unknown_phrase_returns_err() {
let src = "f>R n t;dtparse-rel \"sometime soon\" 1705276800";
for e in engines() {
let (ok, stdout, stderr) = run_result(e, src, "f");
assert!(!ok, "engine={e}: expected exit 1 for Err result");
let combined = format!("{stdout}{stderr}");
assert!(
combined.contains("dtparse-rel") || combined.contains("unrecognised"),
"engine={e}: expected Err with 'dtparse-rel', got stdout={stdout:?} stderr={stderr:?}"
);
}
}
#[test]
fn non_digit_count_falls_through_to_unrecognised_phrase() {
let cases = [
("in this day", "in 0 days"), ("in some days", "in 0 days"), ("for week ago", "0 weeks ago"), ("over the month ago", "0 months ago"), ("in some months", "in 0 months"),
];
for (phrase, _hint) in cases {
let src = format!("f>R n t;dtparse-rel \"{phrase}\" 1705276800");
for e in engines() {
let (ok, stdout, stderr) = run_result(e, &src, "f");
assert!(!ok, "engine={e} phrase={phrase:?}: expected Err exit 1");
let combined = format!("{stdout}{stderr}");
assert!(
combined.contains("unrecognised") || combined.contains("expected"),
"engine={e} phrase={phrase:?}: expected unrecognised-phrase Err, \
got stdout={stdout:?} stderr={stderr:?}"
);
assert!(
!combined.contains("invalid day count")
&& !combined.contains("invalid week count")
&& !combined.contains("invalid month count"),
"engine={e} phrase={phrase:?}: leaked 'invalid <unit> count' error \
from suffix-strip false positive, got stdout={stdout:?} stderr={stderr:?}"
);
}
}
}
#[test]
fn unknown_weekday_name_returns_err() {
let src = "f>R n t;dtparse-rel \"last flursday\" 1705276800";
for e in engines() {
let (ok, stdout, stderr) = run_result(e, src, "f");
assert!(!ok, "engine={e}: expected exit 1 for Err result");
let combined = format!("{stdout}{stderr}");
assert!(
combined.contains("flursday") || combined.contains("weekday"),
"engine={e}: expected Err naming unknown weekday, got stdout={stdout:?} stderr={stderr:?}"
);
}
}
#[test]
fn verify_wrong_arg_types() {
let src = "f>R n t;dtparse-rel 42 0";
let out = ilo()
.args([src, "--vm", "f"])
.output()
.expect("failed to run ilo");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("ILO-T013") || stderr.contains("dtparse-rel"),
"expected type error, got: {stderr}"
);
}