Skip to content

Add weekly GitHub Action for Python command coverage - #3351

Open
Lestropie wants to merge 6 commits into
masterfrom
python_coverage_master
Open

Add weekly GitHub Action for Python command coverage#3351
Lestropie wants to merge 6 commits into
masterfrom
python_coverage_master

Conversation

@Lestropie

@Lestropie Lestropie commented May 12, 2026

Copy link
Copy Markdown
Member

Completes #2773 in conjunction with #2786.

Comprehensiveness of testing can be improved once #3073 is completed. There, for each external neuroimaging software dependency, a list of only those files required to execute MRtrix3 scripts will be created. Consequently, just as the container build process will install those softwares then strip out all unnecessary content, this workflow could do the same. It might also be possible to cache them just for the Action.

  • Change to execute on Pull Request in order to trigger the Action.
  • Remove GUI from dependencies & build.
  • Resolve content naming consistency between this and Generate weekly coverage report for C++ code #2786.
  • Verify artifact storage on GitHub.
  • Disable execution on Pull Request before merging.
  • Evaluate generation of more exhaustive coverage data.
    • Edit: Deferring novel tests until after third-party dependencies are dealt with.

Introduces .github/workflows/weekly_python_coverage.yml, a scheduled
workflow that builds MRtrix3, runs the full suite of Python script
tests under coverage instrumentation, and uploads an HTML report as a
GitHub artifact. Coverage is collected via the COVERAGE_PROCESS_START
+ sitecustomize.py mechanism so that every Python subprocess spawned by
ctest automatically participates in measurement. FSL and ANTs are
installed from conda-forge to satisfy the majority of external
neuroimaging dependencies; tests requiring AFNI, HD-BET, or SynthStrip
are allowed to fail gracefully, still contributing partial coverage.
The ctest invocation is explicitly serialised (-j 1) because all script
tests share the script_test_data root as their working directory and
write to overlapping temporary paths.

Session prompts:
1. > Implement a GitHub Action that will produce a coverage report for
   > Python commands. It must first build the software using cmake. The
   > tests themselves are typically executed using ctest, specifically
   > those tests to which the "script" label is ascribed. Some of these
   > commands rely on external neuroimaging software, which will need to
   > be installed. Evaluate whether it is preferable to make use of
   > ctest and attach the required Python modules for coverage tracking,
   > or to instead parse the testing structure directly, explicitly
   > downloading the "script_test_data" repository at the relevant
   > commit. The resulting coverage report should be uploaded to GitHub
   > as an artifact. The Action should run weekly.
2. > The Bash tests in testinig/scripts/tests/ are all executed from
   > the root of the "script_test_data" repository, and may delete or
   > overwrite contents of that directory. If this makes it unsafe to
   > execute tests in parallel in this environment, disable parallelism.

Generated-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Lestropie added 2 commits May 12, 2026 11:44
- Trigger on Pull Request for initial evaluation
- Do not install GUI dependencies or build GUI commands
- Rename build directory to reflect release build (debugging symbols for C++ binaries not required in this context)
CI Action fails when trying to set up micromamba for FSL and ANTs. So for now, only get faithful test coverage of commands that have zero third-party neuroimaging dependencies; these can all be added together at a later date.
@Lestropie
Lestropie force-pushed the python_coverage_master branch from 56276f2 to b0bcaa0 Compare May 12, 2026 11:47
@Lestropie

