Skip to content

Coverage baseline - #94

Merged
insatomcat merged 5 commits into
mainfrom
coverage-baseline
Aug 6, 2026
Merged

Coverage baseline#94
insatomcat merged 5 commits into
mainfrom
coverage-baseline

Conversation

@insatomcat

@insatomcat insatomcat commented Aug 2, 2026

Copy link
Copy Markdown
Member

Establishes a measured coverage baseline for vm_manager, as a first step
towards the OpenSSF Best Practices gold criteria test_statement_coverage90
and test_branch_coverage80.

What it contains

  • Unit tests for the vm_manager_cmd CLI (55 tests). They monkeypatch
    the public API, so they check argument parsing and dispatch, not the
    backends. vm_manager_cmd.py goes to 92%.
  • Cluster-mode tests runnable without Ceph. tests/conftest.py installs
    the stubs from tests/ceph_stubs.py before importing vm_manager, because
    the backend is chosen at import time in __init__.py. Without them
    cluster_mode is False on any machine without Ceph and every cluster-side
    test is skipped, including those that need no cluster at all. The stubs
    raise on use, so a test that reaches real Ceph code fails loudly instead of
    passing quietly.
  • Coverage measured and published in CI, in the run summary, so the
    figures are readable and linkable without any third party service.
  • A CI hardening commit, detailed below.
  • SonarCloud analysed from the workflow instead of Automatic Analysis,
    which is what finally lets the coverage report reach SonarCloud. Also
    detailed below.

The baseline

TOTAL   1529 statements   560 branches   26%
153 passed, 3 xfailed

26% is low and the reason is structural, not a gap in the suite. CI runs 87
of the 178 test functions. The 91 it ignores are test_vm_manager_cluster.py
and test_vm_manager_cmd_cluster.py, which need a real Ceph and Pacemaker
cluster. Those are exactly the tests covering vm_manager_cluster.py,
rbd_manager.py and pacemaker.py, which together are 1115 of the 1529
statements. Closing the gap means unit tests with the bindings and the crm
CLI mocked, which is the next step, not this one.

There is deliberately no fail_under yet. Setting a threshold before
having an honest baseline would only encode the current state as a target.

