Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_austenite/tests/oracle/trio.rs

6.2 KiB, 21 runs

created by r1870400018:38849, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! The no-regression trio: three fixed fixtures the oracle harness also checks on every run, each
2//! guarding a real fix landed on `main` shortly before this unit -- the inline maths gallery, the
3//! display-equation cases (aligned `&=`, a big-operator's under/over limits, `ceil`/`floor`/`binom`),
4//! and the compact, searchable PDF writer. None of the three needs the Typst oracle or the corpus: they
5//! are smoke-level guards against the engine itself regressing, run from the crate's own public API
6//! exactly as `mathgallery` and `austenite` call it.
7
8use oxedyne_fe2o3_austenite::{
9 doc::{
10 self,
11 },
12 fonts::FaceResolver,
13 theme::Theme,
14 driver::{
15 self,
16 Config,
17 },
18 emit,
19 font::FontMetrics,
20 lang,
21 page::PageGeometry,
22};
23
24use oxedyne_fe2o3_core::prelude::*;
25use oxedyne_fe2o3_font::{
26 face::Role,
27 shape::Dir,
28};
29
30use std::path::Path;
31use std::process::Command;
32use std::sync::Arc;
33
34/// Runs the `mathgallery` binary (the living maths-layout comparison harness, `src/bin/mathgallery.rs`)
35/// into `work_dir` and checks it exited cleanly and left at least one non-empty equation PDF -- a smoke
36/// guard against the inline maths pipeline (parse, layout, PDF emit) regressing outright, not a
37/// pixel check, which is what the gallery itself exists for a person to do by eye.
38pub fn inline_maths_gallery_renders(work_dir: &Path) -> Outcome<()> {
39 let out_dir = work_dir.join("trio-mathgallery");
40 let bin = env!("CARGO_BIN_EXE_mathgallery");
41 let output = match Command::new(bin).arg(&out_dir).output() {
42 Ok(o) => o,
43 Err(e) => return Err(err!(e, "Could not run the built mathgallery binary at {:?}.", bin; IO)),
44 };
45 if !output.status.success() {
46 return Err(err!("mathgallery exited with {}:\n{}",
47 output.status, String::from_utf8_lossy(&output.stderr); Invalid, Unexpected));
48 }
49 let entries = match std::fs::read_dir(&out_dir) {
50 Ok(e) => e,
51 Err(e) => return Err(err!(e, "Could not list {:?}.", out_dir; File, Read)),
52 };
53 let mut pdf_count = 0usize;
54 for entry in entries.flatten() {
55 let p = entry.path();
56 if p.extension().and_then(|e| e.to_str()) == Some("pdf") {
57 let len = res!(std::fs::metadata(&p)).len();
58 if len == 0 {
59 return Err(err!("mathgallery wrote an empty PDF at {:?}.", p; Invalid, Size));
60 }
61 pdf_count += 1;
62 }
63 }
64 if pdf_count == 0 {
65 return Err(err!("mathgallery wrote no equation PDFs into {:?}.", out_dir; Missing, Invalid));
66 }
67 Ok(())
68}
69
70/// Authors, lays out and renders one small document carrying the display-equation shapes now on
71/// `main` -- a multi-line `&`-aligned block (`87ca15c`/`8893ff6` fixed exactly this being stolen line
72/// by line before it reached the maths parser), a big operator's `_`/`^` limits, and `ceil`, `floor`
73/// and `binom` (`59c6e3b`) -- through the real reader-to-PDF pipeline, checking only that the pipeline
74/// completes and the result is a genuine one-page PDF: the geometry of each shape is `mathgallery`'s
75/// and the oracle's job, this fixture's is to trip the moment any of the four stops setting at all.
76pub fn display_equation_cases_render() -> Outcome<()> {
77 let src = r#"= Display Equations Regression
78
79$ a &= b + c \
80&= d $ <eq_aligned>
81
82$ sum_(i=1)^n a_i $ <eq_bigop_limits>
83
84$ binom(n, k) + ceil(x) + floor(y) $ <eq_binom_ceil_floor>
85"#;
86 let blocks = res!(lang::to_blocks(src));
87 let fonts = Arc::new(res!(oxedyne_fe2o3_austenite::fonts::libertinus()));
88 let geom = PageGeometry::a4();
89 let style = Theme::default();
90 let (document, _heads) = res!(doc::author(fonts.clone(), geom, &style, &FaceResolver::default(), &blocks, None, None));
91 let metrics = FontMetrics::new(fonts.clone(), Role::Body, Dir::Ltr, style.text.body_size);
92 let out = res!(driver::run(&document, &metrics, Config::default()));
93 if out.pages.is_empty() {
94 return Err(err!("The display-equation regression doc rendered no pages."; Invalid, Missing));
95 }
96 let pdf = res!(emit::pdf::render_document(&out.pages));
97 if pdf.len() < 200 {
98 return Err(err!(
99 "The display-equation regression PDF is only {} byte(s) -- too small to hold real content.",
100 pdf.len(); Invalid, Size));
101 }
102 Ok(())
103}
104
105/// Renders a one-paragraph prose document and checks the compact/searchable PDF writer's two
106/// invariants at once: the page's text comes back out through `pdftotext` (the Type-3-plus-ToUnicode
107/// glyph encoding stays searchable), and the file stays well under a generous bound (compaction has not
108/// regressed into bloat). The bound is generous on purpose -- this guards against a large regression,
109/// not a byte-for-byte size lock a legitimate change would have to keep re-tuning.
110pub fn pdf_stays_compact_and_extractable(work_dir: &Path) -> Outcome<()> {
111 const NEEDLE: &str = "quick brown fox";
112 const SIZE_BOUND: u64 = 300_000; // bytes; one page of one paragraph of body text
113
114 let src = fmt!("= Regression Prose\n\nThe {} jumps over the lazy dog while the compact PDF writer \
115 keeps its content stream small and its text extractable.\n", NEEDLE);
116 let blocks = res!(lang::to_blocks(&src));
117 let fonts = Arc::new(res!(oxedyne_fe2o3_austenite::fonts::libertinus()));
118 let geom = PageGeometry::a4();
119 let style = Theme::default();
120 let (document, _heads) = res!(doc::author(fonts.clone(), geom, &style, &FaceResolver::default(), &blocks, None, None));
121 let metrics = FontMetrics::new(fonts.clone(), Role::Body, Dir::Ltr, style.text.body_size);
122 let out = res!(driver::run(&document, &metrics, Config::default()));
123 let pdf = res!(emit::pdf::render_document(&out.pages));
124
125 let pdf_path = work_dir.join("trio-pdf-compact.pdf");
126 res!(std::fs::write(&pdf_path, &pdf));
127
128 let len = pdf.len() as u64;
129 if len > SIZE_BOUND {
130 return Err(err!(
131 "The compact-PDF fixture wrote {} byte(s), over the {} byte bound -- the writer may have \
132 regressed out of compaction.", len, SIZE_BOUND; Invalid, TooBig));
133 }
134
135 let output = match Command::new("pdftotext").arg(&pdf_path).arg("-").output() {
136 Ok(o) => o,
137 Err(e) => return Err(err!(e, "Could not run `pdftotext`."; IO)),
138 };
139 if !output.status.success() {
140 return Err(err!("pdftotext on {:?} exited with {}.", pdf_path, output.status; Invalid, Unexpected));
141 }
142 let text = String::from_utf8_lossy(&output.stdout);
143 if !text.contains(NEEDLE) {
144 return Err(err!(
145 "pdftotext on {:?} did not recover {:?}; extracted: {:?}", pdf_path, NEEDLE, text;
146 Invalid, Mismatch));
147 }
148 Ok(())
149}