preCICE system tests
The tutorials repository hosts cases that need multiple components from the preCICE ecosystem to run. Te system tests automatically run tutorials combining the required components and compare the numerical results to references. While the main purpose is to run complete tests in the continuous integration workflows of preCICE, you can also run these tests on your laptop.
Running
The main workflow for the user is executing the systemtests.py script, which is the same that the GitHub Actions workflow executes. Depending on the options given to the script, it reads in the respective metadata files and generates docker-compose.yaml files that can start a fully-defined coupled simulation. For arguments that are not provided, default values from components.yaml are used.
Manual and nightly runs on GitHub
The System tests (manual/nightly) workflow executes the release test suite nightly and can also be triggered manually.
On the workflow page, click Run workflow. The default values will execute the release test suite using the latest develop branches of every component. If you want to override the version of some component, specify it in the respective field. Commit hashes, branches, and tags are all accepted. Branches and tags will get automatically resolved to their current commit on GitHub before starting any test, and all tests will use the same version of any common component.
The available test suites are found in tests.yaml and common values are:
quickstart,elastic-tube-1d, or any other tutorial (see exceptions)openfoam-adapter,micro-manager,fmi-runner, or similar test cases involving the respective componentpreciceis a subset of cases that cover a range of preCICE featuresreleaseis for all available but some very long or known to fail testsextrais for some longer testsselectedandsystem-tests-devfor some special cases
The Use workflow from is a default option of GitHub Actions that concerns the GHA workflow file itself.
Running from a pull request
Several repositories include a workflow that allows triggering the system tests by adding the trigger-system-tests label to the pull request. The event is triggered only at the moment of adding the label, so you need to remove the label and add it again if needed.
See the system tests workflow in the OpenFOAM adapter for an example.
Running from the GitHub CLI
The GitHub CLI allows triggering workflows of a GitHub project. For example:
gh workflow run system-tests-latest-components.yml -f suites=release
More arguments are available, for example:
gh workflow run system-tests-latest-components.yml -f suites=release -f build_args="PLATFORM:ubuntu2404,PRECICE_REF:develop" -f log_level="DEBUG" --ref=develop
The build_args override the defaults set in tools/tests/components.yaml.
Running locally
To run locally, you will need Docker, Docker Compose, and Python 3.
Navigate into the directory tools/tests/ of the tutorials, make a Python virtual environment, and install the dependencies:
python -m venv .venv && source .venv/bin/activate
python -m pip install -r requirements.txt
To test a certain test-suite defined in tests.yaml, use:
python systemtests.py --suites=quickstart
This will build and connect Docker containers and run tutorials in the runs/ directory in the root of the tutorials.
To clean up at the end, you might want to run a docker system prune -a and remove the runs/ directory to save space.
There are also some auxiliary scripts (run without arguments):
print_test_suites.py: Print all test suites defined intests.yamlprint_case_combinations.py: Print all possible combinations of participants in a tutorial, using itsmetadata.yaml.
Understanding the logs
For a local run, look into the tutorials/runs/ directory, where you will find a time-stamped directory for every test executed.
Each of these directories includes the usual tutorial case files and logs, as well as:
system-tests-build.log: The logs of building the respective components.system-tests-run.log: The logs of running the simulation (intermixed, from all participants).system-tests-compare.log: The logs for the comparison to the reference results.
In addition, in the directories of the cases executed, you can find system-tests-<case>.log files.
Numerical regressions
When the tests fail at the results comparison step, this typically means that there are numerical regressions (unless something wrong went undetected in a previous step). We use fieldcompare to compare all preCICE exports to reference results generated from a previous run. Relevant files:
precice-exports/: The coupling meshes of the test run.reference-results/: The coupling meshes of the reference run, as stored on Git LFS, expanded intoreference-results-unpacked. For test cases using implicit coupling, the reference.tar.gzalso contains the referenceprecice-*-iterations.logfiles.diff-results/: Numerical difference of the results in the two directories (computed withfieldcompare dir --diff precice-exports/ reference/). These are only present on failed comparisons.iterations-logs/: Theprecice-*-iterations.logfiles of the test run. Only present in test cases using implicit coupling. The comparisons to references only take into account the file SHA-256 checksums.
To reproduce the comparison locally, use the same fieldcompare command:
fieldcompare dir precice-exports/ reference-results-unpacked/<case>/ \
--ignore-missing-reference-files \
--ignore-unsupported-file-formats \
-rtol 3e-7
The default relative tolerance (-rtol) is 3e-7. Per-test overrides are possible in tests.yaml (e.g., tolerance: 1e-2). Set skip_compare: true to skip the comparison step and only verify that the build and run steps succeed.
The differences are only shown per file, and there is no global metric or other summary (see related discussion in fieldcompare).
Alternatively, visualize the precice-exports/diff_*.vtu in ParaView.
Re-running from CI artifacts
When a system test fails in CI, download the full artifact:
system_tests_run_<run_id>_<run_attempt>_full
(a smaller _logs archive contains only log files). The archive contains a shared runs/ directory:
runs/
├── tools/ # Dockerfiles and helpers (shared)
└── <tutorial>_<cases>_<timestamp>/ # one folder per system test
├── docker-compose.tutorial.yaml
├── docker-compose.field_compare.yaml # written at build time when compare is configured
├── rerun-system-test.sh
├── system-tests-build.log
├── system-tests-run.log
├── system-tests-compare.log
└── …
To re-run one test locally:
- Extract the zip and keep the
runs/layout (the test folder needs the siblingtools/directory). cdinto the test folder.- Run
./rerun-system-test.sh(orsh rerun-system-test.sh).
The script rebuilds images, runs the tutorial, and (if present) runs fieldcompare with --exit-code-from field-compare, matching the Python runner. Compose paths are relative to the test folder (.. is the parent runs/ directory), so you can move the extracted tree elsewhere on a Linux host with Docker.
docker-compose.field_compare.yaml is written when the test is prepared for Docker build. The replay script fixes common permission issues from extracted archives.
Fieldcompare requires reference results in the artifact. If not already unpacked during the original CI run, unpack them manually first.
Extending
Adding new tests
Tests and test suites are defined in tests.yaml. By convention, every tutorial defines a test suite with the same name as its directory, and several test cases using combinations of the available participants. These test cases are later referenced by other test suites: these are typically the release and the test suites of different tested components.
The available cases are listed in the metadata.yaml of each tutorial. To add a new tutorial case as a test, add it to metadata.yaml and then define a test using it. Include that test in the relevant test suites.
Use the max_time or max_time_windows parameters to restrict the runtime of the test to the first few coupling time windows, to save time. Aim for a runtime of less than a minute (assuming cached components), if possible.
Some tutorials require setup before the simulation (e.g. switching configuration files). Use optional run-before and run-after fields in tests.yaml to run shell commands in the copied tutorial directory after copying and before Docker build (run-before), or after the simulation and before field comparison (run-after). Example:
run-before: ./set-case.sh 1d3d
You will need to define a reference results file. The reference results can and should be generated on GitHub using the Generate reference results (manual) workflow for the respective test suite. You might want to temporarily set the selected test suite for requesting results only for a subset of test cases.
Note that you will need to define the TUTORIALS_REF in the file reference_versions.yaml to match the respective branch. Restore that to develop after that. See a related issue.
The results will be added to a Git LFS, but you will need special push access: just use the aforementioned GitHub Actions workflow, instead.
Adding new components
To add a new component, a few changes are needed:
- In the
components.yaml, add a new component, defining all the parameters that it might need.- Add these parameters into the
reference_versions.yaml. - Add fields for these parameters into the
system-tests-latest-components.ymlworkflow.
- Add these parameters into the
- In the
dockerfiles/<platform>/Dockerfile, define a new stage for building your new component. Use the parameters you defined above.- Defining a
<component>_PRvariable will let you integrate the system tests into the respective component repository.
- Defining a
- In the
component_templates.yaml, define a component template. These are Jinja templates that are used by Docker Compose, and the main detail is how to run a simulation using that component. - Refer to the new component in the
metadata.yamlof some tutorial and define some tests.
Adding new repositories that can trigger the tests
If you want to trigger the system tests from a new repository:
- Add a workflow file to that repository (under
.github/workflows/). Example: OpenFOAM adapter. - Create label as a trigger for workflow (under
Issues->Labels->New label). - Give permissions: In the preCICE organization settings, you need to add the new repository to the action secret
WORKFLOW_DISPATCH_TOKENand to the default actions runner group. Adding the new label directly to the pull request in which you add the workflow should already trigger the system tests.
Implementation details
Each tutorial contains automation scripts (mainly run.sh and clean.sh), as well as metadata (metadata.yaml). The metadata file describes the available cases, how to run them, as well as their dependencies. A central tests.yaml file defines test suites, which execute different combinations of cases. The Python script systemtests.py executes the tests, filter for specific test suites.
Read more about the system tests in the publication System Regression Tests for the preCICE Coupling Ecosystem.
Expand the implementation details...
General architecture
Each tutorial directory contains a metadata file, describing which participants each case directory implements, and how to run it.
A list of tests describes all tests to be executed, grouped by test suites. Each test is a combination of tutorial cases.
Test steps include modifying the tutorial configuration files for the test system, building the Docker containers used by the respective Docker Compose service of each component, and comparing results to reference results using fieldcompare.
Tests are executed by the systemtests.py script, which starts the Docker Compose. This can be executed locally, and it is the same script that GitHub Actions also execute.
The multi-stage Docker build allows building each component separately from the same Dockerfile, while Docker reuses cached layers. The Docker Compose services consider GitHub Actions Cache when building the services, although the cache is currently only updated, but not hit (see https://github.com/precice/tutorials/pull/372#issuecomment-1748335750). For this reason, we are running on a dedicated self-hosted runner.
File structure
Metadata and workflow/script files:
.github/workflows/system-tests.yml: workflow for running the tests, triggered by other workflows (e.g., other repositories)system-tests-latest-components.yml: manual triggering front-end forsystem-tests.yml
flow-over-a-heated-plate/fluid-openfoam/run.sh: describes how to execute the respective case
solid-fenics/solid-openfoam/- …
metadata.yml: describes each case directory (which participant, which component, which script to run, …)
tools/tests/component-templates/: jinja2 templates for Docker Compose services for the componentscalculix-adapter.yamlfenics-adapter.yamlopenfoam-adapter.yaml- …
dockerfiles/- Multi-stage build Dockerfiles that define how to build each component, in a layered approach
docker-compose.template.yaml: Describes how to prepare each test (Docker Compose service template)docker-compose.field_compare.template.yaml: Describes how to compare results with fieldcompare (Docker Compose service template)components.yaml: Declares the available components and their parameters/optionsreference_results.metadata.template: Template for reporting the versions used to generate the reference resultsreference_versions.yaml: List of arguments to use for generating the reference resultstests.yaml: Declares the available tests, grouped in test suites
User-facing tools:
tools/tests/systemtests.py: Executes the system tests, starting Docker Compose services of each required component (after building them), running each test, and comparing the results to reference results.print_test_suites.py: Prints the available tests.print_metadata.py: Prints the metadata of each tutorial that contains ametadata.yamlfile.print_case_combinations.py: Prints all possible combinations of tutorial cases, using themetadata.yamlfiles.build_docker_images.py: Build the Docker images for each testgenerate_reference_results.py: Executes the system tests with the versions defined inreference_versions.yamland generates the reference data archives, with the names described intests.yaml. (should only be used by the CI Pipeline)rerun-system-test.sh: Helper script copied into each run directory so CI artifacts can be replayed locally.
Implementation scripts:
tools/tests/systemtests.py: Main entry pointrequirements.txt: Dependencies (jinja2, pyyaml)metadata_parser/: Reads the YAML files into Python objects (defines the schema)systemtests/: Main implementation classesSystemtest.pySystemtestArguments.pyTestSuite.py
Metadata
Every tutorial contains a file called metadata.yaml describing some important properties of the tutorial. For example:
name: Elastic tube 3D
path: elastic-tube-3d
url: https://precice.org/tutorials-elastic-tube-3d.html
participants:
- Fluid
- Solid
cases:
fluid-openfoam:
participant: Fluid
directory: ./fluid-openfoam
run: ./run.sh
component: openfoam-adapter
solid-calculix:
participant: Solid
directory: ./solid-calculix
run: ./run.sh
component: calculix-adapter
solid-fenics:
participant: Solid
directory: ./solid-fenics
run: ./run.sh
component: fenics-adapter
Description:
name: A human-readable, descriptive namepath: Where the tutorial is located, relative to the tutorials repositoryurl: A web page with more information on the tutorialparticipants: A list of preCICE participants, typically corresponding to different domains of the simulationcases: A list of solver configuration directories. Each element of the list includes:participant: Which participant this solver case can serve asdirectory: Where the case directory is located, relative to the tutorial directoryrun: Command that executes the tutorialcomponent: Component or list of components that this case depends upon (typically an adapter)
Components
The components mentioned in the Metadata are defined in the central components.yaml file. This file also specifies some arguments and their default values. These arguments can be anything, but most often they are version-related information. For example, the version of OpenFOAM used by the openfoam-adapter component, or the component version itself. For example:
openfoam-adapter:
template: component-templates/openfoam-adapter.yaml
build_arguments:
PLATFORM:
default: "ubuntu_2404"
PRECICE_REF:
repository: https://github.com/precice/precice
default: "develop"
PRECICE_PRESET:
default: "production-audit"
TUTORIALS_REF:
repository: https://github.com/precice/tutorials
default: "develop"
OPENFOAM_EXECUTABLE:
default: "openfoam2512"
OPENFOAM_ADAPTER_REF:
repository: https://github.com/precice/openfoam-adapter
default: "develop"
This openfoam-adapter component has the following attributes:
template: A template for a Docker Compose service of this componentbuild_arguments: Arguments passed to the Docker Compose service (arbitrary)
Naming schema for build_arguments
The following rules apply for the build_arguments:
- A build argument ending in
_REFrefers to a git commit-ish (like a tag or commit) being used to build the image. - The
repositoryparameter of any_REFargument points to the repository where the respective Git reference should be resolved. This is assumed to be consistent across components and the Dockerfile (and could be simplified). - Some workflows set variables ending in
_PR. These specify the GitHub pull request, which provides the above_REFand can be on a fork. - A build argument ending in
_VERSIONrefers to the version of a third-party dependency to use (e.g., DUNE). - All other
build_argumentsare free of rules and up to the container maintainer.
Component templates
Templates for defining a Docker Compose service for each component are available in component-templates/. For example:
image: precice/fenics-adapter:
depends_on:
prepare:
condition: service_completed_successfully
volumes:
- :/runs
command: >
/bin/bash -c "id &&
cd '/runs//' &&
| tee system-tests_.log 2>&1"
This template defines:
image: The base Docker image for this component, including a Git reference (tag), provided to the template as an argument (e.g., by thesystemtests.pyscript).depends_on: Other services this service depends upon, typically a preparation service that fetches all components and tutorials.volumes: Directories mapped between the host and the container. Apart from directories relating to the users and groups, this also defines where to run the cases.command: How to run a case depending on this component, including how and where to redirect any screen output.
Timeouts
The build and the run/compare steps use separate timeouts.
Build: Each component in components.yaml may set a build_timeout (default: 480s (8min)). The build step runs docker compose build once for all participant images, with one wall-clock subprocess timeout. That limit is the maximum build_timeout among the distinct components of the test (so that the slowest component is not cut off too early). You can override the default via the PRECICE_SYSTEMTESTS_BUILD_TIMEOUT environment variable.
Run and compare: Each test in tests.yaml may set a timeout (default: 180s (3min)), which applies to the running and results comparison steps only. You can override the default via the PRECICE_SYSTEMTESTS_TIMEOUT environment variable.
Tests can define a different tolerance in their tests.yaml entry, which applies to the fieldcompare step (relative tolerance, -rtol). The default is 3e-7. Use skip_compare: true to skip fieldcompare entirely.