From d60792cacc4b5125b6da6250be48aa69aee68a31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:50:57 +0000 Subject: [PATCH 1/6] Add local pre-commit hook for Codecov YAML validation Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com> --- .pre-commit-config.yaml | 5 ++++ scripts/check_codecov_yaml.py | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 scripts/check_codecov_yaml.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e4ed5bf..c0621ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,6 +33,11 @@ repos: files: ^.*\.(py|md|rst|yml)$ - repo: local hooks: + - id: validate-codecov-yaml + name: validate codecov.yml + entry: python scripts/check_codecov_yaml.py + language: python + files: ^codecov\.yml$ - id: check-space-packet-parser-metadata name: check space_packet_parser metadata entry: python scripts/check_metadata.py diff --git a/scripts/check_codecov_yaml.py b/scripts/check_codecov_yaml.py new file mode 100644 index 0000000..25d39aa --- /dev/null +++ b/scripts/check_codecov_yaml.py @@ -0,0 +1,56 @@ +"""Validate codecov.yml by posting it to Codecov's validation endpoint.""" + +from __future__ import annotations + +import argparse +import sys +import urllib.error +import urllib.request +from pathlib import Path + +CODECOV_VALIDATE_URL = "https://codecov.io/validate" +REPO_ROOT = Path(__file__).parent.parent + + +def validate_codecov_yaml(file_path: Path) -> int: + """Validate a Codecov YAML file against Codecov's endpoint.""" + request = urllib.request.Request( + CODECOV_VALIDATE_URL, + data=file_path.read_bytes(), + method="POST", + headers={"Content-Type": "text/yaml"}, + ) + + try: + with urllib.request.urlopen(request) as response: + body = response.read().decode("utf-8", errors="replace").strip() + if body: + print(body) + print(f"{file_path} is valid.") + return 0 + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace").strip() + print(f"{file_path} validation failed with HTTP {exc.code}.", file=sys.stderr) + if body: + print(body, file=sys.stderr) + return 1 + except urllib.error.URLError as exc: + print(f"Unable to reach Codecov validation endpoint: {exc.reason}", file=sys.stderr) + return 1 + + +def main() -> int: + """Parse CLI args and validate codecov.yml.""" + parser = argparse.ArgumentParser() + parser.add_argument( + "--file", + type=Path, + default=REPO_ROOT / "codecov.yml", + help="Path to the codecov YAML file to validate.", + ) + args = parser.parse_args() + return validate_codecov_yaml(args.file) + + +if __name__ == "__main__": + raise SystemExit(main()) From f177447d1b06e498293c8a0b90270b6fc6439214 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:52:10 +0000 Subject: [PATCH 2/6] Handle pre-commit filename args and lint compliance for Codecov hook Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com> --- scripts/check_codecov_yaml.py | 61 ++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/scripts/check_codecov_yaml.py b/scripts/check_codecov_yaml.py index 25d39aa..9744e4a 100644 --- a/scripts/check_codecov_yaml.py +++ b/scripts/check_codecov_yaml.py @@ -3,40 +3,44 @@ from __future__ import annotations import argparse +import http.client import sys -import urllib.error -import urllib.request from pathlib import Path -CODECOV_VALIDATE_URL = "https://codecov.io/validate" +CODECOV_VALIDATE_HOST = "codecov.io" +CODECOV_VALIDATE_PATH = "/validate" REPO_ROOT = Path(__file__).parent.parent def validate_codecov_yaml(file_path: Path) -> int: """Validate a Codecov YAML file against Codecov's endpoint.""" - request = urllib.request.Request( - CODECOV_VALIDATE_URL, - data=file_path.read_bytes(), - method="POST", - headers={"Content-Type": "text/yaml"}, - ) - try: - with urllib.request.urlopen(request) as response: - body = response.read().decode("utf-8", errors="replace").strip() - if body: - print(body) - print(f"{file_path} is valid.") - return 0 - except urllib.error.HTTPError as exc: - body = exc.read().decode("utf-8", errors="replace").strip() - print(f"{file_path} validation failed with HTTP {exc.code}.", file=sys.stderr) + conn = http.client.HTTPSConnection(CODECOV_VALIDATE_HOST) + conn.request( + method="POST", + url=CODECOV_VALIDATE_PATH, + body=file_path.read_bytes(), + headers={"Content-Type": "text/yaml"}, + ) + response = conn.getresponse() + body = response.read().decode("utf-8", errors="replace").strip() + except OSError as exc: + print(f"Unable to reach Codecov validation endpoint: {exc}", file=sys.stderr) + return 1 + finally: + if "conn" in locals(): + conn.close() + + if response.status >= 400: + print(f"{file_path} validation failed with HTTP {response.status}.", file=sys.stderr) if body: print(body, file=sys.stderr) return 1 - except urllib.error.URLError as exc: - print(f"Unable to reach Codecov validation endpoint: {exc.reason}", file=sys.stderr) - return 1 + + if body: + print(body) + print(f"{file_path} is valid.") + return 0 def main() -> int: @@ -48,8 +52,19 @@ def main() -> int: default=REPO_ROOT / "codecov.yml", help="Path to the codecov YAML file to validate.", ) + parser.add_argument( + "files", + nargs="*", + type=Path, + help="Optional file paths provided by pre-commit.", + ) args = parser.parse_args() - return validate_codecov_yaml(args.file) + files_to_validate = args.files or [args.file] + exit_code = 0 + for file_path in files_to_validate: + if validate_codecov_yaml(file_path) != 0: + exit_code = 1 + return exit_code if __name__ == "__main__": From 077ead8b961b81489fdb99ef7bb6f5b9e98ff0da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:01:52 +0000 Subject: [PATCH 3/6] Fix Codecov validator request handling and add unit tests Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com> --- scripts/check_codecov_yaml.py | 33 +++++---- tests/unit/test_check_codecov_yaml.py | 97 +++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 tests/unit/test_check_codecov_yaml.py diff --git a/scripts/check_codecov_yaml.py b/scripts/check_codecov_yaml.py index 9744e4a..fc2987c 100644 --- a/scripts/check_codecov_yaml.py +++ b/scripts/check_codecov_yaml.py @@ -3,39 +3,36 @@ from __future__ import annotations import argparse -import http.client import sys from pathlib import Path +from urllib import error, request -CODECOV_VALIDATE_HOST = "codecov.io" -CODECOV_VALIDATE_PATH = "/validate" +CODECOV_VALIDATE_URL = "https://codecov.io/validate" +NETWORK_TIMEOUT_SECONDS = 10 REPO_ROOT = Path(__file__).parent.parent def validate_codecov_yaml(file_path: Path) -> int: """Validate a Codecov YAML file against Codecov's endpoint.""" try: - conn = http.client.HTTPSConnection(CODECOV_VALIDATE_HOST) - conn.request( - method="POST", - url=CODECOV_VALIDATE_PATH, - body=file_path.read_bytes(), - headers={"Content-Type": "text/yaml"}, - ) - response = conn.getresponse() - body = response.read().decode("utf-8", errors="replace").strip() + request_body = file_path.read_bytes() except OSError as exc: - print(f"Unable to reach Codecov validation endpoint: {exc}", file=sys.stderr) + print(f"Unable to read {file_path}: {exc}", file=sys.stderr) return 1 - finally: - if "conn" in locals(): - conn.close() - if response.status >= 400: - print(f"{file_path} validation failed with HTTP {response.status}.", file=sys.stderr) + validate_request = request.Request(CODECOV_VALIDATE_URL, data=request_body, method="POST") + try: + with request.urlopen(validate_request, timeout=NETWORK_TIMEOUT_SECONDS) as response: # noqa: S310 + body = response.read().decode("utf-8", errors="replace").strip() + except error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace").strip() + print(f"{file_path} validation failed with HTTP {exc.code}.", file=sys.stderr) if body: print(body, file=sys.stderr) return 1 + except (error.URLError, TimeoutError, OSError) as exc: + print(f"Unable to reach Codecov validation endpoint: {exc}", file=sys.stderr) + return 1 if body: print(body) diff --git a/tests/unit/test_check_codecov_yaml.py b/tests/unit/test_check_codecov_yaml.py new file mode 100644 index 0000000..0d4f398 --- /dev/null +++ b/tests/unit/test_check_codecov_yaml.py @@ -0,0 +1,97 @@ +"""Tests for scripts/check_codecov_yaml.py.""" + +import importlib.util +import io +from pathlib import Path +from urllib import error + +SCRIPT_PATH = Path(__file__).resolve().parents[2] / "scripts" / "check_codecov_yaml.py" +SPEC = importlib.util.spec_from_file_location("check_codecov_yaml", SCRIPT_PATH) +MODULE = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(MODULE) + + +class _FakeResponse: + """Minimal context manager for urllib responses.""" + + def __init__(self, body: bytes): + self._body = body + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def read(self): + return self._body + + +def test_validate_codecov_yaml_posts_to_expected_endpoint(tmp_path, monkeypatch, capsys): + file_path = tmp_path / "codecov.yml" + file_path.write_text("coverage:\n status: off\n") + captured: dict[str, object] = {} + + def _fake_urlopen(req, timeout): + captured["url"] = req.full_url + captured["method"] = req.get_method() + captured["data"] = req.data + captured["timeout"] = timeout + return _FakeResponse(b"Valid!") + + monkeypatch.setattr(MODULE.request, "urlopen", _fake_urlopen) + + assert MODULE.validate_codecov_yaml(file_path) == 0 + assert captured == { + "url": "https://codecov.io/validate", + "method": "POST", + "data": file_path.read_bytes(), + "timeout": MODULE.NETWORK_TIMEOUT_SECONDS, + } + output = capsys.readouterr() + assert "is valid." in output.out + assert output.err == "" + + +def test_validate_codecov_yaml_http_error(tmp_path, monkeypatch, capsys): + file_path = tmp_path / "codecov.yml" + file_path.write_text("bad: yaml\n") + + def _raise_http_error(*_args, **_kwargs): + raise error.HTTPError( + url=MODULE.CODECOV_VALIDATE_URL, + code=400, + msg="Bad Request", + hdrs=None, + fp=io.BytesIO(b"Invalid YAML"), + ) + + monkeypatch.setattr(MODULE.request, "urlopen", _raise_http_error) + + assert MODULE.validate_codecov_yaml(file_path) == 1 + output = capsys.readouterr() + assert "validation failed with HTTP 400" in output.err + assert "Invalid YAML" in output.err + + +def test_validate_codecov_yaml_network_error(tmp_path, monkeypatch, capsys): + file_path = tmp_path / "codecov.yml" + file_path.write_text("coverage: {}\n") + + def _raise_url_error(*_args, **_kwargs): + raise error.URLError("network down") + + monkeypatch.setattr(MODULE.request, "urlopen", _raise_url_error) + + assert MODULE.validate_codecov_yaml(file_path) == 1 + output = capsys.readouterr() + assert "Unable to reach Codecov validation endpoint" in output.err + + +def test_validate_codecov_yaml_file_read_error(tmp_path, capsys): + missing_path = tmp_path / "missing-codecov.yml" + + assert MODULE.validate_codecov_yaml(missing_path) == 1 + output = capsys.readouterr() + assert f"Unable to read {missing_path}" in output.err From 3506af9c956e97f0ffe2e1590adb73a1129d0402 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:11:49 +0000 Subject: [PATCH 4/6] Remove Codecov validator unit tests per review Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com> --- tests/unit/test_check_codecov_yaml.py | 97 --------------------------- 1 file changed, 97 deletions(-) delete mode 100644 tests/unit/test_check_codecov_yaml.py diff --git a/tests/unit/test_check_codecov_yaml.py b/tests/unit/test_check_codecov_yaml.py deleted file mode 100644 index 0d4f398..0000000 --- a/tests/unit/test_check_codecov_yaml.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Tests for scripts/check_codecov_yaml.py.""" - -import importlib.util -import io -from pathlib import Path -from urllib import error - -SCRIPT_PATH = Path(__file__).resolve().parents[2] / "scripts" / "check_codecov_yaml.py" -SPEC = importlib.util.spec_from_file_location("check_codecov_yaml", SCRIPT_PATH) -MODULE = importlib.util.module_from_spec(SPEC) -assert SPEC.loader is not None -SPEC.loader.exec_module(MODULE) - - -class _FakeResponse: - """Minimal context manager for urllib responses.""" - - def __init__(self, body: bytes): - self._body = body - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return False - - def read(self): - return self._body - - -def test_validate_codecov_yaml_posts_to_expected_endpoint(tmp_path, monkeypatch, capsys): - file_path = tmp_path / "codecov.yml" - file_path.write_text("coverage:\n status: off\n") - captured: dict[str, object] = {} - - def _fake_urlopen(req, timeout): - captured["url"] = req.full_url - captured["method"] = req.get_method() - captured["data"] = req.data - captured["timeout"] = timeout - return _FakeResponse(b"Valid!") - - monkeypatch.setattr(MODULE.request, "urlopen", _fake_urlopen) - - assert MODULE.validate_codecov_yaml(file_path) == 0 - assert captured == { - "url": "https://codecov.io/validate", - "method": "POST", - "data": file_path.read_bytes(), - "timeout": MODULE.NETWORK_TIMEOUT_SECONDS, - } - output = capsys.readouterr() - assert "is valid." in output.out - assert output.err == "" - - -def test_validate_codecov_yaml_http_error(tmp_path, monkeypatch, capsys): - file_path = tmp_path / "codecov.yml" - file_path.write_text("bad: yaml\n") - - def _raise_http_error(*_args, **_kwargs): - raise error.HTTPError( - url=MODULE.CODECOV_VALIDATE_URL, - code=400, - msg="Bad Request", - hdrs=None, - fp=io.BytesIO(b"Invalid YAML"), - ) - - monkeypatch.setattr(MODULE.request, "urlopen", _raise_http_error) - - assert MODULE.validate_codecov_yaml(file_path) == 1 - output = capsys.readouterr() - assert "validation failed with HTTP 400" in output.err - assert "Invalid YAML" in output.err - - -def test_validate_codecov_yaml_network_error(tmp_path, monkeypatch, capsys): - file_path = tmp_path / "codecov.yml" - file_path.write_text("coverage: {}\n") - - def _raise_url_error(*_args, **_kwargs): - raise error.URLError("network down") - - monkeypatch.setattr(MODULE.request, "urlopen", _raise_url_error) - - assert MODULE.validate_codecov_yaml(file_path) == 1 - output = capsys.readouterr() - assert "Unable to reach Codecov validation endpoint" in output.err - - -def test_validate_codecov_yaml_file_read_error(tmp_path, capsys): - missing_path = tmp_path / "missing-codecov.yml" - - assert MODULE.validate_codecov_yaml(missing_path) == 1 - output = capsys.readouterr() - assert f"Unable to read {missing_path}" in output.err From 5e02e8138053844617fc01de6edda29c28029fe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:27:41 +0000 Subject: [PATCH 5/6] Skip networked Codecov hook in pre-commit.ci Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0621ed..0eb0704 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ ci: autofix_prs: false autoupdate_schedule: "quarterly" - skip: [no-commit-to-branch] + skip: [no-commit-to-branch, validate-codecov-yaml] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 From 6c7dfef04c619521aafb7a98a00b49ced4b23e77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:33:54 +0000 Subject: [PATCH 6/6] Fix Ruff S310 violation in Codecov hook Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com> --- scripts/check_codecov_yaml.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/check_codecov_yaml.py b/scripts/check_codecov_yaml.py index fc2987c..2a87556 100644 --- a/scripts/check_codecov_yaml.py +++ b/scripts/check_codecov_yaml.py @@ -20,9 +20,12 @@ def validate_codecov_yaml(file_path: Path) -> int: print(f"Unable to read {file_path}: {exc}", file=sys.stderr) return 1 - validate_request = request.Request(CODECOV_VALIDATE_URL, data=request_body, method="POST") try: - with request.urlopen(validate_request, timeout=NETWORK_TIMEOUT_SECONDS) as response: # noqa: S310 + with request.urlopen( # noqa: S310 + CODECOV_VALIDATE_URL, + data=request_body, + timeout=NETWORK_TIMEOUT_SECONDS, + ) as response: body = response.read().decode("utf-8", errors="replace").strip() except error.HTTPError as exc: body = exc.read().decode("utf-8", errors="replace").strip()