From 06a0cdb7d3c1c1d9d8eb1f9a28f74b5eeb514321 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 08:13:10 +0100 Subject: [PATCH 1/4] Upload Windows coverage data --- .github/workflows/test.yml | 13 ++++++++----- pyproject.toml | 2 ++ tests/mock_vws/test_docker.py | 8 ++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1daf47d74..92b251db9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -277,11 +277,6 @@ jobs: # We use pytest-xdist to make this run much faster. # The downside is that we cannot use -s / --capture=no. # - # We use coverage to collect coverage data but we currently - # do not upload / use it because combining Windows and Linux - # coverage is challenging. - # - # We therefore have a few ``# pragma: no cover`` statements. uv run --extra=dev \ coverage run -m pytest \ --skip-real \ @@ -292,6 +287,14 @@ jobs: env: UV_PYTHON: ${{ matrix.python-version }} + - name: Upload coverage data + uses: actions/upload-artifact@v7 + with: + name: coverage-data-windows-${{ matrix.python-version }} + path: .coverage.* + include-hidden-files: true + if-no-files-found: error + coverage: name: Combine & check coverage needs: [ci-tests, skip-tests, windows-tests] diff --git a/pyproject.toml b/pyproject.toml index 055ccfebd..2cd60ed01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -380,6 +380,8 @@ run.omit = [ "src/mock_vws/_flask_server/healthcheck.py", ] run.parallel = true +run.patch = [ "subprocess" ] +run.relative_files = true run.source = [ "ci/", "src/", "tests/" ] report.exclude_also = [ "class .*\\bProtocol\\):", diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index d29c130bc..3efb5ed91 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -90,9 +90,7 @@ def fixture_custom_bridge_network() -> Iterator[Network]: name = "test-vws-bridge-" + uuid.uuid4().hex try: network = client.networks.create(name=name, driver="bridge") - # We skip coverage here because combining Windows and Linux coverage - # is challenging. - except NotFound: # pragma: no cover + except NotFound: # On Windows the "bridge" network driver is not available and we use # the "nat" driver instead. network = client.networks.create(name=name, driver="nat") @@ -145,9 +143,7 @@ def test_build_and_run( target="target-manager", rm=True, ) - # We skip coverage here because combining Windows and Linux coverage - # is challenging. - except BuildError as exc: # pragma: no cover + except BuildError as exc: full_log = "\n".join( [item["stream"] for item in exc.build_log if "stream" in item], ) From aa9276a7c11c86db88a89c8fd4ef0b93d179301f Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 08:52:30 +0100 Subject: [PATCH 2/4] Cover unexpected Docker build errors --- tests/mock_vws/test_docker.py | 61 ++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 3efb5ed91..6225164c3 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -4,7 +4,8 @@ import uuid from collections.abc import Iterable, Iterator from http import HTTPStatus -from typing import TYPE_CHECKING +from pathlib import Path +from typing import TYPE_CHECKING, cast import docker import pytest @@ -274,3 +275,61 @@ def test_build_and_run( matching_targets = cloud_reco_client.query(image=high_quality_image) assert matching_targets[0].target_id == target_id + + +def test_build_and_run_raises_full_log_for_unexpected_build_error( + *, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + """An unexpected Docker build error includes the full build log.""" + + class Images: + """Mock Docker images API.""" + + def build(self, **_: object) -> tuple[object, object]: + """Raise an unexpected Docker build error.""" + build_log = [ + {"stream": "first build log line"}, + {"stream": "second build log line"}, + {"aux": "ignored"}, + ] + raise BuildError( + reason="unexpected build failure", + build_log=iter(build_log), + ) + + class Client: + """Mock Docker client.""" + + images = Images() + + class Config: + """Mock pytest config.""" + + rootpath = tmp_path + + class Request: + """Mock pytest request.""" + + config = Config() + + class CustomBridgeNetwork: + """Mock Docker network.""" + + name = "custom-bridge-network" + + monkeypatch.setattr(target=docker, name="from_env", value=Client) + + with pytest.raises( + expected_exception=AssertionError, + match="first build log line\nsecond build log line", + ): + test_build_and_run( + high_quality_image=io.BytesIO(), + custom_bridge_network=cast( + "Network", + CustomBridgeNetwork(), + ), + request=cast("pytest.FixtureRequest", Request()), + ) From 6e6a7ae4b2684ae0bb1b53aae93dc6b33fd5a9de Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 08:58:32 +0100 Subject: [PATCH 3/4] Fix Docker test lint --- tests/mock_vws/test_docker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 6225164c3..716cbbfdf 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -53,7 +53,7 @@ def wait_for_health_check(container: Container) -> None: """Wait for a container to pass its health check. On failure, augment the error with the container's logs and the - Docker health check probe history so CI failures are diagnosable. + Docker health check probe history so CI failures are easy to diagnose. """ try: _poll_health_check(container=container) @@ -287,7 +287,8 @@ def test_build_and_run_raises_full_log_for_unexpected_build_error( class Images: """Mock Docker images API.""" - def build(self, **_: object) -> tuple[object, object]: + @staticmethod + def build(**_: object) -> tuple[object, object]: """Raise an unexpected Docker build error.""" build_log = [ {"stream": "first build log line"}, From ab499ff3a9f6ab43703381dd4c11c6831cdc95d2 Mon Sep 17 00:00:00 2001 From: Adam Dangoor Date: Tue, 19 May 2026 10:00:13 +0100 Subject: [PATCH 4/4] Use pragma for uncovered Docker branch --- tests/mock_vws/test_docker.py | 66 ++--------------------------------- 1 file changed, 3 insertions(+), 63 deletions(-) diff --git a/tests/mock_vws/test_docker.py b/tests/mock_vws/test_docker.py index 716cbbfdf..1538c274f 100644 --- a/tests/mock_vws/test_docker.py +++ b/tests/mock_vws/test_docker.py @@ -4,8 +4,7 @@ import uuid from collections.abc import Iterable, Iterator from http import HTTPStatus -from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING import docker import pytest @@ -53,7 +52,7 @@ def wait_for_health_check(container: Container) -> None: """Wait for a container to pass its health check. On failure, augment the error with the container's logs and the - Docker health check probe history so CI failures are easy to diagnose. + Docker health check probe history so CI failures are diagnosable. """ try: _poll_health_check(container=container) @@ -158,7 +157,7 @@ def test_build_and_run( windows_message_substring in exc.msg for windows_message_substring in windows_message_substrings ): - raise AssertionError(full_log) from exc + raise AssertionError(full_log) from exc # pragma: no cover pytest.skip( reason="We do not currently support using Windows containers." ) @@ -275,62 +274,3 @@ def test_build_and_run( matching_targets = cloud_reco_client.query(image=high_quality_image) assert matching_targets[0].target_id == target_id - - -def test_build_and_run_raises_full_log_for_unexpected_build_error( - *, - monkeypatch: pytest.MonkeyPatch, - tmp_path: Path, -) -> None: - """An unexpected Docker build error includes the full build log.""" - - class Images: - """Mock Docker images API.""" - - @staticmethod - def build(**_: object) -> tuple[object, object]: - """Raise an unexpected Docker build error.""" - build_log = [ - {"stream": "first build log line"}, - {"stream": "second build log line"}, - {"aux": "ignored"}, - ] - raise BuildError( - reason="unexpected build failure", - build_log=iter(build_log), - ) - - class Client: - """Mock Docker client.""" - - images = Images() - - class Config: - """Mock pytest config.""" - - rootpath = tmp_path - - class Request: - """Mock pytest request.""" - - config = Config() - - class CustomBridgeNetwork: - """Mock Docker network.""" - - name = "custom-bridge-network" - - monkeypatch.setattr(target=docker, name="from_env", value=Client) - - with pytest.raises( - expected_exception=AssertionError, - match="first build log line\nsecond build log line", - ): - test_build_and_run( - high_quality_image=io.BytesIO(), - custom_bridge_network=cast( - "Network", - CustomBridgeNetwork(), - ), - request=cast("pytest.FixtureRequest", Request()), - )