09e9f3545f
CodeQL / Analyze (push) Waiting to run
dependency-audit / pip-audit (push) Waiting to run
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
259 lines
9.4 KiB
Python
259 lines
9.4 KiB
Python
"""Tests for ``scripts/check_coverage_thresholds.py``.
|
|
|
|
Covers both the original drift check and the per-file floor extension.
|
|
The script is imported via spec-loading rather than ``from
|
|
scripts.check_coverage_thresholds import main`` because ``scripts/`` is not
|
|
a package in this repo.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.repo_lint
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
SCRIPT_PATH = REPO_ROOT / "scripts" / "check_coverage_thresholds.py"
|
|
|
|
|
|
def _load_module():
|
|
spec = importlib.util.spec_from_file_location("check_coverage_thresholds", SCRIPT_PATH)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def script():
|
|
return _load_module()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Original Phase-1 drift check (regression guard for the existing behavior)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _write_pyproject(tmp_path: Path, fail_under: int = 90, per_file: dict | None = None) -> Path:
|
|
body = f"""\
|
|
[tool.coverage.report]
|
|
fail_under = {fail_under}
|
|
"""
|
|
if per_file:
|
|
body += "\n[tool.notebooklm.per_file_coverage_floors]\n"
|
|
for k, v in per_file.items():
|
|
body += f'"{k}" = {v}\n'
|
|
p = tmp_path / "pyproject.toml"
|
|
p.write_text(body, encoding="utf-8")
|
|
return p
|
|
|
|
|
|
def _write_workflow(tmp_path: Path, threshold: int = 90) -> Path:
|
|
p = tmp_path / "test.yml"
|
|
p.write_text(
|
|
f"jobs:\n test:\n steps:\n - run: pytest --cov-fail-under={threshold}\n",
|
|
encoding="utf-8",
|
|
)
|
|
return p
|
|
|
|
|
|
def test_original_drift_match(tmp_path, capsys, script):
|
|
pp = _write_pyproject(tmp_path, fail_under=90)
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml)])
|
|
assert rc == 0
|
|
out = capsys.readouterr().out
|
|
assert "OK" in out and "90%" in out
|
|
|
|
|
|
def test_original_drift_mismatch_fails(tmp_path, capsys, script):
|
|
pp = _write_pyproject(tmp_path, fail_under=90)
|
|
yml = _write_workflow(tmp_path, threshold=70)
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml)])
|
|
assert rc == 1
|
|
err = capsys.readouterr().err
|
|
assert "DRIFT" in err
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# per-file floor enforcement
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _write_coverage_json(tmp_path: Path, files: dict[str, float]) -> Path:
|
|
"""Build a minimal coverage.json with the requested per-file percentages."""
|
|
payload = {
|
|
"files": {path: {"summary": {"percent_covered": pct}} for path, pct in files.items()}
|
|
}
|
|
p = tmp_path / "coverage.json"
|
|
p.write_text(json.dumps(payload), encoding="utf-8")
|
|
return p
|
|
|
|
|
|
def test_per_file_floors_all_met(tmp_path, capsys, script):
|
|
pp = _write_pyproject(
|
|
tmp_path,
|
|
fail_under=90,
|
|
per_file={"src/notebooklm/cli/doctor.py": 60, "src/notebooklm/cli/profile.py": 70},
|
|
)
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(
|
|
tmp_path,
|
|
{
|
|
"src/notebooklm/cli/doctor.py": 65.0,
|
|
"src/notebooklm/cli/profile.py": 75.0,
|
|
},
|
|
)
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(cov)])
|
|
assert rc == 0
|
|
out = capsys.readouterr().out
|
|
assert "2 per-file floor(s) all met" in out
|
|
|
|
|
|
def test_per_file_floor_breach_fails_with_filename(tmp_path, capsys, script):
|
|
pp = _write_pyproject(
|
|
tmp_path,
|
|
fail_under=90,
|
|
per_file={"src/notebooklm/cli/doctor.py": 80},
|
|
)
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(tmp_path, {"src/notebooklm/cli/doctor.py": 65.0})
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(cov)])
|
|
assert rc == 1
|
|
err = capsys.readouterr().err
|
|
assert "PER-FILE COVERAGE FLOOR BREACH" in err
|
|
assert "src/notebooklm/cli/doctor.py" in err
|
|
assert "65.00" in err
|
|
assert "80%" in err
|
|
|
|
|
|
def test_per_file_missing_from_coverage_json_fails(tmp_path, capsys, script):
|
|
"""A guarded file with no measurement is itself a CI failure."""
|
|
pp = _write_pyproject(
|
|
tmp_path,
|
|
fail_under=90,
|
|
per_file={"src/notebooklm/cli/doctor.py": 60, "src/notebooklm/renamed.py": 60},
|
|
)
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(tmp_path, {"src/notebooklm/cli/doctor.py": 65.0})
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(cov)])
|
|
assert rc == 1
|
|
err = capsys.readouterr().err
|
|
assert "MISSING" in err
|
|
assert "src/notebooklm/renamed.py" in err
|
|
|
|
|
|
def test_per_file_no_floors_table_passes(tmp_path, capsys, script):
|
|
pp = _write_pyproject(tmp_path, fail_under=90) # no per_file table
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(tmp_path, {"src/notebooklm/cli/doctor.py": 0.0})
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(cov)])
|
|
assert rc == 0
|
|
out = capsys.readouterr().out
|
|
assert "no [tool.notebooklm.per_file_coverage_floors]" in out
|
|
|
|
|
|
def test_coverage_json_missing_returns_2(tmp_path, capsys, script):
|
|
pp = _write_pyproject(tmp_path, fail_under=90, per_file={"foo.py": 0})
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
rc = script.main(
|
|
[
|
|
"--pyproject",
|
|
str(pp),
|
|
"--workflow",
|
|
str(yml),
|
|
"--coverage-json",
|
|
str(tmp_path / "no-such.json"),
|
|
]
|
|
)
|
|
assert rc == 2
|
|
|
|
|
|
def test_per_file_floors_non_table_returns_2(tmp_path, capsys, script):
|
|
"""A non-table ``per_file_coverage_floors`` (string, list, etc.) is a
|
|
misconfiguration that must not silently bypass enforcement."""
|
|
pp = tmp_path / "pyproject.toml"
|
|
pp.write_text(
|
|
"[tool.coverage.report]\nfail_under = 90\n\n"
|
|
'[tool.notebooklm]\nper_file_coverage_floors = "oops"\n',
|
|
encoding="utf-8",
|
|
)
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(tmp_path, {"src/notebooklm/cli/doctor.py": 50.0})
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(cov)])
|
|
assert rc == 2
|
|
err = capsys.readouterr().err
|
|
assert "must be a TOML table" in err
|
|
|
|
|
|
def test_per_file_floor_non_numeric_returns_2(tmp_path, capsys, script):
|
|
"""A non-numeric floor value (string, etc.) yields exit 2 with context, not a crash."""
|
|
pp = _write_pyproject(
|
|
tmp_path,
|
|
fail_under=90,
|
|
per_file={"src/notebooklm/cli/doctor.py": '"sixty"'}, # raw TOML — string value
|
|
)
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(tmp_path, {"src/notebooklm/cli/doctor.py": 70.0})
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(cov)])
|
|
assert rc == 2
|
|
err = capsys.readouterr().err
|
|
assert "could not be compared" in err
|
|
assert "src/notebooklm/cli/doctor.py" in err
|
|
|
|
|
|
def test_coverage_json_files_wrong_shape_returns_2(tmp_path, capsys, script):
|
|
"""``coverage.json`` with ``files`` as something other than an object → exit 2."""
|
|
pp = _write_pyproject(tmp_path, fail_under=90, per_file={"foo.py": 0})
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
bad = tmp_path / "coverage.json"
|
|
bad.write_text(json.dumps({"files": []}), encoding="utf-8") # list, not dict
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(bad)])
|
|
assert rc == 2
|
|
assert "'files' must be an object map" in capsys.readouterr().err
|
|
|
|
|
|
def test_coverage_json_malformed_returns_2(tmp_path, capsys, script):
|
|
pp = _write_pyproject(tmp_path, fail_under=90, per_file={"foo.py": 0})
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
bad = tmp_path / "coverage.json"
|
|
bad.write_text("{not-json", encoding="utf-8")
|
|
rc = script.main(["--pyproject", str(pp), "--workflow", str(yml), "--coverage-json", str(bad)])
|
|
assert rc == 2
|
|
assert "malformed" in capsys.readouterr().err
|
|
|
|
|
|
def test_per_file_real_baselines_pass_against_repo_pyproject(tmp_path, capsys, script):
|
|
"""Round-trip: the floors checked into pyproject.toml pass against a fresh
|
|
coverage.json that records the floor exact value (sanity check that the
|
|
parser+comparator agree on the data shape that ``coverage json`` emits).
|
|
"""
|
|
real_pp = REPO_ROOT / "pyproject.toml"
|
|
yml = _write_workflow(tmp_path, threshold=90)
|
|
cov = _write_coverage_json(
|
|
tmp_path,
|
|
{
|
|
# Match the floors checked in — every path at exactly its floor
|
|
# should pass (>= comparison).
|
|
"src/notebooklm/__main__.py": 0.0,
|
|
"src/notebooklm/cli/_firefox_containers.py": 95.0,
|
|
"src/notebooklm/cli/doctor_cmd.py": 63.0,
|
|
"src/notebooklm/cli/profile_cmd.py": 74.0,
|
|
"src/notebooklm/cli/session_cmd.py": 83.0,
|
|
},
|
|
)
|
|
rc = script.main(
|
|
["--pyproject", str(real_pp), "--workflow", str(yml), "--coverage-json", str(cov)]
|
|
)
|
|
assert rc == 0
|
|
|
|
|
|
def teardown_module():
|
|
"""Drop the spec-loaded module so other tests aren't affected."""
|
|
sys.modules.pop("check_coverage_thresholds", None)
|