blob: 95e5647ca1103fce002e2eb2d20f7907515ff6af [file] [view]
# Testing Eigen Changes
Use this guide when adding or changing tests. The checked-out source is authoritative:
- [`test/main.h`](../test/main.h) configures and runs the test framework and aggregates the shared helpers.
- [`test/numerical_test_helpers.h`](../test/numerical_test_helpers.h) defines numerical comparison, assertion, and
tolerance helpers.
- [`test/product_test_helpers.h`](../test/product_test_helpers.h) defines matrix-product error bounds.
- [`test/random_matrix_helper.h`](../test/random_matrix_helper.h) and
[`test/type_test_helpers.h`](../test/type_test_helpers.h) define random-matrix and type utilities.
- [`cmake/EigenTesting.cmake`](../cmake/EigenTesting.cmake) defines test registration and splitting.
- [`test/CMakeLists.txt`](../test/CMakeLists.txt) and
[`contrib/test/CMakeLists.txt`](../contrib/test/CMakeLists.txt) register the suites.
- [`cmake/EigenConfigureTesting.cmake`](../cmake/EigenConfigureTesting.cmake) defines aggregate build and check
targets.
## Configure And Build
Configure a dedicated build directory. Unit tests are excluded from CMake's default `all` target, although a bare
build can still build enabled auxiliary libraries.
```bash
cmake -G Ninja -S . -B build
cmake --build build --target buildtests
ctest --test-dir build --parallel --output-on-failure --no-tests=error
```
Useful aggregate targets are `BuildOfficial`, `BuildContrib`, `buildsmoketests`, `buildtests_gpu`, `check`, and
`check_gpu`. Build and run one test explicitly when possible:
```bash
cmake --build build --target bdcsvd_3
ctest --test-dir build -R '^bdcsvd_3$' --output-on-failure --no-tests=error
```
Run the generated wrappers from the build directory because they invoke the configured build tool relative to their
working directory:
```bash
cd build
./buildtests.sh <regex>
./check.sh <regex>
```
They filter registered parent names such as `bdcsvd`, not generated part names such as `bdcsvd_3`. For one part, use
the explicit target recipe above.
Use a separate build directory for each materially different configuration. Do not rewrite one cache and describe
the result as a second test run.
```bash
cmake -G Ninja -S . -B build-row-major -DEIGEN_DEFAULT_TO_ROW_MAJOR=ON
cmake -G Ninja -S . -B build-no-vector -DEIGEN_TEST_NO_EXPLICIT_VECTORIZATION=ON
```
Consult the top-level [`CMakeLists.txt`](../CMakeLists.txt) and nearby test CMake files for current options instead of
copying an option inventory into documentation.
## Current Test Framework
Eigen currently uses its own framework, not GoogleTest:
1. Add `test/<name>.cpp` or `contrib/test/<name>.cpp`.
2. Include `main.h`, then the public umbrella header for tests of public behavior. A focused test of a private utility
may include its implementation header only when that matches an established nearby pattern. Never present such a
path as a user include.
3. Use `VERIFY`, `VERIFY_IS_EQUAL`, `VERIFY_IS_APPROX`, and the other helpers exposed through `test/main.h`.
4. End with `EIGEN_DECLARE_TEST(<name>) { ... }`.
5. Register the source with `ei_add_test(<name>)` in the matching `CMakeLists.txt`, then reconfigure.
Keep `test/main.h` limited to framework configuration, registration, shared-helper aggregation, and the test driver.
Put reusable utilities in a narrowly named helper header. Include it from `main.h` only when most tests need it.
For compile-failure coverage, use the established `failtest/` pattern. Its `_ok` target must compile and its `_ko`
target must fail with `EIGEN_SHOULD_FAIL_TO_BUILD` defined. Register it with `ei_add_failtest` above the closing
`ei_add_failtest_fixture()` call. The `buildfailtests` fixture builds the whole suite at once. `_ko` passes when its own
target failed to build and `_ok` built. Because `_ok` built, the toolchain works, but the `_ko` failure can still be a
different compile error from the intended one. Keep the failing construct narrow.
## Split Tests
`ei_add_test` scans the source for `CALL_SUBTEST_N`, `EIGEN_TEST_PART_N`, and `EIGEN_SUFFIXES;...` markers.
- With `EIGEN_SPLIT_LARGE_TESTS=ON`, every discovered suffix becomes an executable `<name>_<N>` compiled with
`EIGEN_TEST_PART_<N>=1`; the parent `<name>` target builds all parts.
- `EIGEN_SUFFIXES;...` supplies an explicit suffix list when ordinary source scanning cannot see macro-generated or
conditional parts.
- With splitting off, tests containing only `CALL_SUBTEST_N` or `EIGEN_SUFFIXES` fold into one `<name>` executable
compiled with `EIGEN_TEST_PART_ALL=1`.
- An explicit `EIGEN_TEST_PART_N` marker forces splitting even when the option is off. If any such marker is present,
all suffixes discovered in that source are emitted.
- [`cmake/EigenTestPartGroups.cmake`](../cmake/EigenTestPartGroups.cmake) lists ranges of parts that compile together as
one executable. The executable takes the name of the range's first part, e.g. `array_cwise_1` for `1-4`, and
`ctest -R '<name>'` still selects it. Every part in a range must differ from the others only in its `CALL_SUBTEST_N`
calls, and the smoke list must not name it individually. The range's compile must stay under the 4 GiB peak RSS
recorded in the file header. After adding or renumbering subtests inside a listed range, re-measure that compile,
because the change makes it bigger.
`ctest -R '^<name>$'` does not match split parts. Use `ctest -R '<name>'` to run every part, or anchor a single
generated name such as `'^<name>_3$'`.
After changing subtest registration, reconfigure and read back the generated target list. Two failure modes are silent.
A subtest function whose `CALL_SUBTEST` call was dropped still compiles and looks like coverage. And under
`EIGEN_SPLIT_LARGE_TESTS=ON`, a part invoked only through a dispatch macro is not built unless an `EIGEN_SUFFIXES`
marker lists it.
## Coverage That Can Fail
A test that passes when the change is reverted is not coverage. Establish that the test fails at the parent commit.
When that is impractical, show that the test runs the new code by construction.
- Test a new fast path through the public entry point that selects it, with inputs that actually take it, not only
through a direct call to the new method. Where a flag or trait selects the fast path, pin the selection with a
`STATIC_CHECK` on it in both directions: for a type that must opt in and for one that must stay out.
- Cover the branches the change adds, not just one convenient shape:
- sizes that are not a multiple of the packet or block dimension;
- complex scalars where conjugation is otherwise a no-op;
- both storage orders;
- the uncompressed or strided variants of an input type.
- Verify the complete result against an independent reference, including coefficients the test setup did not write.
Corruption in those coefficients goes unnoticed when the check skips them.
- Exercise the documented customization points, such as custom scalars and functors without declared traits, not only
the built-in specializations that happen to satisfy a new precondition.
## Build-System Tests
[`test/buildsystem`](../test/buildsystem) holds the coverage for Eigen's own CMake surface: what an install tree
contains, what `find_package(Eigen3)` and the version ranges in
[`cmake/Eigen3ConfigVersion.cmake.in`](../cmake/Eigen3ConfigVersion.cmake.in) accept, and how an embedding project
opts out of Eigen's install rules. [`doc/TopicCMakeGuide.dox`](../doc/TopicCMakeGuide.dox) makes these claims to
users, and nothing else checks them: the blocking documentation job builds the docs but does not run what they describe.
Not every scenario checks a documented claim. Others cover CMake behavior that no other test exercises: a find module
that has to survive a second configure of the same build tree, or the wiring that routes a compiler launcher into a
test's compile command.
```bash
cmake -G Ninja -S . -B build -DEIGEN_BUILD_TESTING=ON
cmake -E chdir build ctest -L buildsystem --output-on-failure --no-tests=error
```
Pass `--no-tests=error` to every `ctest` invocation, as this guide does, because CTest otherwise exits 0 when nothing
matched. Nothing matches in three common cases: an anchored `-R '^name$'` against a split test, a mistyped name, and
`--test-dir` under CMake 3.17 to 3.19. Those versions predate `--test-dir`; they ignore it and inspect the source
directory instead. The `cmake -E chdir` form above works on them too.
No target needs building first: each scenario runs its own nested configure, build, and install into the CTest
binary directory. To add a claim, drop a scenario in `scenarios/` and name it in the list in
`test/buildsystem/CMakeLists.txt`. The driver `run_scenario.cmake` supplies the assertion helpers.
Two hazards are specific to these tests. First, Eigen calls `export(PACKAGE Eigen3)`, so CMake's user package registry
names every Eigen build tree on the machine. A `find_package` scenario must therefore disable both the user and the
system package registries and assert that the package came from the prefix it installed; otherwise it passes without
reading that prefix at all. Second, CMake code registers these tests. If a guard around that code stops matching, CTest
finds no tests instead of reporting a failure, so the CI job runs `ctest` with `--no-tests=error`.
## Configurations The Test Suite Cannot See
- In the default host-test configuration, no test compiles an `EIGEN_NO_DEBUG` code path: `test/main.h` undefines
`NDEBUG`, and `Macros.h` derives `EIGEN_NO_DEBUG` from it. (HIP/SYCL device compilation and an explicit
`-DEIGEN_NO_DEBUG` define it independently.) For behavior that depends on the macro, add a dedicated
`-DEIGEN_NO_DEBUG` test target or a standalone `-DNDEBUG` check. Conversely, the compiler type-checks the condition
inside an `eigen_assert` only where assertions are enabled. A condition that calls a member its argument type lacks
still compiles in every release build.
- When layout is in play, run an `EIGEN_DEFAULT_TO_ROW_MAJOR` build. Where a test aliases one object's storage through
a view whose default layout is fixed, pin the layout explicitly.
- When the change reasons about packets, alignment, or index width, cover `EIGEN_TEST_NO_EXPLICIT_VECTORIZATION`,
`EIGEN_UNALIGNED_VECTORIZE=0`, or a narrower `EIGEN_DEFAULT_DENSE_INDEX_TYPE`.
- Tests build optimized (`CMAKE_BUILD_TYPE` defaults to Release), and no CI job builds Debug. In C++14 an odr-used
`static constexpr` member of a class template needs a namespace-scope definition. Without one, every CI build links
and only an -O0 build fails; see [`conventions.md`](conventions.md). When adding such constants, build one Debug tree.
- Compiler fast-math coverage comes only from the targets that `test/CMakeLists.txt` registers with fast-math flags.
The smoke list includes `packetmath_fastmath`, `packetmath_fastmath_generic_16` where vector extensions are
available, `bfloat16_classification_fastmath`, and parts of `fastmath`, `bdcsvd_fastmath`, and
`stable_norm_fastmath`; these compile with `-ffast-math` where supported. Ordinary `packetmath` uses Eigen's
`EIGEN_FAST_MATH=1` approximation switch, which does not enable the compiler flag. When a changed path falls outside
the existing fast-math tests, add focused coverage. [`numerics.md`](numerics.md) records the special-value hazards.
## Numerical Assertions
`VERIFY_IS_APPROX` is a convenient broad comparison, not a machine-epsilon guarantee. `test_precision<T>()` uses
`NumTraits<T>::dummy_precision()` generically and currently specializes float to `1e-3` and double/long double to
`1e-6`. Do not use it alone to claim ULP accuracy, backward stability, or IEEE special-value conformance.
For numerical kernels, add explicit named bounds based on epsilon, dimension, conditioning, or a backward-error
model as appropriate. When their distinction matters, check NaN, infinity, and signed zero explicitly. Follow
[`numerics.md`](numerics.md) for solver, packet, and scalar-math coverage.
Write such a bound as `factor * NumTraits<RealScalar>::epsilon()` at the site, and explain `factor` by its error model.
Do not introduce tolerance wrapper helpers: the raw form is the established idiom across `test/` and `contrib/test/`,
and it shows the computation itself, so a bound like `10 * n * eps * A.norm()` reads as the formula it is. A bare
decimal literal is worse than opaque: `1e-9` demands impossible accuracy from a `float` instantiation.
Two kinds of comparison silently accept everything, and both have shipped here. The first is a tolerance computed by the
operation under test: a bound formed as `(A.cwiseAbs() * B.cwiseAbs())` goes through the product code being tested, so
accumulate it independently instead. The second is a comparison that admits non-finite values: `error <= tolerance`
holds for two infinities, and `if (error > bound)` never fires for a NaN error. Test for failure as the negation of the
passing condition, `!(error <= bound)`. Reject a non-finite tolerance.
When a numerical check fails for some seeds, find out whether the computation or the check is at fault before changing
the tolerance. Compare the results with a reference computed in higher precision (quad or MPFR). Measure the backward
error, and the forward error relative to the first-order condition bound, across enough seeds to see the tail:
- A result worse than its conditioning allows is an accuracy defect. Fix the algorithm; a wider tolerance would hide
it.
- If a result is within that accuracy and the check still fails, the check asks for more than the working precision
can deliver. Derive the tolerance from the conditioning rather than a flat factor.
- Solving the same inputs in the next wider type proves neither. The wider type resolves what the working precision
cannot, such as a tight cluster of roots that the narrower type can only locate to within a wider set.
An accuracy defect and an over-strict check can both be present. In the `polynomialsolver` flake, the companion
eigenvalues had backward errors of 1.8e4 eps. Once every root's backward error was below one eps, the flat 3.16% check
still failed at the same rate. Land such a computation fix and test fix as independent merge requests. In the
computation fix, state whether it removes the failure, with seed sweeps against the parent commit as evidence.
Run reproducible failures directly with a fixed seed and repeat count:
```bash
EIGEN_REPEAT=10 EIGEN_SEED=1 build/test/foo_3
build/test/foo_3 r10 s1
```
## External BLAS And Shim Libraries
`EIGEN_TEST_EXTERNAL_BLAS=ON` finds a system BLAS, defines `EIGEN_USE_BLAS`, and links that BLAS into applicable
official tests. With it off, ordinary tests exercise Eigen's normal implementation; they do not transparently use
the in-tree `eigen_blas` library. `EIGEN_BUILD_BLAS` and `EIGEN_BUILD_LAPACK` separately build Eigen's ABI shim
libraries. Some optional sparse backends also link against those shims. There is currently no
`EIGEN_TEST_EXTERNAL_LAPACK` option.
Report the exact targets, CTest regexes, configurations, compiler, and seeds run. Also report relevant hardware or
optional backends that were unavailable locally.