mod common;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
const MIXED_PRECISION: &str = r#"2024-01-01 open Assets:Bank
2024-01-01 open Assets:Broker
2024-01-01 open Income:Salary
2024-01-01 open Expenses:Food
2024-01-15 * "salary"
Assets:Bank 2500.00 USD
Income:Salary -2500.00 USD
2024-02-01 * "groceries"
Assets:Bank -42.35 USD
Expenses:Food 42.35 USD
2024-02-10 * "integer-written transfer"
Assets:Bank -100 USD
Assets:Broker 100 USD
"#;
fn run_report(binary: &PathBuf, path: &str, args: &[&str]) -> String {
let out = Command::new(binary)
.arg("report")
.arg(path)
.args(args)
.arg("--no-pager")
.output()
.expect("run rledger report");
assert!(
out.status.success(),
"report failed: {}",
String::from_utf8_lossy(&out.stderr),
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[test]
fn balances_render_at_ledger_precision() {
let bin = require_rledger!();
let mut f = tempfile::Builder::new()
.prefix("display-ctx-")
.suffix(".beancount")
.tempfile()
.expect("tempfile");
f.write_all(MIXED_PRECISION.as_bytes()).expect("write");
let path = f.path().to_str().unwrap().to_owned();
let text = run_report(&bin, &path, &["balances"]);
assert!(
text.contains("100.00 USD"),
"integer-written USD amounts must render at the ledger's 2dp \
convention (bean-query parity), got: {text}",
);
assert!(
!text.contains(" 100 USD"),
"raw booking-scale rendering must not leak through: {text}",
);
let csv = run_report(&bin, &path, &["balances", "--format", "csv"]);
assert!(
csv.contains(",100.00,USD"),
"CSV must use the same context-formatted numbers: {csv}",
);
}
#[test]
fn income_totals_render_at_ledger_precision() {
let bin = require_rledger!();
let mut f = tempfile::Builder::new()
.prefix("display-ctx-income-")
.suffix(".beancount")
.tempfile()
.expect("tempfile");
f.write_all(MIXED_PRECISION.as_bytes()).expect("write");
let path = f.path().to_str().unwrap().to_owned();
let text = run_report(&bin, &path, &["income"]);
assert!(
text.contains("-2500.00 USD") && text.contains("2500.00 USD"),
"income statement must render at ledger precision: {text}",
);
}
#[test]
fn csv_amounts_with_render_commas_are_quoted() {
let bin = require_rledger!();
let source = format!("option \"render_commas\" \"TRUE\"\n{MIXED_PRECISION}");
let mut f = tempfile::Builder::new()
.prefix("display-ctx-commas-")
.suffix(".beancount")
.tempfile()
.expect("tempfile");
f.write_all(source.as_bytes()).expect("write");
let path = f.path().to_str().unwrap().to_owned();
let csv = run_report(&bin, &path, &["balances", "--format", "csv"]);
assert!(
csv.contains("\"2,357.65\""),
"comma-bearing amounts must be CSV-quoted: {csv}",
);
for line in csv.lines().skip(1) {
let quoted_stripped: String = {
let mut out = String::new();
let mut in_q = false;
for c in line.chars() {
match c {
'\"' => in_q = !in_q,
',' if in_q => out.push('_'),
c => out.push(c),
}
}
out
};
assert_eq!(
quoted_stripped.matches(',').count(),
3,
"row must have 4 columns outside quotes: {line}",
);
}
}