# Testing ETL This document describes how to build and run the ETL test suite locally, inside Dev Containers, and in CI. ## Table of Contents 1. [Prerequisites](#prerequisites) 2. [Running Tests Locally (`test/run-tests.sh`)](#running-tests-locally) 3. [Syntax Checks (`test/run-syntax-checks.sh`)](#syntax-checks) 4. [Cross-Architecture Testing (`.devcontainer/run-tests.sh`)](#cross-architecture-testing) 5. [Dev Containers for Native Compilers](#dev-containers-for-native-compilers) 6. [CMake Options Reference](#cmake-options-reference) 7. [CI Checks (GitHub Actions)](#ci-checks-github-actions) 8. [Appveyor (Windows / MSVC)](#appveyor-windows--msvc) 9. [Code Coverage](#code-coverage) 10. [Generator Tests (`scripts/generator_test.py`)](#generator-tests) --- ## Prerequisites * **CMake** ≥ 3.10 * **GCC** and/or **Clang** (any version supported by the project) * **Make** or **Ninja** (build backend) * **Docker** (only needed for cross-architecture testing via `.devcontainer/run-tests.sh`) * **QEMU user-mode** (installed automatically inside the cross-arch Docker images) The project is header-only, so there is no library to compile – the build step compiles the test binary `etl_tests` which links against a bundled copy of **UnitTest++**. --- ## Running Tests Locally The main entry point for local testing is **`test/run-tests.sh`**. It iterates over a matrix of compiler / configuration combinations, creates a temporary `build-make` directory for each one, runs CMake + Make + CTest, and reports coloured pass/fail output (also appended to `log.txt`). ### Synopsis ```bash cd test ./run-tests.sh [Optimisation] [Threads] [Sanitizer] [Compiler] [Verbose] ``` | Argument | Values | Default | |---|---|---| | C++ Standard | `11`, `14`, `17`, `20`, `23`, or `all` | *(required)* | | Optimisation | `0`, `1`, `2`, `3` | `0` | | Threads | any positive integer | `4` | | Sanitizer | `s` (enable) / `n` (disable) | `n` (disabled) | | Compiler | `gcc`, `clang` | all compilers | | Verbose | `v` (enable) / `n` (disable) | `n` (disabled) | ### Examples ```bash # Run all C++17 tests with GCC only, optimisation -O0, 8 threads ./run-tests.sh 17 0 8 n gcc # Run every standard with both compilers, sanitizers enabled, verbose ./run-tests.sh all 0 4 s "" v ``` ### What the script does For every selected C++ standard the script loops over a built-in list of configurations (STL / No STL / Force C++03 / Non-virtual messages / …) for each selected compiler. For every combination it: 1. Creates a fresh `build-make` directory inside the configuration's source subdirectory. 2. Invokes `cmake` with the appropriate `-D` flags (see [CMake Options Reference](#cmake-options-reference)). 3. Builds via `cmake --build .` (parallel level controlled by `CMAKE_BUILD_PARALLEL_LEVEL`). 4. Runs `ctest -V`. 5. Reports success or failure and removes the build directory. The script exits immediately on the first compilation or test failure. ### Test configurations exercised | Compiler | Configuration | |---|---| | GCC | STL | | GCC | STL – Non-virtual messages | | GCC | STL – Force C++03 | | GCC | No STL | | GCC | No STL – Force C++03 | | GCC | No STL – Builtin mem functions | | Clang | STL | | Clang | STL – Force C++03 | | Clang | No STL | | Clang | No STL – Force C++03 | | Clang | No STL – Builtin mem functions | | GCC / Clang | Initializer list test | | GCC / Clang | Error macros – log_errors, exceptions, log_errors_and_exceptions, assert_function | --- ## Syntax Checks The script **`test/run-syntax-checks.sh`** performs compilation-only syntax checks across multiple C++ standards and configurations. Unlike `run-tests.sh`, it **does not run the test binary** – it only verifies that the code compiles successfully. This is useful for quickly validating that header changes do not introduce compilation errors across the supported standard/configuration matrix. ### Synopsis ```bash cd test ./run-syntax-checks.sh [Threads] [Compiler] ``` | Argument | Values | Default | |---|---|---| | C++ Standard | `03`, `11`, `14`, `17`, `20`, `23`, or `a` (all) | *(required)* | | Threads | any positive integer | `4` | | Compiler | `gcc`, `clang` | all compilers | ### Examples ```bash # Check C++17 syntax with GCC only, using 8 threads ./run-syntax-checks.sh 17 8 gcc # Check all standards with both compilers ./run-syntax-checks.sh a ``` ### What the script does The script operates from the `test/syntax_check` directory and iterates over the selected C++ standard(s). For each standard and compiler combination it: 1. Creates fresh build directories (`bgcc` / `bclang`). 2. Invokes `cmake` with the appropriate `-D` flags for the configuration. 3. Builds via `cmake --build`. 4. Reports compilation success or failure (logged to `log.txt`). The script exits immediately on the first compilation failure. ### Configurations checked per standard For each C++ standard the following configurations are compiled: | Compiler | Configuration | |---|---| | GCC | STL | | GCC | No STL | | GCC | STL – Built-in traits | | GCC | No STL – Built-in traits | | Clang | STL | | Clang | No STL | | Clang | STL – Built-in traits | | Clang | No STL – Built-in traits | --- ## Cross-Architecture Testing **`.devcontainer/run-tests.sh`** builds and runs the test suite for non-x86_64 architectures using Docker and QEMU user-mode emulation. It is designed to be run **from the project root**. ### Supported architectures | Argument | Target | Endianness | QEMU binary | |---|---|---|---| | `armhf` | ARM hard-float (32-bit) | Little | `qemu-arm-static` | | `i386` | x86 32-bit | Little | `qemu-i386-static` | | `powerpc` | PowerPC 32-bit | Big | `qemu-ppc` | | `riscv64` | RISC-V 64-bit | Little | `qemu-riscv64-static` | | `s390x` | IBM Z (64-bit) | Big | `qemu-s390x-static` | ### Synopsis ```bash # From the project root .devcontainer/run-tests.sh ``` ### How it works The script has two phases controlled by a second (internal) argument: 1. **Outside the container** (no second argument): * Builds a Docker image from `.devcontainer//Dockerfile`. * Starts a container, bind-mounting the project at `/workspaces/etl`. * Re-invokes itself *inside* the container with the `inside_container` flag. 2. **Inside the container** (`inside_container`): * Creates `build-` and runs CMake with the appropriate cross- compilation toolchain file (`.devcontainer//toolchain-.cmake`). * Builds with `cmake --build .` using all available cores. * Runs the test suite via `ctest --output-on-failure`. The toolchain files set `CMAKE_CROSSCOMPILING_EMULATOR` so that CTest can run the binary transparently through QEMU. ### Example ```bash # Build & run the armhf test suite .devcontainer/run-tests.sh armhf ``` The cross-arch containers build with the following fixed settings: * C++23, No STL, sanitizer off, optimisation -O0. --- ## Dev Containers for Native Compilers The `.devcontainer/` directory also provides Dev Container definitions for a wide range of **native** (x86_64) compiler versions. These are intended for use with **VS Code Dev Containers** or **GitHub Codespaces**. | Directory | Compiler | |---|---| | `gcc09` – `gcc15` | GCC 9 through 15 | | `clang7` – `clang21` | Clang 7 through 21 | Each subdirectory contains a `devcontainer.json` that references the shared `Dockerfile` (`.devcontainer/Dockerfile`) and passes the appropriate base Docker image via the `BASE_IMAGE_NAME` build argument (e.g. `gcc:15`). The default Dev Container (`.devcontainer/devcontainer.json`) uses the Microsoft C++ dev-container base image. To use one of these containers: 1. Open the repository in VS Code. 2. **Ctrl+Shift+P → Dev Containers: Reopen in Container** and select the desired configuration (e.g. *Gcc 15*). 3. Use `test/run-tests.sh` inside the container as described above. --- ## CMake Options Reference When invoking CMake for the test suite (source directory is `test/`), the following `-D` options control the build: | Option | Type | Description | |---|---|---| | `BUILD_TESTS` | `BOOL` | Must be `ON` to compile the test binary. | | `NO_STL` | `BOOL` | Build without the C++ Standard Library. | | `ETL_CXX_STANDARD` | `STRING` | C++ standard: `11`, `14`, `17`, `20`, `23`. | | `ETL_OPTIMISATION` | `STRING` | Compiler optimisation flag, e.g. `-O0`. | | `ETL_ENABLE_SANITIZER` | `BOOL` | Enable address / undefined-behaviour sanitizers. | | `ETL_USE_TYPE_TRAITS_BUILTINS` | `BOOL` | Use compiler built-in type traits. | | `ETL_USER_DEFINED_TYPE_TRAITS` | `BOOL` | Use user-defined type traits. | | `ETL_FORCE_TEST_CPP03_IMPLEMENTATION` | `BOOL` | Force the C++03 code paths even on newer standards. | | `ETL_MESSAGES_ARE_NOT_VIRTUAL` | `BOOL` | Use non-virtual message types. | | `ETL_USE_BUILTIN_MEM_FUNCTIONS` | `BOOL` | Use built-in memory functions in No-STL mode. | | `CMAKE_TOOLCHAIN_FILE` | `PATH` | Toolchain file for cross-compilation. | ### Minimal manual build example ```bash cd test mkdir build && cd build cmake -DBUILD_TESTS=ON -DNO_STL=OFF -DETL_CXX_STANDARD=20 .. cmake --build . -j$(nproc) ctest -V ``` --- ## CI Checks (GitHub Actions) Every push or pull request to `master`, `development`, or `pull-request/*` branches triggers a comprehensive set of GitHub Actions workflows defined in `.github/workflows/`. ### Workflow matrix | Workflow file | Compiler | Standard | Notes | |---|---|---|---| | `gcc-c++11.yml` | GCC | C++11 | STL, No STL, Force C++03 | | `gcc-c++14.yml` | GCC | C++14 | STL, No STL, Force C++03 | | `gcc-c++17.yml` | GCC | C++17 | STL, No STL, Force C++03 | | `gcc-c++20.yml` | GCC | C++20 | STL, No STL, Force C++03 | | `gcc-c++23.yml` | GCC | C++23 | STL, No STL, Force C++03 | | `clang-c++11.yml` | Clang | C++11 | STL, No STL, Force C++03 | | `clang-c++14.yml` | Clang | C++14 | STL, No STL, Force C++03 | | `clang-c++17.yml` | Clang | C++17 | STL, No STL, Force C++03 | | `clang-c++20.yml` | Clang | C++20 | STL, No STL, Force C++03 | | `clang-c++23.yml` | Clang | C++23 | STL, No STL, Force C++03 | | `gcc-syntax-checks.yml` | GCC | C++03 – C++23 | Compilation-only syntax checks (no tests run) | | `clang-syntax-checks.yml` | Clang | C++03 – C++23 | Compilation-only syntax checks (no tests run) | | `msvc.yml` | MSVC 2022 | C++17 | Windows, STL & No STL | | `gcc-c++23-armhf.yml` | GCC cross | C++23 | armhf via QEMU | | `gcc-c++23-i386.yml` | GCC cross | C++23 | i386 via QEMU | | `gcc-c++23-powerpc.yml` | GCC cross | C++23 | powerpc via QEMU | | `gcc-c++23-riscv64.yml` | GCC cross | C++23 | RISC-V 64 via QEMU | | `gcc-c++23-s390x.yml` | GCC cross | C++23 | s390x via QEMU | | `coverage.yml` | GCC | — | Generates lcov coverage report, deploys to GitHub Pages | | `generator.yml` | — | — | Runs the code generator | | `platformio-update.yml` | — | — | PlatformIO registry update | ### Typical CI job structure Each compiler/standard workflow follows the same pattern: 1. **Checkout** – `actions/checkout@v4`. 2. **Build** – set `CC`/`CXX`, call `cmake` with the appropriate `-D` flags, then `make -j`. 3. **Run tests** – execute `./test/etl_tests -v` (or `ctest -V` for cross- arch jobs). The cross-architecture CI jobs additionally install a cross-compiler toolchain and QEMU inside a `debian:trixie` container, use the matching toolchain file from `.devcontainer//`, and run tests via CTest (which delegates to QEMU through `CMAKE_CROSSCOMPILING_EMULATOR`). ### Branches tested * `master` * `development` * `pull-request/*` All workflows run on both `push` and `pull_request` events (types: opened, synchronize, reopened). --- ## Appveyor (Windows / MSVC) The `appveyor.yml` at the repository root provides additional Windows CI using **Visual Studio 2022**. It builds the `master` branch only. Configurations tested: * Debug MSVC C++14 * Debug MSVC C++14 – No STL * Debug MSVC C++17 * Debug MSVC C++17 – No STL * Debug MSVC C++20 * Debug MSVC C++20 – No STL The build uses the VS 2022 solution file at `test/vs2022/etl.vcxproj`. --- ## Code Coverage The `coverage.yml` GitHub Actions workflow generates an **lcov** coverage report: 1. Runs `test/run-coverage.sh` which builds and tests with GCC coverage flags. 2. Uploads the HTML report as a build artifact (retained for 30 days). 3. On pushes to `master`, deploys the report to **GitHub Pages**. To generate coverage locally: ```bash cd test ./run-coverage.sh # Open test/build-coverage/coverage/index.html ``` --- ## Generator Tests The script **`scripts/generator_test.py`** verifies that the code generators in `include/etl/generators/` produce output matching the checked-in header files in `include/etl/private/`. ETL uses [Cog](https://nedbatchelder.com/code/cog/) to generate certain repetitive header files (e.g. `delegate.h`, `message_packet.h`). The generator templates live in `include/etl/generators/*_generator.h` and the generated output is committed to `include/etl/private/*.h`. This test ensures the two stay in sync. ### Prerequisites * **Python 3** * **cogapp** – install via `pip install cogapp` ### Synopsis ```bash python3 scripts/generator_test.py ``` ### What the script does 1. Iterates over every `*_generator.h` file in `include/etl/generators/`. 2. Runs Cog on each generator, outputting to `build/generator_tmp/`. 3. Compares each generated file against the corresponding file in `include/etl/private/`. 4. Reports success if all files match, or failure if any differ. The script exits with code `0` on success and `1` on failure. ### CI integration The `generator.yml` GitHub Actions workflow runs this script automatically on pushes and pull requests to verify generator consistency.