use std::fmt::Write as _;
use std::fs;
use std::io::Write as _;
fn blocks() -> Vec<&'static str> {
vec![
"2024-01-01 open Assets:Bank",
"2024-01-01 open Expenses:Food",
"2024-01-01 open Equity:Open",
"2024-01-05 * \"a\"\n Assets:Bank -10.00 USD\n Expenses:Food 10.00 USD",
"2024-01-07 * \"b\"\n Assets:Bank -5.00 USD\n Expenses:Food 4.00 USD",
"2024-01-09 pad Assets:Bank Equity:Open",
"2024-01-09 balance Assets:Bank -15.00 USD",
"2024-01-11 * \"c\"\n Assets:Bank -1.00 USD\n Expenses:Never 1.00 USD",
]
}
fn codes_for(order: &[usize], dir: &std::path::Path) -> Vec<String> {
let all = blocks();
let mut src = String::new();
for &i in order {
let _ = writeln!(src, "{}\n", all[i]);
}
let file = dir.join("main.beancount");
let mut f = fs::File::create(&file).expect("create");
f.write_all(src.as_bytes()).expect("write");
drop(f);
let mut loader = rustledger_loader::Loader::new();
let result = loader.load(&file).expect("load");
let options = rustledger_loader::validation_options_from_options(&result.options);
let today = rustledger_core::naive_date(2024, 12, 31).unwrap();
let plain: Vec<rustledger_core::Directive> =
result.directives.iter().map(|d| (**d).clone()).collect();
let session = rustledger_validate::ValidationSession::new(options);
let (session, early) = session.run_early(&plain, today);
let (session, late) = session.run_late(&plain, today);
let pad = session.finalize();
let mut codes: Vec<String> = early
.iter()
.chain(&late)
.chain(&pad)
.map(|e| e.code.code().to_owned())
.collect();
codes.sort_unstable();
codes
}
#[test]
fn diagnostics_are_invariant_under_input_permutation() {
let dir = tempfile::Builder::new()
.prefix("rledger_order_")
.tempdir()
.expect("temp dir");
let canonical: Vec<usize> = (0..blocks().len()).collect();
let expected = codes_for(&canonical, dir.path());
for code in ["E1001", "E2003", "E3001"] {
assert!(
expected.contains(&code.to_owned()),
"fixture no longer produces {code}; got {expected:?}",
);
}
let mut perm = canonical;
let mut checked = 0usize;
permute(&mut perm, 0, &mut |p| {
let got = codes_for(p, dir.path());
assert_eq!(
got, expected,
"input order {p:?} changed the diagnostics; validation must not \
depend on how the file was typed",
);
checked += 1;
});
let expected_perms: usize = (1..=blocks().len()).product();
assert_eq!(
checked,
expected_perms,
"expected every permutation of all {} blocks",
blocks().len(),
);
}
fn permute(v: &mut Vec<usize>, k: usize, f: &mut impl FnMut(&[usize])) {
if k == v.len() {
f(v);
return;
}
for i in k..v.len() {
v.swap(k, i);
permute(v, k + 1, f);
v.swap(k, i);
}
}