Skip to content

Increase C++ test coverage - #3377

Merged
Lestropie merged 9 commits into
devfrom
cpp_coverage
Jun 1, 2026
Merged

Lestropie merged 9 commits into
devfrom
cpp_coverage

Conversation

@Lestropie

Copy link
Copy Markdown
Member

With several mass refactorings on dev it would give greater confidence if the coverage of the existing set of tests were greater. With #2786 providing a quantitative report on test coverage, I decided to set a Claude session on parsing from the report those C++ commands that had the least test coverage and writing candidate tests for them, with it explicitly leaving instructions for generating reference regression test data using 3.0.x where necessary. It did a half decent job; certainly less effort to clean up its mistakes than it would have been to do it all entirely manually.

There's a couple of test failures I still get locally, will see if CI exposes any more:

Lestropie added 2 commits May 12, 2026 20:58
Analysed GCC HTML coverage reports to identify 40 C++ commands with
line coverage below 75%. For commands with non-zero existing coverage,
extracted uncovered code paths from individual HTML files and targeted
them with new tests. Created ~105 Bash test scripts across 42 command
directories, exercising diverse option combinations using pre-existing
test data where available. Tests are self-verifying where possible
(mathematical invariants, round-trip transforms, output structure
checks); where not, they are regression tests with REFERENCE comments
indicating how to generate reference data on a stable version. All new
tests are registered in testing/binaries/CMakeLists.txt.

Session prompts:
1. > Analyse the content of GCC code coverage report
   > "mrtrix3-coverage-report/coverage.html". Consider only files in
   > cpp/cmd/. Consider only commands for which line coverage is low
   > (75%). Look at the contents of the 'test_data' repository (found
   > in build_debug/testing/binaries_data/src/BinariesTestData/). For
   > each of the commands in the remaining list, perform this series of
   > steps: 1. If existing command test coverage is non-zero, find the
   > HTML coverage file in directory 'mrtrix3-coverage-report/'
   > corresponding to that command file to discover which code paths are
   > not yet being evaluated during testing. 2. Attempt to construct one
   > or more Bash tests in 'testing/binaries/tests/' that ensure proper
   > execution of those aspects of the command not yet evaluated.
   > Construct multiple tests with different command-line options where
   > appropriate. Use pre-existing test data as command input if
   > possible; if novel input data are required, comment in the Bash
   > test file what input data need to be generated by the user. If
   > possible, construct in such a way that the test verifies 'correct
   > operation'; where this is not possible, instead construct as a
   > regression test, where the user will construct the corresponding
   > output reference data based on the content of the Bash test file
   > using a stable software version. 3. Add all new tests created to
   > 'testing/binaries/CMakeLists.txt'.

Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@Lestropie Lestropie self-assigned this May 29, 2026
Lestropie added 3 commits May 30, 2026 11:53
Augment get_peaks() in cpp/core/math/SH.h so that a Newton-Raphson
iterate is only accepted as a genuine spherical-harmonic peak when it
satisfies criteria beyond angular convergence. The amplitude is now
required to be positive, the local function shape is interrogated to
confirm a true maximum, and the iteration-based convergence handling is
made more robust. The sh2peaks command and its tests are updated to
reflect the refined behaviour, and the redundant PeakSign enum is
removed since only positive-valued peaks are ever of interest.

Session prompts:
1. > In cpp/core/math/SH.h:448, function get_peaks() identifies a peak
   > orientation and amplitude of a spherical harmonic function through
   > Newton-Raphson updates. Currently the only termination criterion is
   > that the angular change on the sphere between two iterations is
   > below some threshold. This is however not necessarily a guarantee
   > that a true "peak" has been found. Propose additional criteria to
   > this function to provide greater confidence about the nature of the
   > direction found. This could include, but should not necessarily be
   > limited to: ensuring that the amplitude is positive; interrogating
   > the derivatives to ensure that the function shape at that location
   > corresponds to a maximum; more robust iteration-based convergence
   > criteria.
