Skip to content

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

Open
insatomcat wants to merge 3 commits into
mainfrom
add-tests-and-coverage
Open

tests: add a unit test suite and measure coverage#22
insatomcat wants to merge 3 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, to be fixed in a follow-up

This pull request changes no production code. git diff main..HEAD -- setup_ovs/ is empty. The five defects below were not introduced here: git log -L puts every one of them in 6c83aa0, "init repo from meta-seapath folder", 2022-05-21. They have been there since the repository was created, over four years ago.

Writing the tests did not create them, it executed those code paths for the first time. That is also why the coverage is not 100 %: the four uncovered statements in check.py are literally the dead code these defects produce, and no input can reach them.

They are deliberately left unfixed here. Correcting them means changing the behaviour of validation code that has been running on SEAPATH clusters for four years, which deserves its own pull request and its own discussion rather than being buried in a test suite. Mixing the two would also make this diff impossible to review: right now it adds tests and touches nothing else.

None of them is encoded as expected behaviour either. Each is marked xfail(strict=True) with its reason, so the day one is fixed the suite goes red and the marker has to be removed. The cleanup cannot be forgotten. They are listed in tests/test_helpers.py:180 and tests/test_check.py lines 324, 382, 427 and 520, and pytest -rx prints them with their reason on every run.

  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.

SonarCloud analysis

The project now runs CI-based analysis. Automatic Analysis has been turned off
and SONAR_TOKEN is configured, so the scanner step actually runs and the
coverage report is imported. Automatic Analysis could never have done it: it
runs neither the build nor the tests, and the Sonar documentation states
plainly that "Code coverage information is not supported".

The first real analysis confirms the figures measured locally:

Sonar metric Value
line_coverage 99.1 %
branch_coverage 98.6 %
coverage (combined) 98.9 %

The quality gate passes on its five conditions. new_coverage is not
evaluated, since new_lines_to_cover is empty: this branch adds no line to
sonar.sources.

Worth knowing for later: the analysis token expires. When it does, the scanner
will fail with a 403 and nothing will warn beforehand. That is exactly the
state vm_manager is in today, with a token issued in 2023.

Why the CI matrix does not start at Python 3.6

pyproject.toml declares requires-python = ">=3.6". That declaration is factually wrong: the package cannot run on 3.6.

subprocess.run() is called with capture_output=True in two places, setup_ovs/check.py:18 and setup_ovs/ovs.py:76. That argument was added in Python 3.7. On 3.6 both calls raise TypeError, which means system_check() and clear_ovs() fail immediately. So the real floor is at least 3.7, whatever the metadata says. Python 3.6 itself has been end of life since December 2021.

The matrix therefore runs 3.9 to 3.13, the versions available on current GitHub runners. 3.7 and 3.8 are also end of life and are no longer provided there, so testing the declared floor is not possible even if we wanted to.

I have not changed requires-python, because lowering or raising it is a packaging decision that depends on what SEAPATH actually deploys on, not on what the code happens to tolerate. Tell me the real floor and I will align the metadata and the matrix in one go.

This is not purely cosmetic. It also blocks a deprecation: the build warns that license = { text = "Apache-2.0" } should become a plain SPDX string, and that fix requires setuptools>=77 in build-system. Stable distributions ship considerably older setuptools, so raising that floor could break building the package on the SEAPATH target. The warning is not fatal before 2027-02-18, so both questions are better settled together than one at a time.

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 draft August 2, 2026 18:01
@insatomcat
insatomcat marked this pull request as ready for review August 3, 2026 07:56
setup_ovs.py imports yaml to read .yaml and .yml configuration files, but
the project declared no dependency at all. An installed package therefore
raised ModuleNotFoundError on those files unless PyYAML happened to be
present for another reason.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
The project had no test at all, so the two OpenSSF gold coverage criteria
(test_statement_coverage90 and test_branch_coverage80) could not be
evaluated: they ask for a measured figure, not for a suite that merely
exists.

Add a pytest suite covering the five modules. Everything that touches the
system is mocked (subprocess, sysfs, /proc, the network stack), so the
suite needs neither root nor a cluster and runs anywhere. It measures
99.09 percent of statements and 98.65 percent of branches.

Enable branch coverage in pyproject.toml and add a test extra. No
fail_under yet: the point of this commit is an honest baseline.

Five tests are marked xfail(strict=True). Each pins a defect found while
writing the suite rather than encoding it as expected behaviour, so the
suite fails again once the defect is fixed and the marker has to go. All
five predate this branch, they come from the initial import of the
repository.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
The repository had no CI. Add a workflow that runs the suite on python
3.9 to 3.13, publishes the coverage table in the run summary, and checks
that the wheel builds reproducibly.

Coverage evidence does not depend on a third party: the OpenSSF criteria
are self-asserted and only require a FLOSS tool able to measure them,
which coverage.py is. The run summary is therefore enough on its own.

Reproducible build: two builds with SOURCE_DATE_EPOCH pinned produce
byte-identical wheels, without it they differ because setuptools stamps
the archive with the source mtimes. The job builds the wheel twice and
compares the SHA-256. It builds outside the work tree, since the project
uses a flat layout and an output directory next to setup_ovs/ would be
picked up as a second top-level package.

Validation happens on pull requests, which build the simulated merge
commit. main is built too, but minimally, one python version and no
reproducible build job: SonarCloud needs an analysis of main as the
reference for the new code comparison, and that analysis needs a coverage
report.

Actions are pinned to full commit SHAs and the CI toolchain is pinned in
requirements-ci.txt, so a run cannot silently pick up a new release.

sonar-project.properties declares the coverage report path. It only takes
effect once Automatic Analysis is turned off on the SonarCloud project,
since that mode never runs the tests. The scanner step stays skipped
while SONAR_TOKEN is unset.

Signed-off-by: Florent Carli <florent.carli@rte-france.com>
@insatomcat
insatomcat force-pushed the add-tests-and-coverage branch from e43b4aa to e5917f6 Compare August 3, 2026 08:00
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 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