Copy link
Copy Markdown
Member Author
Report generated via Action:
Name                                                                 Stmts   Miss Branch BrPart  Cover
------------------------------------------------------------------------------------------------------
python/mrtrix3/__init__.py                                              33      8     14      2    66%
python/mrtrix3/app.py                                                 1149    487    458     70    54%
python/mrtrix3/commands/5ttgen/5ttgen.py                                22      3      6      1    79%
python/mrtrix3/commands/5ttgen/__init__.py                               1      0      0      0   100%
python/mrtrix3/commands/5ttgen/deep_atropos.py                          44      3     12      3    89%
python/mrtrix3/commands/5ttgen/freesurfer.py                            43     24     14      3    39%
python/mrtrix3/commands/5ttgen/fsl.py                                  142    119     48      2    13%
python/mrtrix3/commands/5ttgen/gif.py                                   28     18      6      0    29%
python/mrtrix3/commands/5ttgen/hsvs.py                                 567    272    176     36    49%
python/mrtrix3/commands/__init__.py                                      4      4      0      0     0%
python/mrtrix3/commands/dwi2mask/3dautomask.py                          54     27     24      1    36%
python/mrtrix3/commands/dwi2mask/__init__.py                             1      0      0      0   100%
python/mrtrix3/commands/dwi2mask/ants.py                                29     11      8      1    51%
python/mrtrix3/commands/dwi2mask/b02template.py                        118     72     40      4    33%
python/mrtrix3/commands/dwi2mask/consensus.py                           83     18     34      8    71%
python/mrtrix3/commands/dwi2mask/dwi2mask.py                            28      1      4      1    94%
python/mrtrix3/commands/dwi2mask/fslbet.py                              41     21     14      1    38%
python/mrtrix3/commands/dwi2mask/hdbet.py                               37     19      6      1    44%
python/mrtrix3/commands/dwi2mask/legacy.py                              13      0      0      0   100%
python/mrtrix3/commands/dwi2mask/mean.py                                15      0      0      0   100%
python/mrtrix3/commands/dwi2mask/mtnorm.py                              59      5     16      4    88%
python/mrtrix3/commands/dwi2mask/synthstrip.py                          43     17     16      2    47%
python/mrtrix3/commands/dwi2mask/trace.py                               59      4     12      2    92%
python/mrtrix3/commands/dwi2response/__init__.py                         1      0      0      0   100%
python/mrtrix3/commands/dwi2response/dhollander.py                     190      8     32      7    93%
python/mrtrix3/commands/dwi2response/dwi2response.py                    58      7     28      7    84%
python/mrtrix3/commands/dwi2response/fa.py                              37      1     10      2    94%
python/mrtrix3/commands/dwi2response/manual.py                          38      6     16      5    80%
python/mrtrix3/commands/dwi2response/msmt_5tt.py                        85     12     32     10    79%
python/mrtrix3/commands/dwi2response/tax.py                             83      4     20      4    92%
python/mrtrix3/commands/dwi2response/tournier.py                        83      1     20      2    97%
python/mrtrix3/commands/dwibiascorrect/__init__.py                       1      0      0      0   100%
python/mrtrix3/commands/dwibiascorrect/ants.py                          38     21     12      1    40%
python/mrtrix3/commands/dwibiascorrect/dwibiascorrect.py                32      4     12      4    82%
python/mrtrix3/commands/dwibiascorrect/fsl.py                           27     11      6      2    55%
python/mrtrix3/commands/dwibiascorrect/mtnorm.py                        52      5     14      4    86%
python/mrtrix3/commands/dwibiasnormmask.py                             243     61     68     22    71%
python/mrtrix3/commands/dwicat.py                                      130     25     66     17    79%
python/mrtrix3/commands/dwifslpreproc.py                               792    705    404      7     8%
python/mrtrix3/commands/dwigradcheck.py                                107     44     52     12    55%
python/mrtrix3/commands/dwinormalise/__init__.py                         1      0      0      0   100%
python/mrtrix3/commands/dwinormalise/dwinormalise.py                    10      0      0      0   100%
python/mrtrix3/commands/dwinormalise/group.py                           66      3     14      3    92%
python/mrtrix3/commands/dwinormalise/manual.py                          27      1      4      1    94%
python/mrtrix3/commands/dwinormalise/mtnorm.py                          72      6     20      5    88%
python/mrtrix3/commands/dwishellmath.py                                 32      2      8      2    90%
python/mrtrix3/commands/for_each.py                                    215     67     80     14    64%
python/mrtrix3/commands/labelsgmfirst.py                                87     65     26      2    21%
python/mrtrix3/commands/mask2glass.py                                   37      3      6      2    88%
python/mrtrix3/commands/mrtrix_cleanup.py                              102     23     40      8    73%
python/mrtrix3/commands/peakscheck.py                                  312    155    122      9    45%
python/mrtrix3/commands/population_template/__init__.py                 17      0      0      0   100%
python/mrtrix3/commands/population_template/contrasts.py                41      5     12      2    79%
python/mrtrix3/commands/population_template/execute.py                 618    143    398     69    73%
python/mrtrix3/commands/population_template/input.py                    61     20     20      5    62%
python/mrtrix3/commands/population_template/population_template.py       6      0      0      0   100%
python/mrtrix3/commands/population_template/usage.py                    72      8      2      1    88%
python/mrtrix3/commands/population_template/utils.py                   199    119    104     19    37%
python/mrtrix3/commands/responsemean.py                                 45      3     16      3    90%
python/mrtrix3/fsl.py                                                  124    116     56      0     4%
python/mrtrix3/image.py                                                187     54     86     24    66%
python/mrtrix3/matrix.py                                               177     62    124     37    57%
python/mrtrix3/path.py                                                 116    101     62      2    11%
python/mrtrix3/phaseencoding.py                                         91     87     64      0     3%
python/mrtrix3/run.py                                                  452    155    202     34    59%
python/mrtrix3/sh.py                                                     7      1      2      1    78%
python/mrtrix3/utils.py                                                 98     47     30      4    49%
python/mrtrix3/version.py                                                2      2      0      0     0%
------------------------------------------------------------------------------------------------------
TOTAL                                                                 7884   3296   3198    495    53%

