Three layers of tests live in this repo, in increasing scope:
- Unit-style:
apps/library_example/validate_codegen.shinvokes one binary with one example and gates against the matchingbaseline/<example>_serial/. - Backend matrix: four configure-time variants (cpu / gpu / mpi /
mpi+gpu) of the same binary family, registered as CTest tests so
ctest -jruns them all. - CI smoke:
.github/workflows/poisson2d-cpu.ymlruns a single hand-written-model example end-to-end on a fresh Ubuntu runner. Currently disabled — see § CI status.
This doc covers all three.
bash apps/library_example/validate_codegen.sh \
[--variant gpu|mpi|mpi_gpu] \
[--np N] \
[--build <build-dir>] \
[--facade] \
[--quick] \
[--no-regen] \
[<example> ...]| flag | default | meaning |
|---|---|---|
--variant |
none → bin suffix "" (cpu) |
gpu/mpi/mpi_gpu → adds matching suffix |
--np |
1 |
rank count for the mpi* variants |
--build |
./build |
directory holding the binary |
--facade |
off (uses _codegen family) |
switches binary stem to _facade (the embedded library API path) |
--quick |
off (full 12-example matrix) | runs the small fast subset (poisson2d, periodic, naca0012steady) |
--no-regen |
off (re-runs regenerate.sh) |
skip text2code regen — caller must have done it already |
<example>... |
all 12 codegen examples | pass one or more to validate a subset |
What it does, per example:
- (Unless
--no-regen) runsregenerate.sh <example>to make suregrid.bin,pdemodel.txt,datain/, andmy_model.hppare present and consistent with the source app. - Sets up a per-gate
dataout_<stem><variant>/directory (so codegen and facade gates for the same example can run in parallel without clobbering each other's output). - For MPI variants: rewrites
pdeapp.txtsogendatain=0, forcing the binary to callParallelPreprocessingat runtime (the committedpdeapp.txthasgendatain=1for serial-fast paths). - Runs the binary; if it produces no output files, fail.
- Compares against the baseline using the comparison priority below.
Output: one line per example, [ OK ] <name> or [FAIL] <name>.
Exit 0 if every gate passed; non-zero otherwise.
# Single example, single variant:
bash apps/library_example/validate_codegen.sh poisson2d
bash apps/library_example/validate_codegen.sh --variant gpu --build build_gpu poisson2d
# Full matrix (legacy codegen path):
bash apps/library_example/validate_codegen.sh --build build_cpu
bash apps/library_example/validate_codegen.sh --variant gpu --build build_gpu
bash apps/library_example/validate_codegen.sh --variant mpi --np 2 --build build_mpi
bash apps/library_example/validate_codegen.sh --variant mpi_gpu --np 2 --build build_mpi_gpu
# Same matrix, but through the ExasimSolver façade:
bash apps/library_example/validate_codegen.sh --facade --build build_cpu
bash apps/library_example/validate_codegen.sh --facade --variant gpu --build build_gpu
# Inner-loop iteration — small fast subset:
bash apps/library_example/validate_codegen.sh --quickThe harness chooses one of four comparison strategies, in order of preference, based on which artifacts the baseline contains:
- Element-L2 (preferred). If both baseline and current dataout
contain
outelemid_np<r>.binsidecars, aggregateoutudgelement-by-element across ranks and compute a relative L2 norm. Partition-invariant and FP-order-tolerant. Threshold:< 1e-3. Seebaselines.mdfor details. - outqoi.txt (good fallback for variant invariance). Domain or
boundary integrals are partition-invariant and FP-stable to ~1e-8;
compare byte-identical first, then numerically with rel-diff
threshold
< 1e-4per cell. - bin md5 / numerical RMS (serial-only fallback). md5-compare
outudg_np0.binand friends; if the digest doesn't match, fall back to per-file rel-RMS< 1e-6. Sensitive to FP order — fragile across platforms. - Ran-cleanly (last resort, MPI variants without QoI baseline).
Verify each rank in
[0, NP)produced itsoutudg/outuhat_np<r>.bin. Catches binary aborts and missing SaveSolutions calls; doesn't gate numerics.
The element-L2 path is the architectural answer to bin-md5
fragility. See baselines.md for the sidecar format
and how to record a new baseline.
| symptom | usually means |
|---|---|
[SKIP] <name> — <bin> not built |
the corresponding cmake target wasn't built. run cmake --build. |
[SKIP] <name> — <dir> not populated |
regenerate.sh failed; check that text2code is built (build/text2code). |
[SKIP] <name> — no baseline |
new example without a recorded baseline. run a recording pass first. |
[FAIL] <name> — element-L2 = X (>1e-3) |
numerical drift past threshold — bisect against last green commit. magnitude tells you whether it's platform numerics (e-3 to e-7) or a real bug (>1e-2). |
[FAIL] <name> — outqoi.txt differs |
scalar QoI drift past 1e-4. usually a real boundary or source bug. |
[FAIL] <name> — binary produced no dataout/ files |
the binary aborted before SaveSolutions ran. |
| segfault during run | likely a struct-init bug or library version mismatch — gdb -batch -ex run -ex bt it. |
When codegen:<example>:<variant> and facade:<example>:<variant>
both fail with the same magnitude, the divergence is not facade-
specific; both stems are agreeing on the same wrong answer. Most
common cause: platform-numerics drift vs the recorded baseline. If
only one stem fails, suspect a real regression in that path.
Every cmake build dir registers the same gates as CTest tests. The
test names are <family>:<example>:<variant>, e.g.
facade:poisson2d:cpu or codegen:naca0012steady:gpu.
# Run every test for the current build dir's variant:
ctest --test-dir build_cpu --output-on-failure -j16
# Filter by name (glob):
ctest --test-dir build_cpu -R poisson2d
ctest --test-dir build_cpu -R 'facade:.*:cpu'
# Show what would run, don't run:
ctest --test-dir build_cpu -N
# Re-run only the tests that failed last time:
ctest --test-dir build_cpu --rerun-failedThe tests parallelize cleanly with -j8 because text2code's output
goes to per-example dirs, so different examples never race on
backend/Model/libpdemodel*.{so,dylib}. The codegen and facade
gates for the same example also run in parallel because each writes
to its own per-stem dataout_<stem><variant>/ directory.
The TIMEOUT is 1800s per gate (large NS meshes like cone or sharpb2 under -j8 contention can take 10+ minutes per Newton+GMRES solve).
The set of test names a build dir registers depends on which backend variant cmake was configured with:
| build dir | configure flags | tests registered |
|---|---|---|
build_cpu/ |
EXASIM_NOMPI=ON, EXASIM_CUDA=OFF |
*:*:cpu |
build_gpu/ |
EXASIM_NOMPI=ON, EXASIM_CUDA=ON |
*:*:gpu |
build_mpi/ |
EXASIM_MPI=ON, EXASIM_NOMPI=OFF |
*:*:mpi (np=2) |
build_mpi_gpu/ |
EXASIM_MPI=ON, EXASIM_CUDA=ON |
*:*:mpi_gpu (np=2) |
For each example × variant, two tests are registered:
codegen:<ex>:<variant> (legacy <exasim/run.hpp> driver) and
facade:<ex>:<variant> (embedded ExasimSolver<M> driver).
12 examples × 2 families = 24 gates per build dir, plus per-example
regen and per-target build fixtures.
_build_text2code (FIXTURES_SETUP text2code)
↓
_regen_<example> (FIXTURES_SETUP regen_<example>)
↓
_build_<example>_<binary> (FIXTURES_SETUP build_<example>_<binary>)
↓
<family>:<example>:<variant> (FIXTURES_REQUIRED both above)
Each gate runs validate_codegen.sh --no-regen <example> so the
gate itself only runs the binary and diffs.
- Drop a
<name>_codegen/directory underapps/library_example/withpdeapp.txt,main.cpp, and (committed alongside the source app)regenerate.sh-compatible inputs. - Add
add_codegen_example(<name>)toinstall/CMakeLists.txt. - Add
<name>to the_codegen_exampleslist at the bottom of the same file (the CTest registration loop). - Record the baseline:
cmake --build build_cpu --target <name>_codegen bash apps/library_example/validate_codegen.sh <name> # FAIL — no baseline cp apps/library_example/<name>_codegen/dataout_codegen/outudg_np0.bin \ apps/library_example/<name>_codegen/dataout_codegen/outelemid_np0.bin \ baseline/<name>_serial/ # If the model defines QoIs, also copy outqoi.txt: cp apps/library_example/<name>_codegen/dataout_codegen/outqoi.txt \ baseline/<name>_serial/ # optional but partition-invariant bash apps/library_example/validate_codegen.sh <name> # OK
See baselines.md for the full sidecar format.
.github/workflows/poisson2d-cpu.yml is the lone CI workflow. It
builds Kokkos serial + SymEngine + METIS + text2code + Exasim CPU
from source on a fresh Ubuntu 22.04 runner and runs poisson2d_template
as a smoke. Heavy deps are cached by source SHA.
Status: disabled. The GitHub-hosted runner has been flaky during
the SymEngine + Kokkos build (intermittent OOM and unexplained
hangs). The workflow_dispatch trigger is preserved so it can be
invoked manually for diagnosis, and the job is gated by if: false.
Steps to re-enable once the runner is sorted out:
- Open
.github/workflows/poisson2d-cpu.yml. - Remove the
if: falseline on thebuild-and-runjob. - Restore the
push:andpull_request:triggers (currently commented out). - Push — CI fires on the next commit.
A larger CI matrix (gpu / mpi / mpi+gpu via self-hosted runners or container images) is queued but not yet built — the immediate priority is stabilizing the existing CPU smoke first.
Inner-loop iteration (a few seconds per cycle):
# Edit a backend file …
# Build the affected target(s):
cmake --build build_cpu --target poisson2d_codegen poisson2d_facade
# Validate just that example:
bash apps/library_example/validate_codegen.sh poisson2d
bash apps/library_example/validate_codegen.sh --facade poisson2d
# … or via ctest:
ctest --test-dir build_cpu -R poisson2d --output-on-failureQuick cross-section (~30s on Mac):
bash apps/library_example/validate_codegen.sh --quickWhen a regression is suspected:
# Bisect candidate range:
git bisect start HEAD <good-sha>
git bisect run bash apps/library_example/validate_codegen.sh --facade poisson2dCross-arch validation (the four-build layout):
# Build all four variants in parallel:
PATH=/usr/local/cuda/bin:/path/to/openmpi/bin:$PATH \
cmake --build build_cpu -j16 &
PATH=/usr/local/cuda/bin:/path/to/openmpi/bin:$PATH \
cmake --build build_gpu -j8 &
PATH=/usr/local/cuda/bin:/path/to/openmpi/bin:$PATH \
cmake --build build_mpi -j16 &
PATH=/usr/local/cuda/bin:/path/to/openmpi/bin:$PATH \
cmake --build build_mpi_gpu -j8 &
wait
# Validate each:
ctest --test-dir build_cpu -j 8
ctest --test-dir build_gpu -j 8
ctest --test-dir build_mpi -j 8
ctest --test-dir build_mpi_gpu -j 8The four ctests must run sequentially because they share
apps/library_example/<name>_codegen/ directories for text2code
regen.