Testing
PETGEM ships a pytest suite under tests/,
covering the forward kernel (fm.csem). The inverse kernel is out of
scope for the suite. The full rationale is in tests/README.md.
The tests never modify the production sources: the C harnesses link against the
unchanged src/ translation units, and the end-to-end tests drive the built
binary.
Structure
Six levels of increasing scope, split so the finite-element core is checked at every order cheaply, while the expensive full-pipeline runs are limited to the orders that exercise new code paths.
Level |
What it verifies |
Orders |
How |
|---|---|---|---|
1 |
Basis functions |
1-6 |
C harness against |
2 |
DOF ordering |
1-6 |
C harness against |
3 |
Element matrices |
1-6 |
C harness against |
4 |
Global assembly |
1-3 |
|
5 |
Basic physics |
1-3 |
|
6 |
Order of accuracy (MMS) |
1, 3, 6 |
|
Levels 1-3 (tests/unit/) are fast and need PETSc and mpicc, but no
built binary. Levels 4-5 (tests/e2e/) run the assemble → solve →
interpolate pipeline on examples/unit_cube. Orders 1, 2, 3 are enough there
because they activate every DOF entity class - order 1 has edge DOFs only,
order 2 adds face DOFs, order 3 adds interior DOFs; higher orders only add more
DOFs of the same classes, already covered by levels 1-3.
Level 6 (tests/mms/) is the order-of-accuracy check. It runs fm.csem
-mms on the \([0,1]^3\) cube with a manufactured solution (see
Numerical formulation), and compares the reported relative \(L^2\) and
\(H(\mathrm{curl})\) errors and the DOF count against
tests/mms/reference/mms_golden.json. Its bundle is self-contained and built
on demand by tests/mms/make_bundle.sh.
Markers, declared in pytest.ini:
e2e- needs the built binary and theunit_cubedataset; skipped automatically when the binary is unavailable.slow- the full-pipeline family (levels 4-5).
testpaths is tests/unit tests/e2e, so a bare pytest does not collect
level 6; run it explicitly.
Running
# Levels 1-3 - all orders 1..6, fast (needs PETSc + mpicc; no binary)
pytest tests/unit
# Levels 4-5 - orders 1..3 (needs the fm.csem binary)
PETGEM_FM_CSEM=build/fm.csem pytest tests/e2e
# ... with 4 MPI tasks for the solves
PETGEM_FM_CSEM=build/fm.csem FM_CSEM_NP=4 pytest tests/e2e
# Level 6 - MMS (needs the binary + gmsh; build the N=4 bundle first)
bash tests/mms/make_bundle.sh 4
PETGEM_FM_CSEM=build/fm.csem FM_CSEM_NP=2 pytest tests/mms
Environment variables:
PETGEM_FM_CSEM- path to thefm.csembinary. Without it, the tests that need a binary skip.FM_CSEM_NP- number of MPI ranks for the kernel runs.
Fixtures skip cleanly when the PETSc toolchain (levels 1-3) or the binary (levels 4-6) is unavailable, so a partial environment still runs whatever it can.
Determinism and tolerances
Levels 1-3 assert analytic invariants (DOF-count closed forms, H(curl) constant-vector reproduction, partition of unity, symmetry of the element matrices, and the de Rham identity \(K_e G_e = 0\)) to about
1e-9.Level 5 compares a direct LU (MUMPS) solve against committed exact-LU golden responses to a relative tolerance of
1e-3. A direct solve is deterministic and always succeeds, so the check is stable across rank counts and does not depend on iterative-solver convergence.Level 6 compares the MMS error norms against the golden with a combined
atol + rtol*|golden|tolerance (rtol=1e-3,atol=1e-8), and requires the backward residual \(\|Ax-b\|/\|b\|\) to stay below1e-10.
Golden references are exact serial LU solves of the current code; regenerate
them only after an intentional change to the forward result. The commands are in
tests/README.md and examples/unit_cube/README.md.
Continuous integration
.github/workflows/ci-develop.yml compiles fm.csem and calls the reusable
tests-fm-csem.yml workflow, which runs the suite (levels 1-3; levels 4-5 and
6 against the compiled binary) plus an Extrae smoke run, inside the project’s CI
image. Code and documentation jobs are gated independently, so a docs-only
change skips the kernel build.