I'd like to get more detail than just overall per-file statistics. Eg. Knowledge of which lines are missed could direct creation of additional tests.

- Include evaluation of both branches for if statements
- Print line ranges not executed
@Lestropie

Copy link
Copy Markdown
Member Author

OK, that looks much more useful now.

Index page for generated report:
File	 	coverage	statements	missing	excluded	 	coverage	branches	partial	 	coverage
python / mrtrix3 / __init__.py	 	76%	33	8	0	 	43%	14	2	 	66%
python / mrtrix3 / app.py	 	58%	1149	487	0	 	45%	458	70	 	54%
python / mrtrix3 / commands / __init__.py	 	0%	4	4	0	 	100%	0	0	 	0%
python / mrtrix3 / commands / 5ttgen / __init__.py	 	100%	1	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / 5ttgen / 5ttgen.py	 	86%	22	3	0	 	50%	6	1	 	79%
python / mrtrix3 / commands / 5ttgen / deep_atropos.py	 	93%	44	3	0	 	75%	12	3	 	89%
python / mrtrix3 / commands / 5ttgen / freesurfer.py	 	44%	43	24	0	 	21%	14	3	 	39%
python / mrtrix3 / commands / 5ttgen / fsl.py	 	16%	142	119	0	 	4%	48	2	 	13%
python / mrtrix3 / commands / 5ttgen / gif.py	 	36%	28	18	0	 	0%	6	0	 	29%
python / mrtrix3 / commands / 5ttgen / hsvs.py	 	52%	567	272	0	 	40%	176	36	 	49%
python / mrtrix3 / commands / dwi2mask / __init__.py	 	100%	1	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwi2mask / 3dautomask.py	 	50%	54	27	0	 	4%	24	1	 	36%
python / mrtrix3 / commands / dwi2mask / ants.py	 	62%	29	11	0	 	12%	8	1	 	51%
python / mrtrix3 / commands / dwi2mask / b02template.py	 	39%	118	72	0	 	15%	40	4	 	33%
python / mrtrix3 / commands / dwi2mask / consensus.py	 	78%	83	18	0	 	53%	34	8	 	71%
python / mrtrix3 / commands / dwi2mask / dwi2mask.py	 	96%	28	1	0	 	75%	4	1	 	94%
python / mrtrix3 / commands / dwi2mask / fslbet.py	 	49%	41	21	0	 	7%	14	1	 	38%
python / mrtrix3 / commands / dwi2mask / hdbet.py	 	49%	37	19	0	 	17%	6	1	 	44%
python / mrtrix3 / commands / dwi2mask / legacy.py	 	100%	13	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwi2mask / mean.py	 	100%	15	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwi2mask / mtnorm.py	 	92%	59	5	0	 	75%	16	4	 	88%
python / mrtrix3 / commands / dwi2mask / synthstrip.py	 	60%	43	17	0	 	12%	16	2	 	47%
python / mrtrix3 / commands / dwi2mask / trace.py	 	93%	59	4	0	 	83%	12	2	 	92%
python / mrtrix3 / commands / dwi2response / __init__.py	 	100%	1	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwi2response / dhollander.py	 	96%	190	8	0	 	78%	32	7	 	93%
python / mrtrix3 / commands / dwi2response / dwi2response.py	 	88%	58	7	0	 	75%	28	7	 	84%
python / mrtrix3 / commands / dwi2response / fa.py	 	97%	37	1	0	 	80%	10	2	 	94%
python / mrtrix3 / commands / dwi2response / manual.py	 	84%	38	6	0	 	69%	16	5	 	80%
python / mrtrix3 / commands / dwi2response / msmt_5tt.py	 	86%	85	12	0	 	62%	32	10	 	79%
python / mrtrix3 / commands / dwi2response / tax.py	 	95%	83	4	0	 	80%	20	4	 	92%
python / mrtrix3 / commands / dwi2response / tournier.py	 	99%	83	1	0	 	90%	20	2	 	97%
python / mrtrix3 / commands / dwibiascorrect / __init__.py	 	100%	1	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwibiascorrect / ants.py	 	45%	38	21	0	 	25%	12	1	 	40%
python / mrtrix3 / commands / dwibiascorrect / dwibiascorrect.py	 	88%	32	4	0	 	67%	12	4	 	82%
python / mrtrix3 / commands / dwibiascorrect / fsl.py	 	59%	27	11	0	 	33%	6	2	 	55%
python / mrtrix3 / commands / dwibiascorrect / mtnorm.py	 	90%	52	5	0	 	71%	14	4	 	86%
python / mrtrix3 / commands / dwibiasnormmask.py	 	75%	243	61	0	 	59%	68	22	 	71%
python / mrtrix3 / commands / dwicat.py	 	81%	130	25	0	 	74%	66	17	 	79%
python / mrtrix3 / commands / dwifslpreproc.py	 	11%	792	705	0	 	3%	404	7	 	8%
python / mrtrix3 / commands / dwigradcheck.py	 	59%	107	44	0	 	46%	52	12	 	55%
python / mrtrix3 / commands / dwinormalise / __init__.py	 	100%	1	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwinormalise / dwinormalise.py	 	100%	10	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / dwinormalise / group.py	 	95%	66	3	0	 	79%	14	3	 	92%
python / mrtrix3 / commands / dwinormalise / manual.py	 	96%	27	1	0	 	75%	4	1	 	94%
python / mrtrix3 / commands / dwinormalise / mtnorm.py	 	92%	72	6	0	 	75%	20	5	 	88%
python / mrtrix3 / commands / dwishellmath.py	 	94%	32	2	0	 	75%	8	2	 	90%
python / mrtrix3 / commands / for_each.py	 	69%	215	67	0	 	52%	80	14	 	64%
python / mrtrix3 / commands / labelsgmfirst.py	 	25%	87	65	0	 	8%	26	2	 	21%
python / mrtrix3 / commands / mask2glass.py	 	92%	37	3	0	 	67%	6	2	 	88%
python / mrtrix3 / commands / mrtrix_cleanup.py	 	77%	102	23	0	 	60%	40	8	 	73%
python / mrtrix3 / commands / peakscheck.py	 	50%	312	155	0	 	32%	122	9	 	45%
python / mrtrix3 / commands / population_template / __init__.py	 	100%	17	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / population_template / contrasts.py	 	88%	41	5	0	 	50%	12	2	 	79%
python / mrtrix3 / commands / population_template / execute.py	 	77%	618	143	0	 	68%	398	69	 	73%
python / mrtrix3 / commands / population_template / input.py	 	67%	61	20	0	 	45%	20	5	 	62%
python / mrtrix3 / commands / population_template / population_template.py	 	100%	6	0	0	 	100%	0	0	 	100%
python / mrtrix3 / commands / population_template / usage.py	 	89%	72	8	0	 	50%	2	1	 	88%
python / mrtrix3 / commands / population_template / utils.py	 	40%	199	119	0	 	30%	104	19	 	37%
python / mrtrix3 / commands / responsemean.py	 	93%	45	3	0	 	81%	16	3	 	90%
python / mrtrix3 / fsl.py	 	6%	124	116	0	 	0%	56	0	 	4%
python / mrtrix3 / image.py	 	71%	187	54	0	 	56%	86	24	 	66%
python / mrtrix3 / matrix.py	 	65%	177	62	0	 	46%	124	37	 	57%
python / mrtrix3 / path.py	 	13%	116	101	0	 	6%	62	2	 	11%
python / mrtrix3 / phaseencoding.py	 	4%	91	87	0	 	0%	64	0	 	3%
python / mrtrix3 / run.py	 	66%	452	155	0	 	46%	202	34	 	59%
python / mrtrix3 / sh.py	 	86%	7	1	0	 	50%	2	1	 	78%
python / mrtrix3 / utils.py	 	52%	98	47	0	 	40%	30	4	 	49%
python / mrtrix3 / version.py	 	0%	2	2	0	 	100%	0	0	 	0%
Total	 	58%	7884	3296	0	 	41%	3198	495	 	53%

Each file contains a hyperlink to another page that highlights the per-line hits and misses.

Overall percentage is low right now, partly because anything with an external dependency is going to error out immediately as the Action environment is not yet set up to install them. Entirely feasible to add the workflow first, and then deal with inclusion of external dependencies later (eg. integrating the use of #3073).

- Rename file (to eventually be more consistent with C++ coverage analysis workflow).
- Disable trigger on Pull Request.
- Enable manual triggering.
@Lestropie
Lestropie marked this pull request as ready for review May 18, 2026 11:11
@Lestropie
Lestropie requested a review from a team May 18, 2026 11:12
@Lestropie Lestropie mentioned this pull request May 27, 2026
27 tasks
@jdtournier

Copy link
Copy Markdown
Member

Not sure I feel competent to approve this, I'm not familiar with the concept. If the point is purely to generate a report and doesn't otherwise affect the software, generated artefacts, or block merging, I suggest we merge and see how it goes...?

@Lestropie

Copy link
Copy Markdown
Member Author

Generates new artifacts, doesn't modify existing ones. On Github go to Actions, on left side hit "Show more workflows" and select "Python coverage". Select the most recent, navigate to the individual workflow, and expand "Upload coverage report", and there's a URL for the download.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants