Tests
SLUG has an extensive unit test suite covering both the C++ core and the
Python slugpy package. Tests are built and run through CTest, CMake’s
test driver, and are organized into subdirectories that largely mirror the
source tree. This page describes how to run the tests, the major pieces of
the testing architecture, and the automated checks that run against every
pull request.
Running the tests
Building the project (see Configuring and Building) also builds one test
executable per subdirectory of tests. Running ctest from the build
directory executes all of them, along with two Python-based entries
(test_PythonBindings and test_Slugpy, described below):
cd build
ctest
The Python-based tests require pytest, numpy, h5py,
tomlkit, astropy, and scipy to be installed for whichever
Python interpreter CMake picked up at configure time.
To run just the tests for a single area of the code, use CTest’s -R
(regex) filter, or invoke the corresponding test executable directly, e.g.:
ctest -R test_PDFs
# or, equivalently:
./slugTestPDFs
This is almost always preferable to running the whole suite while iterating on a change – see the note below on quick vs. slow tests.
Quick vs. slow tests
Every test is labelled in CMakeLists.txt as either quick or
slow. The quick tests each run in a few seconds; the slow ones
(currently just test_CoreFull, the full-scale end-to-end counterpart to
test_Core) drive real simulations against large data files (full MIST
tracks, POWR/TLUSTY/BOSZ/CK04 spectral libraries) that are gitignored
rather than committed to the repository, and can take upwards of a minute
to run even when that data is present. When it isn’t, the slow tests
self-skip rather than fail.
Use CTest’s label filter to select one group or the other:
ctest -L quick # fast tests only
ctest -LE slow # equivalently, everything except the slow tests
ctest -L slow # just the slow, full-scale tests
Plain ctest with no label filter still runs everything, quick and slow
alike. As a rule of thumb: run the quick tests for the areas you touched
routinely while developing, and reserve the slow tests for cases where
you’ve changed something likely to affect a full end-to-end run (e.g. the
simulation drivers in src/core, or the data files a real run depends
on).
Test suite layout
The tests directory’s subdirectory structure mirrors the first-party
C++ modules under src, with one test executable per subdirectory (see
AGENTS.md for the full convention, including the one exception:
src/extern, vendored third-party code, has no corresponding
tests/extern). Subdirectories may contain their own assets
directory holding data files the tests need; some of these are generated
by scripts in data/tools (e.g. make_powr_test_fixture.py).
The C++ test executables are:
tests/interpolation– the grid interpolation classes (src/interpolation).tests/elem– element/isotope data handling (src/elem).tests/extinct– extinction curves and their application (src/extinct).tests/phot– photometric filters and magnitude conversions (src/phot).tests/pdfs– the probability distribution classes used for the IMF and other sampled quantities (src/pdfs).tests/utils– assorted utility functions (src/utils).tests/tracks– stellar evolutionary track classes (src/tracks).tests/specsyn– spectral synthesis (src/specsyn).tests/nebular– nebular emission processing (src/nebular).tests/io– output and simulation-control I/O (src/io).tests/core– the simulation drivers (clusters, galaxies, and their combination with spectral synthesis) insrc/core. This directory holds two executables:slugTestCore, fast tests that run on every CI build, andslugTestCoreFull, the slow, full-scale end-to-end tests described above.
Two further entries run Python tests via pytest, rather than being
their own compiled executables:
tests/pybind(test_PythonBindings) – exercises the compiled_slugextension module directly, against the same interpreter pybind11 built it for.tests/slugpy(test_Slugpy) – pure-Python tests of theslugpypackage itself (readers, post-processing utilities, and itscloudysubpackage for building/parsing Cloudy input and output). Since these are pure Python, they need no compiled extension, and since they exerciseslugpyagainst a small real output file committed to the repository (examples/clusterlib/clusterlib.h5) rather than a full simulation run, they stay in thequickgroup.
Continuous integration
Every pull request triggers a GitHub Actions workflow (.github/workflows/ci.yml)
with four jobs:
test – builds SLUG and runs
ctest --output-on-failure -L quickacross a matrix of four configurations: GCC and Clang, each on Ubuntu and macOS. Only the quick tests run in CI, for the same reason they self-skip locally without the necessary data: the large track/spectral-library files the slow tests need don’t fit in the repository.lint – runs
clang-tidyon the C++ source and header files changed relative to the PR’s base branch (excluding vendored code undersrc/extern), using the exactclang-tidyversion CI uses.pyright – type-checks the
slugpyandtests/slugpyPython files changed relative to the base branch. This requires the compiled_slugextension to be built first, so thatslugpy’sfrom ._slug import ...imports resolve.ruff – runs
ruff checkagainst the same changedslugpy/tests/slugpyfiles.
The clang-tidy, pyright, and ruff jobs all operate only on files
that changed in the PR, not the whole tree, so it’s worth running the
equivalent check locally on your own changed files before pushing (see
AGENTS.md’s Coding style section for the exact commands). data/tools
and tests/pybind are not held to the pyright/ruff standard and
are excluded from those two jobs.
A pull request is expected to have all four CI jobs passing, plus a completed CodeRabbit review, before it is merged.