SonarCloud will show 21.2%, not 26%, and both are correct. The two
figures measure slightly different things, so it is worth writing the
reconciliation down once:

  • pyproject.toml omits vm_manager/helpers/tests/* from the measurement,
    the hand-run integration scripts. That is 522 statements. 1529 + 522 =
    2051, against the 2042 coverable lines SonarCloud reports: same code, plus
    those scripts, counted as fully uncovered. They are shipped in the
    installed package, so SonarCloud is right to count them, and the scope is
    deliberately left alone rather than tuned to make the two numbers agree.
  • pytest reports one blended figure over statements and branches. SonarCloud
    reports them apart: 22.4% line, 16.6% branch, 21.2% overall.

The CI hardening commit

It exists because SonarCloud reported four findings on the two pip install
lines, under two rules, and both are legitimate.

  • githubactions:S8544, dependencies without locked versions. Fixed with
    requirements-ci.txt used as a pip constraints file. The permissive
    ranges in pyproject.toml are untouched, so nothing changes for anyone
    installing the package. Only CI is pinned.
  • githubactions:S8541, source distributions may run setup scripts at
    install time. Fixed with --only-binary :all:, with libvirt-python
    excluded by name
    : it publishes no wheel on PyPI and compiles against the
    system libvirt headers, which is what the libvirt-dev and pkg-config
    apt packages in the workflow are for. Naming it keeps source builds to that
    single reviewed package rather than reopening them for the whole dependency
    graph.

The install is also editable. Coverage instruments the package in the
checkout, so the tests must import it from there. A regular install copies it
to site-packages, the tests import that copy, and coverage reports 0% on
every module while the suite passes.

SonarCloud analysed from the workflow

The project ran SonarCloud Automatic Analysis: the GitHub App analysed
every push server-side, with no scanner in the repository. That mode cannot
serve this change, for two reasons.

  • It never runs the build or the tests, so it cannot import a coverage
    report. SonarCloud has had no coverage data at all for this project:
    the coverage metric is simply absent from the project measures, and
    new_lines_to_cover is 0 on pull requests. Producing a coverage.xml in
    CI is useless as long as nothing can consume it.
  • It has no git history to blame lines with, so on a pull request every line
    of a touched file counts as new code. That is why this branch first went
    red on a line of ci.yml identical to main, at the same line number,
    dated 2026-02-19 by SonarCloud itself, while main stayed green on the
    same finding.

So the scanner now runs from the test job, after the tests, with
fetch-depth: 0 so blame works, and sonar-project.properties declares the
coverage report path. Scope stays the whole repository as it was under
Automatic Analysis, so the workflow, Dockerfile and XML analysers keep
reporting. The step is skipped when SONAR_TOKEN is absent, which is the
case for pull requests from forks: those keep the rest of the job.

Automatic Analysis has been turned off in the project settings and the
SONAR_TOKEN repository secret is in place, both prerequisites for the
above. The trade-off is worth stating: pull requests from forks are no
longer analysed at all, where Automatic Analysis did analyse them. In
exchange the project gets coverage and correct new-code detection, neither
of which Automatic Analysis can ever provide.

This already works on this pull request. The analysis run from the workflow
reports coverage 21.2, line_coverage 22.4, branch_coverage 16.6 over
2042 coverable lines, and the quality gate is green. Before it, the
coverage metric did not exist on the project at all.

Known issues left open on purpose

Three tests are marked xfail(strict=True) because they document two real
CLI bugs rather than hide them:

  • create --disable and clone --disable disable nothing. main() only
    assigns args.enable under if "enable" in args, but enable is never an
    argparse dest for those subcommands, so the key never reaches the backend
    and _configure_vm() treats a missing enable as True.
  • create --enable-live-migration is a no-op, same pattern with
    if "live_migration" in args.

add-to-cluster assigns both unconditionally and is the correct reference.
Fixing them changes CLI behaviour, so it belongs in its own change.

insatomcat added a commit that referenced this pull request Aug 2, 2026
…alysis

The project runs SonarCloud Automatic Analysis (autoscanEnabled=true,
ciName=Autoscan): the SonarCloud GitHub App analyses every push
server-side, with no scanner in the repository. That mode cannot work
for what this branch is about, for two reasons.

Automatic Analysis never runs the build or the tests, so it cannot
import a coverage report. SonarCloud has therefore never had coverage
data for this project: the `coverage` metric is absent on main and
`new_lines_to_cover` is 0 on pull requests. The coverage.xml produced by
CI has nowhere to go.

It also has no git history to blame lines with, so on a pull request
every line of a touched file counts as new code. On PR #94 that is
visible on .github/workflows/ci.yml line 54: the line is identical to
main, at the same line number, and SonarCloud dates the issue on it
2026-02-19, yet it lands in the new-code period and fails the
new_security_rating gate. main itself is green with the same four
findings.

Run the scanner from the test job instead, after the tests, with
fetch-depth: 0 so blame works, and declare the coverage report path in
sonar-project.properties. Scope stays the whole repository as it was
under Automatic Analysis, so the workflow, Dockerfile and XML analysers
keep reporting.

Requires two manual steps before this can work: turn Automatic Analysis
off in the SonarCloud project settings, and add the SONAR_TOKEN
repository secret. SonarCloud rejects a CI analysis while Automatic
Analysis is enabled, so the order matters.
Comment thread .github/workflows/ci.yml Fixed
insatomcat added a commit that referenced this pull request Aug 2, 2026
…alysis

The project runs SonarCloud Automatic Analysis (autoscanEnabled=true,
ciName=Autoscan): the SonarCloud GitHub App analyses every push
server-side, with no scanner in the repository. That mode cannot work
for what this branch is about, for two reasons.

Automatic Analysis never runs the build or the tests, so it cannot
import a coverage report. SonarCloud has therefore never had coverage
data for this project: the `coverage` metric is absent on main and
`new_lines_to_cover` is 0 on pull requests. The coverage.xml produced by
CI has nowhere to go.

It also has no git history to blame lines with, so on a pull request
every line of a touched file counts as new code. On PR #94 that is
visible on .github/workflows/ci.yml line 54: the line is identical to
main, at the same line number, and SonarCloud dates the issue on it
2026-02-19, yet it lands in the new-code period and fails the
new_security_rating gate. main itself is green with the same four
findings.

Run the scanner from the test job instead, after the tests, with
fetch-depth: 0 so blame works, and declare the coverage report path in
sonar-project.properties. Scope stays the whole repository as it was
under Automatic Analysis, so the workflow, Dockerfile and XML analysers
keep reporting.

Requires two manual steps before this can work: turn Automatic Analysis
off in the SonarCloud project settings, and add the SONAR_TOKEN
repository secret. SonarCloud rejects a CI analysis while Automatic
Analysis is enabled, so the order matters.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
insatomcat added a commit that referenced this pull request Aug 2, 2026
…alysis

The project runs SonarCloud Automatic Analysis (autoscanEnabled=true,
ciName=Autoscan): the SonarCloud GitHub App analyses every push
server-side, with no scanner in the repository. That mode cannot work
for what this branch is about, for two reasons.

Automatic Analysis never runs the build or the tests, so it cannot
import a coverage report. SonarCloud has therefore never had coverage
data for this project: the `coverage` metric is absent on main and
`new_lines_to_cover` is 0 on pull requests. The coverage.xml produced by
CI has nowhere to go.

It also has no git history to blame lines with, so on a pull request
every line of a touched file counts as new code. On PR #94 that is
visible on .github/workflows/ci.yml line 54: the line is identical to
main, at the same line number, and SonarCloud dates the issue on it
2026-02-19, yet it lands in the new-code period and fails the
new_security_rating gate. main itself is green with the same four
findings.

Run the scanner from the test job instead, after the tests, with
fetch-depth: 0 so blame works, and declare the coverage report path in
sonar-project.properties. Scope stays the whole repository as it was
under Automatic Analysis, so the workflow, Dockerfile and XML analysers
keep reporting.

Requires two manual steps before this can work: turn Automatic Analysis
off in the SonarCloud project settings, and add the SONAR_TOKEN
repository secret. SonarCloud rejects a CI analysis while Automatic
Analysis is enabled, so the order matters.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
@insatomcat insatomcat closed this Aug 2, 2026
@insatomcat insatomcat reopened this Aug 2, 2026
insatomcat added a commit that referenced this pull request Aug 2, 2026
…alysis

The project runs SonarCloud Automatic Analysis (autoscanEnabled=true,
ciName=Autoscan): the SonarCloud GitHub App analyses every push
server-side, with no scanner in the repository. That mode cannot work
for what this branch is about, for two reasons.

Automatic Analysis never runs the build or the tests, so it cannot
import a coverage report. SonarCloud has therefore never had coverage
data for this project: the `coverage` metric is absent on main and
`new_lines_to_cover` is 0 on pull requests. The coverage.xml produced by
CI has nowhere to go.

It also has no git history to blame lines with, so on a pull request
every line of a touched file counts as new code. On PR #94 that is
visible on .github/workflows/ci.yml line 54: the line is identical to
main, at the same line number, and SonarCloud dates the issue on it
2026-02-19, yet it lands in the new-code period and fails the
new_security_rating gate. main itself is green with the same four
findings.

Run the scanner from the test job instead, after the tests, with
fetch-depth: 0 so blame works, and declare the coverage report path in
sonar-project.properties. Scope stays the whole repository as it was
under Automatic Analysis, so the workflow, Dockerfile and XML analysers
keep reporting.

Requires two manual steps before this can work: turn Automatic Analysis
off in the SonarCloud project settings, and add the SONAR_TOKEN
repository secret. SonarCloud rejects a CI analysis while Automatic
Analysis is enabled, so the order matters.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
Comment thread .github/workflows/ci.yml Fixed
Comment thread .github/workflows/ci.yml Fixed
Comment thread .github/workflows/ci.yml Fixed
Comment thread .github/workflows/ci.yml Fixed
@insatomcat
insatomcat marked this pull request as ready for review August 4, 2026 07:18
vm_manager selects its backend when it is first imported: if `rados` and
`rbd` are importable it exposes the cluster API, otherwise the
libvirt-only one. Those bindings ship with Ceph and are not installable
from PyPI, so on the CI runner `cluster_mode` is False.

The consequence was that tests/test_vm_manager_cmd.py, whose docstring
says it has no cluster dependencies, was skipped in its entirety by its
own `skipif(not vm_manager.cluster_mode)` guard. The only pure unit tests
in the repository never actually ran.

Register stub `rados` and `rbd` modules from tests/conftest.py, before
vm_manager is imported, when the real bindings are absent. The stubs only
satisfy the import: instantiating one raises, so a test that reaches real
Ceph code fails loudly rather than passing against a fake. On a machine
with Ceph the genuine bindings are found and nothing is stubbed.

CI now collects 36 tests instead of 32 plus 4 skipped, and cluster-side
code that needs no cluster (argparse, XML building, crm command
construction) becomes reachable by future unit tests.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
Cover the argparse layer and the main() dispatch table with every
vm_manager entry point replaced by a recorder, so nothing here needs
libvirt, Ceph or Pacemaker: the ParseMetaData action, the registered
subcommands and their required arguments, and the function plus
arguments main() forwards each command to.

Statement coverage of vm_manager_cmd.py goes from 39% to 92%. What is
left uncovered is the standalone-mode half of get_parser(), which cannot
be reached while the tests run in cluster mode.

Three tests are marked xfail(strict) because they document two real
bugs found while writing them:

  - `create --disable` and `clone --disable` do not disable anything.
    main() only assigns args.enable under `if "enable" in args`, but
    `enable` is never an argparse dest for those subcommands, so the key
    never reaches the backend, and _configure_vm() treats a missing
    'enable' key as True.

  - `create --enable-live-migration` is a no-op. main() guards the
    rename with `if "live_migration" in args`, and `live_migration` is
    likewise never a dest, so _configure_vm() never writes the
    _live_migration metadata.

add-to-cluster assigns both unconditionally and behaves correctly; the
tests covering it are the reference for what create and clone should do.
Coverage confirms the diagnosis: lines 542, 547 and 550 of
vm_manager_cmd.py are unreachable.

The markers are strict, so whoever fixes the guards will get an XPASS
failure telling them to drop the marker.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
The OpenSSF gold criteria test_statement_coverage90 and
test_branch_coverage80 both require a measured number, and nothing in
this repository measured one: no pytest-cov, no coverage configuration,
no CI step.

Add pytest-cov and coverage[toml] to the `test` extra, enable branch
coverage over the vm_manager package (excluding the hand-driven
integration scripts under vm_manager/helpers/tests/), and have CI print a
summary table in the job summary and publish coverage.xml plus the HTML
report as an artifact.

No exclusions and no fail_under for now: the point of this change is to
get an honest baseline before deciding where to set thresholds.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
The two pip install steps resolved their dependencies freely and accepted
source distributions. A run could therefore install different versions
than the previous one, and any dependency could execute a setup script at
install time. SonarCloud reports both, as githubactions:S8544 and
githubactions:S8541.

Pin the versions through a pip constraints file rather than by narrowing
the ranges declared in pyproject.toml: the package keeps its permissive
ranges for downstream users, only CI is nailed down. The pinned versions
are the ones the workflow already resolved, so nothing changes but the
drift.

Restrict both steps to wheels. libvirt-python cannot follow, it publishes
no wheel on PyPI and compiles against the system libvirt headers, which is
what the libvirt-dev and pkg-config packages installed above are for.
Excluding it by name keeps source builds to that single reviewed package
rather than reopening them for the whole dependency graph.

The docs step becomes editable as well. It used to reinstall the package
as a copy over the editable install, which was pointless since the tests
had already put it in place.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
…alysis

The project ran SonarCloud Automatic Analysis (ciName=Autoscan): the
SonarCloud GitHub App analysed every push server-side, with no scanner in
the repository. That mode cannot serve what this branch is about, for two
reasons.

Automatic Analysis never runs the build or the tests, so it cannot import
a coverage report. SonarCloud consequently had no coverage data for this
project at all: the `coverage` metric is absent from the project measures
and `new_lines_to_cover` is 0 on pull requests. The coverage.xml the
previous commit produces would have nowhere to go.

It also has no git history to blame lines with, so on a pull request every
line of a touched file counts as new code. That was visible on this branch
before it was fixed: an issue on a line of .github/workflows/ci.yml
identical to main, at the same line number, dated 2026-02-19 by SonarCloud
itself, still landed in the new-code period and failed the
new_security_rating gate, while main stayed green with the same finding.

Run the scanner from the test job instead, after the tests, with
fetch-depth: 0 so blame works, and declare the coverage report path in
sonar-project.properties. Scope stays the whole repository as it was under
Automatic Analysis, so the workflow, Dockerfile and XML analysers keep
reporting.

Automatic Analysis has been turned off in the project settings and the
SONAR_TOKEN repository secret is in place, which this step needs. It is
skipped when the secret is unavailable, as happens for pull requests
opened from a fork; those keep the rest of the job.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@insatomcat
insatomcat merged commit 1dd9bdf into main Aug 6, 2026
5 checks passed
@insatomcat
insatomcat deleted the coverage-baseline branch August 6, 2026 10:02
insatomcat added a commit that referenced this pull request Aug 6, 2026
The New Code definition is "previous version" and no analysis ever ran
with one, so the period falls back to the first analysis ever: 3562 new
lines for 3334 lines of code, the whole code base. That is what turned
the main gate red on new_coverage and new_security_rating as soon as #94
published coverage. Pull requests were never affected, their gate is
computed on their own diff.

Pass the version from pyproject.toml, which stays the single source of
truth, so each release gives the period a real boundary.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
insatomcat added a commit that referenced this pull request Aug 7, 2026
The New Code definition is "previous version" and no analysis ever ran
with one, so the period falls back to the first analysis ever: 3562 new
lines for 3334 lines of code, the whole code base. That is what turned
the main gate red on new_coverage and new_security_rating as soon as #94
published coverage. Pull requests were never affected, their gate is
computed on their own diff.

Pass the version from pyproject.toml, which stays the single source of
truth, so each release gives the period a real boundary.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants