Skip to content

tests: add a unit test suite and measure coverage - #22

Draft
insatomcat wants to merge 6 commits into
mainfrom
add-tests-and-coverage
Draft

tests: add a unit test suite and measure coverage#22
insatomcat wants to merge 6 commits into
mainfrom
add-tests-and-coverage

Conversation

@insatomcat

@insatomcat insatomcat commented Aug 2, 2026

Copy link
Copy Markdown
Member

The repository had no test and no CI. The two OpenSSF gold coverage criteria (test_statement_coverage90 and test_branch_coverage80) could therefore not be evaluated at all: they ask for a measured figure, not for a test suite that merely exists.

What this brings

A pytest suite of 219 tests over the five modules, running in about 0.2 s. Everything that touches the system is mocked (subprocess, sysfs, /proc, the network stack), so the suite needs neither root, nor OVS, nor a cluster, and runs on any machine.

Measured result:

Criterion Before After
test_statement_coverage90 no test 99.09 % (435/439)
test_branch_coverage80 no test 98.65 % (292/296)
build_reproducible unknown reproducible, with SOURCE_DATE_EPOCH

Contents

  • test suite in tests/, a test extra, and branch coverage enabled in pyproject.toml
  • CI workflow: tests on Python 3.9 to 3.13, a check that the wheel builds reproducibly, and the coverage report sent to SonarCloud
  • sonar-project.properties so the report is imported
  • requirements-ci.txt, a pinned toolchain used as a pip constraints file, so a CI run does not silently pick up a new release of pytest or build between two runs. Every pin supports Python 3.9 to 3.13. Actions are pinned to full commit SHAs for the same reason, with the major version kept in a trailing comment.
  • PyYAML declared in the project dependencies. setup_ovs.py imports yaml to read .yaml and .yml configurations, but nothing required it, so an installed package crashed on those files.

Reproducible build

Measured both ways rather than assumed. Two builds with SOURCE_DATE_EPOCH pinned produce byte-for-byte identical wheels. Without it they differ: setuptools stamps the archive with the source file mtimes, which change on every checkout. The reproducible-build job therefore builds the wheel twice and compares the SHA-256 of the two archives.

Bugs found while writing the tests

None of them is encoded as expected behaviour. Each is marked xfail(strict=True) with its reason: the day the bug is fixed the suite goes red again and forces the marker to be removed. These five points are still open, they are not fixed here.

  1. helpers.run_command does nothing when the caller passes check= (helpers.py:60). The return subprocess.run(...) sits inside the if "check" not in kargs branch. No caller passes check today, so it is latent, but it is a trap for the next one.
  2. The VLAN tag range is never validated (check.py:283). The guard is if "vlan" in port while the attribute actually consumed by ovs._create_bridges is tag.
  3. The "must be set if type is vxlan" check is unreachable (check.py:270): it sits inside if attribute in port and then tests attribute not in port. A vxlan port with neither key nor remote_ip passes the check, then raises KeyError further down in ovs.py:239.
  4. The IANA VXLAN port 4789 is rejected (check.py:111). _attribute_is_a_port is documented as a TCP/UDP port check but enforces the VLAN tag range, 0 to 4095. A TCP/UDP port goes up to 65535.
  5. The duplicate-NIC guard can never fire (check.py:165). dpdk_interfaces and system_interfaces are locals of _check_port_configuration, which runs once per port, so they are always empty.

Two pending items, outside this PR

Neither blocks the badge. The OpenSSF coverage criteria are self-asserted and only require a FLOSS tool able to measure them, which coverage.py is, so the figures published in the CI run summary are sufficient evidence on their own.

  • Automatic Analysis is still on on the SonarCloud project. It runs neither the build nor the tests, so it cannot import a coverage report ("Code coverage information is not supported" in the Sonar documentation). Switching to CI analysis would add the coverage metric and its history to the dashboard, nothing more.
  • The SONAR_TOKEN secret is not configured yet. The scanner step is conditioned on its presence, so CI stays green in the meantime. It must only be added once Automatic Analysis is off, since a CI analysis started while it is active fails the build.

The project had no test and no CI, so the two OpenSSF gold coverage
criteria (test_statement_coverage90 and test_branch_coverage80) could not
be evaluated at all.

Add a pytest suite covering the five modules. Everything OVS touches is
mocked (subprocess, sysfs, /proc, the network stack), so the suite needs
neither root nor a cluster and runs anywhere. The measured result is
98.75 % of statements and 98.23 % of branches.

Also:
 - declare PyYAML in the project dependencies. setup_ovs.py imports yaml
   to read .yaml/.yml configurations but nothing required it, so an
   installed package crashed on those files.
 - add a test extra, and enable branch coverage in pyproject.toml.
 - add a CI workflow running the suite on python 3.9 to 3.13, checking
   that the wheel builds reproducibly, and feeding the coverage report to
   SonarCloud.
 - add sonar-project.properties so the coverage report is imported. This
   only takes effect once Automatic Analysis is turned off on the
   SonarCloud project, since it never runs the tests.

Five tests are marked xfail(strict=True). Each pins a bug found while
writing the suite rather than encoding it as expected behaviour, so the
suite fails again once the bug is fixed and the marker has to go.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
python -m build creates its output directory before invoking the backend.
With --outdir first, setuptools flat-layout auto-discovery then sees
'first' as a second top-level package next to setup_ovs and refuses to
build.

Build into RUNNER_TEMP instead.

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
SonarCloud flagged four vulnerabilities, all in the workflow this branch
adds, and around thirty maintainability issues in the new tests.

Workflow:
 - pin every action to a full commit SHA instead of a moving major tag,
   which is what the OpenSSF supply chain guidance asks for. The major
   version is kept in a trailing comment.
 - add requirements-ci.txt and install through it, so a CI run does not
   silently pick up a new release of pytest or build between two runs.
   Every pin supports python 3.9 to 3.13.

Tests:
 - add an assert_rejects helper in test_check.py so the assertion block
   holds a single call, instead of building the configuration inside the
   pytest.raises block. Shorter and unambiguous about what is expected to
   raise.
 - set helpers.dry_run through monkeypatch rather than by hand, and let
   monkeypatch restore it. The autouse fixture now captures the real
   find_command before the test body runs, so its teardown still clears
   the memoization when a test replaces that function.

No behaviour change, same 217 tests and same coverage.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
The OpenSSF statement and branch coverage criteria are self-asserted and
only require a FLOSS tool able to measure them. coverage.py qualifies, so
the evidence does not have to come from a third party.

Print the coverage table into the job summary, which gives a readable and
linkable figure on every run, independently of whether SonarCloud has
been switched to CI analysis yet.

Also drop the SonarCloud coverage badge from the README. The coverage
metric does not exist on the project yet, so that badge rendered as an
error. Point at the CI workflow instead, which works today.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
@insatomcat
insatomcat marked this pull request as draft August 2, 2026 18:01
clear_ovs walks ignored_bridges and removes each entry from the list of
live bridges. The case where an ignored bridge is not up, so the removal
is skipped and the loop just continues, had no test. It was the last
partial branch in ovs.py, now at 100 percent on both statements and
branches.

Also add a test for a blank line in the ovs-vsctl list-br output, which
the empty-name guard exists for.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
vm_manager pull request 94 pins SonarSource/sonarqube-scan-action to
v8.2.1 and sets SONAR_HOST_URL, which v8 needs to target SonarQube Cloud
rather than a server. Use the same version and the same environment so
both repositories stay on one convention.

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

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

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.

2 participants