3.1 KiB
Rendering-regression harness
Renders the committed corpus/ with the PDFium engine and compares each page to
a frozen baseline using SSIM. A failure means this build renders
differently from the blessed build — not that it disagrees with Acrobat.
Prerequisites
- The engine is built and copied to
gateway/(runscripts/build_cpp.ps1 -Preset win-local-pdfium). - The gateway venv has
numpyandPillow(gateway/.venv).
Usage
# Check the current build against the frozen baseline (CI mode; non-zero exit on regression)
gateway/.venv/Scripts/python.exe tests/regression/run.py
# Re-freeze the baseline after an *intentional* rendering change (review the diff!)
gateway/.venv/Scripts/python.exe tests/regression/run.py --update
Options: --dpi 72, --max-pages 2, --threshold 0.990, --corpus, --baseline.
Large corpus (500+ real-world PDFs)
The corpus is pinned and shared via a manifest so every developer and CI run
gets a byte-identical set. The 600 PDFs themselves are gitignored (≈68 MB); only
the manifest (corpus-manifest.json, name + SHA-256 per file, ≈96 KB) is
committed. Reproduce the exact corpus with:
python scripts/fetch_corpus.py --manifest tests/regression/corpus-manifest.json
This downloads each pinned file and verifies its SHA-256 — a mismatch is
reported and skipped, so the corpus can never silently drift between machines.
(scripts/fetch_corpus.py with no --manifest just grabs "latest" from pdf.js,
which is not reproducible — use it only to refresh/regenerate the manifest.)
Two ways to use the corpus for regression:
# 1. Render-stability sweep — no baseline; passes as long as the engine never
# crashes on any real-world PDF. The CI-friendly form of a 500+ regression.
python scripts/fetch_corpus.py
gateway/.venv/Scripts/python.exe tests/regression/run.py --sweep --corpus corpus/fuzz --max-pages 3
# 2. SSIM baseline over the large corpus (baseline is gitignored — it is derived
# from gitignored inputs and is ~tens of MB, so it lives locally / as a CI
# artifact, not in git).
... run.py --update --corpus corpus/fuzz --baseline tests/regression/baseline-large
... run.py --corpus corpus/fuzz --baseline tests/regression/baseline-large
The committed baseline/ stays the small curated set (reviewable in PRs);
baseline-large/ is the throwaway large-corpus reference. In CI the libFuzzer
binary doubles as a sweep via pdfengine_fuzz -runs=0 corpus/fuzz/ (see
.github/workflows/fuzz.yml).
How it works
ssim.py— dependency-light SSIM (numpy only) via an integral-image box filter. Discriminates correctly: identical→1.0, shifted→~0.81, noise→~0.00, shape mismatch→0.0.run.py— enumeratescorpus/**/*.pdf(excluding the gitignoredcorpus/fuzz/), renders to grayscale, and scores againstbaseline/*.png.baseline/— committed PNGs. Because the baseline is the engine's own prior output, it is tied to this PDFium version; re-freeze deliberately when PDFium is upgraded.
Encrypted / intentionally-malformed fixtures are skipped (and reported), not failed.