2. > implement (1)+(3)+(4)+(5).
3. > Remove enum PeakSign; contextually it is only ever positive-valued
   > peaks that will be of interest.

Generated-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prior changes to make Math::SH::get_peak() more robust necessitated recomputation of the fixel-wise "skew" metric, which is highly sensitive to peak orientation.
@Lestropie
Lestropie marked this pull request as ready for review May 30, 2026 05:02
github-actions[bot]

This comment was marked as outdated.

@Lestropie

Copy link
Copy Markdown
Member Author

The sh2peaks regression turned out to be primarily, but not exclusively, due to the change in seed direction set (#3256). It wasn't just the use of dev rather than master, nor the use of one direction set or the other, there was some kind of more complex interaction going on.

In looking at the differences closely, the first thing I noticed was that on dev, there were peak orientations with no corresponding lobe. It turned out that it was finding orientations where the FOD amplitude was negative, but a local maxima was formed within that negative lobe. From first implementation in MRtrix3, sh2peaks has used a default threshold of -inf; this has not surfaced previously in previous tests but came up here, maybe because you typically need to extract more than 3 peaks to see it.

Rather than just enforce positivity in "peaks", I decided to make the whole peak-finding process a little more robust. I think I've seen it picking up saddle points in the past, so it now does a Hessian check. And there's some mitigation against possible oscillations in the search.

The robustness of the updated implementation has been checked by generating the test data using a very dense seed direction set (10,000), then applying random rotations to the 60-direction set and ensuring that the same set of peaks is always found.

Lestropie added a commit that referenced this pull request May 30, 2026
- clang-tidy review fixes.
- Remove erroneously added page from documentation sidebar.
- Update some newly created tests to conform to #3356.
- Change some test thresholds seeking passes on all platforms.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread cpp/cmd/amp2sh.cpp Outdated
if (get_rician_bias(sh2amp, noise.value()))
break;
for (ssize_t n = 0; n < sh2amp.rows(); ++n)
for (ssize_t n = 0; n < C.sh2amp.rows(); ++n)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "ssize_t" is directly included [misc-include-cleaner]

cpp/cmd/amp2sh.cpp:27:

+ #include <sys/types.h>

Lestropie added a commit that referenced this pull request May 31, 2026
- clang-tidy review fixes.
- Remove erroneously added page from documentation sidebar.
- Update some newly created tests to conform to #3356.
- Change some test thresholds seeking passes on all platforms.
- Ignore what is believed to be a false positive clang-tidy uninitialised value detection in amp2sg.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

Comment thread cpp/cmd/amp2sh.cpp
if (get_rician_bias(sh2amp, noise.value()))
break;
for (ssize_t n = 0; n < sh2amp.rows(); ++n)
for (Eigen::Index n = 0; n < C.sh2amp.rows(); ++n)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "Eigen::Index" is directly included [misc-include-cleaner]

cpp/cmd/amp2sh.cpp:27:

+ #include <Eigen/src/Core/util/Meta.h>

- clang-tidy review fixes.
- Remove erroneously added page from documentation sidebar.
- Update some newly created tests to conform to #3356.
- Change some test thresholds seeking passes on all platforms.
- Ignore what is believed to be a false positive clang-tidy uninitialised value detection in amp2sg.
@Lestropie
Lestropie force-pushed the cpp_coverage branch 2 times, most recently from ddbbe1e to 51d8cbd Compare June 1, 2026 01:07
Despite explicit interventions to prevent Eigen from yielding false positives ("_deps/" in ExcludeHeaderRegex, NOLINTNEXTLINE above relevant MRtrix3 code line, using -isystem rather than -I), the clang-tidy enforcement CI action nevertheless fails due to throwing a false positive result in Eigen code. The relevant checks are here globally disabled in the hope of passing CI. Updating clang-tidy to version 22 in a separate attempt did not resolve.
@Lestropie
Lestropie merged commit ac589ce into dev Jun 1, 2026
6 of 7 checks passed
@Lestropie
Lestropie deleted the cpp_coverage branch June 1, 2026 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant