How PeachPDF Is Tested
PeachPDF is a rendering engine, and rendering bugs are easy to ship and hard to notice — a PDF can contain exactly the right operators and still look wrong. This page describes how the project guards against that: the automated test suite, the continuous-integration pipeline that runs it, the coverage gate on every change, and the rasterization-based checks that verify output actually looks right rather than merely containing the expected tokens.
If you’re contributing and want the exact local commands and conventions, see CONTRIBUTING.md — this page is the higher-level overview of what runs and why.
The test suite
The tests live in src/PeachPDF.Tests, an xUnit project of 3000+ tests covering the HTML parser, CSS cascade, layout engines (block, inline, flex, table, multi-column), painting, the SVG subsystem, the font pipeline, and PDF output.
The suite multi-targets net8.0 and net10.0, and both target frameworks are first-class: continuous integration builds and runs the whole suite against each. (For routine local iteration you can run a single target framework to halve build/test time — see CONTRIBUTING.md — but that’s a local convenience, not the canonical way the project is validated.)
Continuous integration
Every pull request and every push to main runs the test workflow across a three-OS matrix — windows-latest, ubuntu-latest, and macos-latest — so platform-specific behavior (most often font discovery and text metrics) is exercised everywhere PeachPDF is expected to run. On each OS the job:
- restores and builds
PeachPDF.Testsin Release, - builds the TestHarness showcase generator (see below),
- installs a Playwright Chromium browser used by the suite’s browser-backed tests,
- runs
dotnet testwith code-coverage collection, and - on pull requests, enforces the diff-coverage gate.
A documentation-only change shouldn’t pay for the full build/test cycle, but the test job is also a required status check — so it always runs, and the heavy steps above are gated on whether anything under src/** actually changed. When only docs change, those steps are skipped and the job still reports success.
Coverage gate
Pull requests must meet 90% diff coverage — the coverage of the lines the change actually touches, not the whole codebase. This is enforced by diff-cover comparing the change against its base branch, using coverlet output in Cobertura format (configured in src/PeachPDF.Tests/coverlet.runsettings). The HTML coverage report and the diff-coverage report are uploaded as build artifacts on every run, so a shortfall is easy to inspect.
The intent is simple: new and changed code arrives with tests, rather than leaving CI to discover the gap after merge.
Rasterization-based verification (a development practice, not a CI gate)
A test that only asserts on substrings of the PDF content stream (/SMask, Tj, /ShadingType, and the like) is not proof that a feature renders correctly. A token can be fully present while the composed, positioned result is visually broken or blank — an entirely non-functional <mask> implementation once passed every substring test it had. For anything touching PDF graphics state — soft masks, patterns, clip paths, gradients, transparency groups, transforms — the automated suite prefers structural/adjacency assertions (for example, checking that a graphics-state operator and the drawing operator it modifies appear together) rather than a bare token search.
Beyond those automated assertions, the strongest check is to rasterize the output and inspect the pixels — and where transparency, soft-mask, or blend-mode output is involved, to rasterize with two independent renderers, not one:
- PDFium — the engine inside Chrome and Edge, and the stricter, more representative check.
- MuPDF — useful as a second opinion, but unusually lenient about transparency-group conformance: it will happily render content “correctly” that PDFium refuses.
Agreement between both is real evidence; a single lenient render that happens to look right is not. This two-renderer cross-check is not part of the automated CI pipeline — it’s a manual verification step performed during development when a change touches graphics-state output, and it’s a practice we recommend contributors run themselves before submitting such a change. (One way to do it is a short Python script using pymupdf and pypdfium2 to render the same page with each engine and compare the images.)
The showcase harness
src/PeachPDF.TestHarness is a small runnable app that renders a gallery of feature showcases. It serves two purposes: it generates the PDFs behind the Feature Showcase on the documentation site, and it’s a place to visually exercise a new capability. CI builds it on every source change so a break is caught before release day, not on it.
This matters because automated assertions alone have real blind spots. Several genuine rendering bugs — a paint-order regression, a broken soft mask, a gradient spread-method that was silently a no-op at render time — were caught only by looking at a showcase render, not by the token-level tests that were passing at the time.
Benchmarks
src/PeachPDF.Benchmarks is a BenchmarkDotNet project for measuring rendering throughput and catching performance regressions. It isn’t part of the per-PR gate; it’s run deliberately when a change is expected to affect performance.
See also
- Architecture — what each of these tests is verifying: the HTML → DOM → CSS → layout → paint → PDF pipeline.
- CONTRIBUTING.md — exact local commands, testing conventions (layout-property assertions,
RGraphicsrecording mocks), and how to reproduce the coverage gate locally.