fix(ci): install coverage toml support

This commit is contained in:
rookiestar28
2026-04-21 00:44:23 +08:00
parent 2195e2d2a5
commit fc00538b43
8 changed files with 109 additions and 18 deletions
+3 -1
View File
@@ -94,8 +94,10 @@ jobs:
python -m pip install --upgrade pip
# Keep aligned with local pre-push/full-test scripts.
# aiohttp is required by multiple unit-test import paths.
# CRITICAL: Python 3.10 coverage reads pyproject.toml only when the
# TOML extra is present; do not downgrade this back to plain coverage.
python -m pip install -r requirements.txt
python -m pip install numpy pillow aiohttp coverage
python -m pip install numpy pillow aiohttp "coverage[toml]"
- name: R120 preflight
run: |
python scripts/preflight_check.py --strict
+5 -3
View File
@@ -218,9 +218,11 @@ if ! "$VENV_PY" -c "import defusedxml" >/dev/null 2>&1; then
echo "[pre-push] INFO: installing defusedxml into project venv ($VENV_DIR) ..." >&2
pip_install_or_fail "required for S85 fail-closed XML parsing paths/tests" defusedxml
fi
if ! "$VENV_PY" -c "import coverage" >/dev/null 2>&1; then
echo "[pre-push] INFO: installing coverage into project venv ($VENV_DIR) ..." >&2
pip_install_or_fail "required for backend coverage gate" coverage
if ! "$VENV_PY" -c "import sys, importlib.util; has_coverage = importlib.util.find_spec('coverage') is not None; has_toml = sys.version_info >= (3, 11) or importlib.util.find_spec('tomli') is not None; sys.exit(0 if has_coverage and has_toml else 1)" >/dev/null 2>&1; then
# CRITICAL: Python 3.10 coverage cannot read pyproject.toml without TOML
# support; plain coverage here causes local pass/CI fail drift.
echo "[pre-push] INFO: installing coverage[toml] into project venv ($VENV_DIR) ..." >&2
pip_install_or_fail "required for backend coverage gate" "coverage[toml]"
fi
require_cmd npm
+6 -1
View File
@@ -89,7 +89,12 @@ def decide_should_publish(
def _write_outputs(
*, output_path: Path | None, should_publish: bool, reason: str, current_version: str, previous_version: str | None
*,
output_path: Path | None,
should_publish: bool,
reason: str,
current_version: str,
previous_version: str | None,
) -> None:
lines = [
f"should_publish={'true' if should_publish else 'false'}",
+18
View File
@@ -7,6 +7,7 @@ coverage-governance reporting.
from __future__ import annotations
import argparse
import importlib.util
import subprocess
import sys
from pathlib import Path
@@ -17,6 +18,14 @@ def _run_command(command: list[str]) -> int:
return int(completed.returncode)
def _coverage_has_pyproject_toml_support() -> bool:
# IMPORTANT: keep this probe in sync with the CI/local bootstrap checks so
# Python 3.10 fails fast with a clear remediation instead of a coverage crash.
if sys.version_info >= (3, 11):
return True
return importlib.util.find_spec("tomli") is not None
def _build_unittest_args(args: argparse.Namespace) -> list[str]:
command = [
sys.executable,
@@ -65,6 +74,15 @@ def run_backend_coverage(argv: list[str] | None = None) -> int:
coverage_json = Path(args.coverage_json)
coverage_json.parent.mkdir(parents=True, exist_ok=True)
if not _coverage_has_pyproject_toml_support():
# CRITICAL: coverage reads repo config from pyproject.toml; Python 3.10
# needs the TOML extra or CI/local coverage gates fail before tests run.
print(
"Coverage pyproject support is unavailable on this interpreter. "
"Install with `coverage[toml]` before running the backend coverage gate."
)
return 2
erase_cmd = [sys.executable, "-m", "coverage", "erase"]
if (code := _run_command(erase_cmd)) != 0:
return code
+8 -6
View File
@@ -183,14 +183,16 @@ if (-not $hasDefusedXml) {
Invoke-Checked "pip install defusedxml" { & $venvPython -m pip install defusedxml }
}
$hasCoverage = $true
& $venvPython -c "import coverage" | Out-Null
$hasCoverageTomlSupport = $true
# CRITICAL: keep this aligned with CI/pre-push bootstrap so Python 3.10 lanes
# install coverage[toml] before the shared backend coverage gate runs.
& $venvPython -c "import sys, importlib.util; has_coverage = importlib.util.find_spec('coverage') is not None; has_toml = sys.version_info >= (3, 11) or importlib.util.find_spec('tomli') is not None; sys.exit(0 if has_coverage and has_toml else 1)" | Out-Null
if ($LASTEXITCODE -ne 0) {
$hasCoverage = $false
$hasCoverageTomlSupport = $false
}
if (-not $hasCoverage) {
Write-Host "[tests] Installing coverage into project venv (R184 backend coverage gate) ..."
Invoke-Checked "pip install coverage" { & $venvPython -m pip install coverage }
if (-not $hasCoverageTomlSupport) {
Write-Host "[tests] Installing coverage[toml] into project venv (R184 backend coverage gate) ..."
Invoke-Checked "pip install coverage[toml]" { & $venvPython -m pip install "coverage[toml]" }
}
# Ensure Node >= 18
@@ -4,18 +4,13 @@ import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "registry_publish_guard.py"
PUBLISH_WORKFLOW = ROOT / ".github" / "workflows" / "publish.yml"
def _pyproject_text(version: str) -> str:
return (
"[project]\n"
'name = "comfyui-openclaw"\n'
f'version = "{version}"\n'
)
return "[project]\n" 'name = "comfyui-openclaw"\n' f'version = "{version}"\n'
class RegistryPublishVersionGuardTests(unittest.TestCase):
@@ -23,7 +18,9 @@ class RegistryPublishVersionGuardTests(unittest.TestCase):
workflow = PUBLISH_WORKFLOW.read_text(encoding="utf-8")
self.assertIn("fetch-depth: 2", workflow)
self.assertIn("scripts/registry_publish_guard.py", workflow)
self.assertIn("if: steps.publish_guard.outputs.should_publish == 'true'", workflow)
self.assertIn(
"if: steps.publish_guard.outputs.should_publish == 'true'", workflow
)
def test_same_version_sets_should_publish_false(self):
with tempfile.TemporaryDirectory() as tmpdir:
+41
View File
@@ -0,0 +1,41 @@
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
class CoverageTomlParityContractTests(unittest.TestCase):
def test_ci_unit_test_lane_installs_coverage_with_toml_support(self):
ci_workflow = (ROOT / ".github" / "workflows" / "ci.yml").read_text(
encoding="utf-8"
)
self.assertIn(
'python -m pip install numpy pillow aiohttp "coverage[toml]"', ci_workflow
)
self.assertNotIn(
"python -m pip install numpy pillow aiohttp coverage\n", ci_workflow
)
def test_local_bootstrap_scripts_install_coverage_with_toml_support(self):
pre_push = (ROOT / "scripts" / "pre_push_checks.sh").read_text(encoding="utf-8")
windows_full = (ROOT / "scripts" / "run_full_tests_windows.ps1").read_text(
encoding="utf-8"
)
self.assertIn(
'pip_install_or_fail "required for backend coverage gate" "coverage[toml]"',
pre_push,
)
self.assertIn('Invoke-Checked "pip install coverage[toml]"', windows_full)
self.assertNotIn(
'pip_install_or_fail "required for backend coverage gate" coverage',
pre_push,
)
self.assertNotIn(
'Invoke-Checked "pip install coverage" { & $venvPython -m pip install coverage }',
windows_full,
)
if __name__ == "__main__":
unittest.main()
+24
View File
@@ -1,6 +1,7 @@
import subprocess
import sys
import unittest
from io import StringIO
from pathlib import Path
from unittest.mock import patch
@@ -68,6 +69,29 @@ class TestR184BackendCoverageGate(unittest.TestCase):
self.assertEqual(result, 1)
self.assertEqual(mock_run.call_count, 2)
@patch("scripts.run_backend_coverage._coverage_has_pyproject_toml_support")
@patch("scripts.run_backend_coverage.subprocess.run")
def test_missing_toml_support_fails_closed_before_running_coverage(
self, mock_run, mock_support
):
from scripts.run_backend_coverage import run_backend_coverage
mock_support.return_value = False
stdout = StringIO()
with patch("sys.stdout", stdout):
result = run_backend_coverage(
[
"--module",
"tests.test_r156_quality_governance",
"--coverage-json",
str(ROOT / ".tmp" / "coverage" / "missing_toml.json"),
]
)
self.assertEqual(result, 2)
self.assertEqual(mock_run.call_count, 0)
self.assertIn("coverage[toml]", stdout.getvalue())
if __name__ == "__main__":
unittest.main()