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:

  1. restores and builds PeachPDF.Tests in Release,
  2. builds the TestHarness showcase generator (see below),
  3. installs a Playwright Chromium browser used by the suite’s browser-backed tests,
  4. runs dotnet test with code-coverage collection, and
  5. 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:

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