use std::path::Path;
use std::process::{Command, Output};
use tempfile::TempDir;
fn badness(dir: &Path, args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_badness"))
.arg("--no-config")
.args(args)
.current_dir(dir)
.output()
.expect("run badness")
}
fn stdout(output: &Output) -> String {
String::from_utf8_lossy(&output.stdout).into_owned()
}
fn stderr(output: &Output) -> String {
String::from_utf8_lossy(&output.stderr).into_owned()
}
#[test]
fn passing_file_exits_zero() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("ok.tex"), "Hello \\emph{world}.\n").unwrap();
let output = badness(
dir.path(),
&["debug", "format", "--checks", "all", "ok.tex"],
);
assert!(output.status.success(), "stderr: {}", stderr(&output));
assert!(stdout(&output).contains("All checks passed (checks: all, files: 1)"));
}
#[test]
fn report_on_passing_file_has_no_failure_headings() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("ok.tex"), "Hello world.\n").unwrap();
let output = badness(dir.path(), &["debug", "format", "--report", "ok.tex"]);
assert!(output.status.success(), "stderr: {}", stderr(&output));
let report = stdout(&output);
assert!(report.contains("# Debug-format regression report"));
assert!(report.contains("All checks passed."));
assert!(!report.contains("(idempotency)"));
assert!(!report.contains("(losslessness)"));
}
#[test]
fn dump_passes_writes_sanitized_artifact_names() {
let dir = TempDir::new().unwrap();
std::fs::create_dir(dir.path().join("sub dir")).unwrap();
let source = "Hello \\emph{world}.\n";
std::fs::write(dir.path().join("sub dir/a b.tex"), source).unwrap();
let output = badness(
dir.path(),
&[
"debug",
"format",
"--dump-dir",
"dumps",
"--dump-passes",
"sub dir/a b.tex",
],
);
assert!(output.status.success(), "stderr: {}", stderr(&output));
let dumps = dir.path().join("dumps");
let input = std::fs::read_to_string(dumps.join("sub_dir_a_b.tex.idempotency.input.txt"))
.expect("input dump exists");
let once = std::fs::read_to_string(dumps.join("sub_dir_a_b.tex.idempotency.once.txt"))
.expect("once dump exists");
let twice = std::fs::read_to_string(dumps.join("sub_dir_a_b.tex.idempotency.twice.txt"))
.expect("twice dump exists");
assert_eq!(input, source);
assert_eq!(once, twice);
assert!(
dumps
.join("sub_dir_a_b.tex.losslessness.input.txt")
.exists()
);
assert!(
dumps
.join("sub_dir_a_b.tex.losslessness.parsed.txt")
.exists()
);
}
#[test]
fn dump_passes_requires_dump_dir() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("ok.tex"), "x\n").unwrap();
let output = badness(dir.path(), &["debug", "format", "--dump-passes", "ok.tex"]);
assert_eq!(output.status.code(), Some(2), "clap usage error expected");
}
#[test]
fn trivia_check_passes_on_stable_prose() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("ok.tex"), "alpha\nbeta gamma delta.\n").unwrap();
let output = badness(
dir.path(),
&["debug", "format", "--checks", "trivia", "ok.tex"],
);
assert!(output.status.success(), "stderr: {}", stderr(&output));
assert!(stdout(&output).contains("All checks passed (checks: trivia, files: 1)"));
}
#[test]
fn trivia_check_counts_skipped_bib_files_separately() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("ok.tex"), "alpha beta.\n").unwrap();
std::fs::write(
dir.path().join("refs.bib"),
"@article{key, title = {T}, year = {2020}}\n",
)
.unwrap();
let output = badness(dir.path(), &["debug", "format", "--checks", "trivia", "."]);
assert!(output.status.success(), "stderr: {}", stderr(&output));
assert!(
stdout(&output).contains("All checks passed (checks: trivia, files: 1, skipped: 1)"),
"stdout: {}",
stdout(&output)
);
let output = badness(
dir.path(),
&["debug", "format", "--checks", "trivia", "--report", "."],
);
assert!(output.status.success(), "stderr: {}", stderr(&output));
let report = stdout(&output);
assert!(report.contains("- Files checked: 1"), "report: {report}");
assert!(report.contains("- Files skipped: 1"), "report: {report}");
}
#[test]
fn trivia_check_fires_on_a_known_hybrid() {
let dir = TempDir::new().unwrap();
std::fs::write(
dir.path().join("expl.dtx"),
"% \\section{Implementation}\n\
% \\begin{macrocode}\n\
\\ExplSyntaxOn\n\
\\tl_new:N \\l_tmpa_tl\n\
% \\end{macrocode}\n\
%\n\
% Some prose.\n\
% \\begin{macrocode}\n\
\\ExplSyntaxOff\n\
% \\end{macrocode}\n",
)
.unwrap();
let output = badness(
dir.path(),
&["debug", "format", "--checks", "trivia", "expl.dtx"],
);
assert_eq!(output.status.code(), Some(1));
let log = stderr(&output);
assert!(log.contains("Debug check failed (trivia:"), "log: {log}");
let output = badness(
dir.path(),
&[
"debug", "format", "--checks", "trivia", "--report", "expl.dtx",
],
);
assert_eq!(output.status.code(), Some(1));
let report = stdout(&output);
assert!(report.contains("- Checks: `trivia`"), "report: {report}");
assert!(
report.contains("### 1. `expl.dtx` (trivia)"),
"report: {report}"
);
assert!(report.contains("- Variant: `"), "report: {report}");
let output = badness(
dir.path(),
&["debug", "format", "--checks", "all", "--report", "expl.dtx"],
);
assert!(output.status.success(), "stderr: {}", stderr(&output));
assert!(!stdout(&output).contains("(trivia)"));
}
#[test]
fn line_width_flag_reaches_the_formatter() {
let dir = TempDir::new().unwrap();
let long = "alpha beta gamma delta epsilon zeta eta theta iota kappa\n";
std::fs::write(dir.path().join("wide.tex"), long).unwrap();
let output = badness(
dir.path(),
&[
"debug",
"format",
"--checks",
"idempotency",
"--line-width",
"30",
"--dump-dir",
"dumps",
"--dump-passes",
"wide.tex",
],
);
assert!(output.status.success(), "stderr: {}", stderr(&output));
let once = std::fs::read_to_string(
dir.path()
.join("dumps")
.join("wide.tex.idempotency.once.txt"),
)
.expect("once dump exists");
assert!(
once.lines().count() > 1 && once.lines().all(|l| l.len() <= 30),
"expected a wrap at width 30, got: {once:?}"
);
}
#[test]
fn format_error_never_reads_as_an_invariant_failure() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("bad.tex"), "a{\n").unwrap();
let output = badness(dir.path(), &["debug", "format", "bad.tex"]);
assert_eq!(output.status.code(), Some(1));
let log = stderr(&output).to_lowercase();
assert!(log.contains("(format-error)"), "log: {log}");
assert!(!log.contains("idempot"), "log: {log}");
assert!(!log.contains("lossless"), "log: {log}");
let output = badness(dir.path(), &["debug", "format", "--report", "bad.tex"]);
assert_eq!(output.status.code(), Some(1));
let report = stdout(&output);
assert!(report.contains("### 1. `bad.tex` (format-error)"));
assert!(report.contains("- Files checked: 1"));
}