"""Tests for the separate line/branch coverage policy gate.""" from __future__ import annotations from scripts import check_coverage_thresholds as MODULE def _report(line: float = 55.0, branch: float = 42.0) -> dict[str, object]: return { "totals": { "percent_statements_covered": line, "percent_branches_covered": branch, }, "files": { "obliteratus/example.py": { "summary": {"percent_statements_covered": 70.0}, "executed_lines": [1, 2, 4], "missing_lines": [3], }, }, } def test_validate_coverage_accepts_exact_floors(): assert MODULE.validate_coverage( _report(), min_line=55.0, min_branch=42.0, ) == [] def test_validate_coverage_reports_each_regression(): failures = MODULE.validate_coverage( _report(line=54.9, branch=41.9), min_line=55.0, min_branch=42.0, ) assert failures == [ "line coverage 54.90% is below the 55.00% floor", "branch coverage 41.90% is below the 42.00% floor", ] def test_validate_coverage_rejects_malformed_totals(): assert MODULE.validate_coverage( {}, min_line=55.0, min_branch=42.0, ) == ["coverage report is missing the totals object"] def test_validate_coverage_rejects_non_numeric_metrics(): report = _report() report["totals"]["percent_statements_covered"] = True report["totals"]["percent_branches_covered"] = float("nan") assert MODULE.validate_coverage( report, min_line=55.0, min_branch=42.0, ) == [ "coverage report is missing numeric percent_statements_covered", "coverage report is missing numeric percent_branches_covered", ] def test_validate_coverage_enforces_critical_file_floors(): assert MODULE.validate_coverage( _report(), min_line=55.0, min_branch=42.0, file_floors={"obliteratus/example.py": 70.0}, ) == [] report = _report() report["files"]["obliteratus/example.py"]["summary"]["percent_statements_covered"] = 69.9 assert MODULE.validate_coverage( report, min_line=55.0, min_branch=42.0, file_floors={"obliteratus/example.py": 70.0, "missing.py": 70.0}, ) == [ "critical file obliteratus/example.py coverage 69.90% is below the 70.00% floor", "coverage report is missing critical file missing.py", ] def test_critical_file_validation_continues_and_rejects_malformed_values(): report = _report() report["files"]["bool.py"] = { "summary": {"percent_statements_covered": True}, } report["files"]["text.py"] = { "summary": {"percent_statements_covered": "70"}, } assert MODULE.validate_coverage( report, min_line=55.0, min_branch=42.0, file_floors={ "missing.py": 70.0, "bool.py": 70.0, "text.py": 70.0, }, ) == [ "coverage report is missing critical file missing.py", "coverage report is missing numeric line coverage for bool.py", "coverage report is missing numeric line coverage for text.py", ] def test_parse_changed_lines_and_measurement_ignore_non_executable_lines(): diff = """diff --git a/obliteratus/example.py b/obliteratus/example.py +++ b/obliteratus/example.py @@ -1,2 +1,4 @@ diff --git a/tests/test_example.py b/tests/test_example.py +++ b/tests/test_example.py @@ -0,0 +1,2 @@ """ changed = MODULE.parse_changed_lines(diff) assert changed == { "obliteratus/example.py": {1, 2, 3, 4}, "tests/test_example.py": {1, 2}, } assert MODULE.changed_line_coverage(_report(), changed) == (3, 4, 75.0) assert MODULE.validate_changed_coverage( _report(), changed, minimum=90.0, ) == ["changed-line coverage 75.00% (3/4) is below the 90.00% floor"] def test_changed_line_gate_passes_when_diff_has_no_measured_source(): assert MODULE.changed_line_coverage(_report(), {"tests/test_example.py": {1}}) == ( 0, 0, 100.0, ) def test_changed_line_parser_ignores_hunks_before_paths_and_defaults_count_to_one(): diff = """@@ -1 +99 @@ +++ b/obliteratus/example.py @@ -2 +3 @@ """ assert MODULE.parse_changed_lines(diff) == {"obliteratus/example.py": {3}} def test_changed_line_measurement_handles_missing_fields_and_multiple_files(): report = { "files": { "obliteratus/no-fields.py": {"summary": {}}, "obliteratus/first.py": { "executed_lines": [1], "missing_lines": [2], }, "obliteratus/second.py": { "executed_lines": [4], "missing_lines": [5], }, }, } changed = { "missing-entry.py": {1}, "obliteratus/no-fields.py": {1}, "obliteratus/first.py": {1, 2, 99}, "obliteratus/second.py": {4, 5, 100}, } assert MODULE.changed_line_coverage(report, changed) == (2, 4, 50.0) def test_changed_line_gate_accepts_exact_floor(): report = _report() report["files"]["obliteratus/example.py"]["executed_lines"] = list(range(1, 20)) report["files"]["obliteratus/example.py"]["missing_lines"] = [20] changed = {"obliteratus/example.py": set(range(1, 21))} assert MODULE.validate_changed_coverage( report, changed, minimum=95.0, ) == [] assert MODULE.validate_changed_coverage( report, changed, minimum=95.01, ) == ["changed-line coverage 95.00% (19/20) is below the 95.01% floor"] def _module_report( path: str = "obliteratus/example.py", *, covered_lines: int = 8, statements: int = 10, covered_branches: int = 3, branches: int = 4, ) -> dict[str, object]: return { "files": { path: { "summary": { "covered_lines": covered_lines, "num_statements": statements, "covered_branches": covered_branches, "num_branches": branches, }, }, }, } def test_parse_changed_modules_handles_nul_renames_and_filters_nonproduction(): raw = ( b"M\0obliteratus/current.py\0" b"A\0tests/test_current.py\0" b"D\0obliteratus/deleted.py\0" b"R100\0obliteratus/old.py\0obliteratus/new.py\0" b"R100\0obliteratus/removed.py\0docs/removed.py\0" b"C100\0obliteratus/source.py\0obliteratus/copied.py\0" ) assert MODULE.parse_changed_modules(raw) == [ MODULE.ChangedModule("M", "obliteratus/current.py", "obliteratus/current.py"), MODULE.ChangedModule("D", "obliteratus/deleted.py", "obliteratus/deleted.py"), MODULE.ChangedModule("R100", "obliteratus/old.py", "obliteratus/new.py"), MODULE.ChangedModule("D", "obliteratus/removed.py", "obliteratus/removed.py"), MODULE.ChangedModule("C100", None, "obliteratus/copied.py"), ] def test_parse_changed_modules_rejects_truncated_and_unknown_statuses(): import pytest with pytest.raises(ValueError, match="truncated"): MODULE.parse_changed_modules(b"R100\0obliteratus/old.py\0") with pytest.raises(ValueError, match="unsupported"): MODULE.parse_changed_modules(b"U\0obliteratus/example.py\0") def test_touched_module_gate_reports_line_and_branch_regressions_separately(): base = _module_report(covered_lines=9, covered_branches=4) head = _module_report(covered_lines=8, covered_branches=3) changes = [MODULE.ChangedModule("M", "obliteratus/example.py", "obliteratus/example.py")] assert MODULE.validate_touched_module_regression(head, base, changes) == [ "touched module obliteratus/example.py line coverage regressed from 90.00% to 80.00%", "touched module obliteratus/example.py branch coverage regressed from 100.00% to 75.00%", ] def test_touched_module_gate_applies_tolerances_to_existing_module(): base = _module_report(covered_lines=801, statements=1000, covered_branches=751, branches=1000) head = _module_report(covered_lines=800, statements=1000, covered_branches=750, branches=1000) changes = [MODULE.ChangedModule("M", "obliteratus/example.py", "obliteratus/example.py")] assert MODULE.validate_touched_module_regression( head, base, changes, line_tolerance=0.1, branch_tolerance=0.1, ) == [] assert len(MODULE.validate_touched_module_regression( head, base, changes, line_tolerance=0.09, branch_tolerance=0.09, )) == 2 def test_touched_module_gate_enforces_new_module_floors(): head = _module_report(covered_lines=7, statements=10, covered_branches=2, branches=4) changes = [MODULE.ChangedModule("A", None, "obliteratus/example.py")] assert MODULE.validate_touched_module_regression(head, {}, changes) == [ "new module obliteratus/example.py line coverage 70.00% is below the 80.00% floor", "new module obliteratus/example.py branch coverage 50.00% is below the 75.00% floor", ] def test_touched_module_gate_requires_floor_when_existing_module_adds_branches(): base = _module_report(covered_branches=0, branches=0) head = _module_report(covered_branches=2, branches=4) changes = [MODULE.ChangedModule("M", "obliteratus/example.py", "obliteratus/example.py")] assert MODULE.validate_touched_module_regression(head, base, changes) == [ "touched module obliteratus/example.py added branches at 50.00% coverage, " "below the 75.00% floor", ] def test_touched_module_gate_accepts_branch_removal_and_rename(): base = _module_report("obliteratus/old.py", covered_branches=3, branches=4) head = _module_report("obliteratus/new.py", covered_branches=0, branches=0) changes = [MODULE.ChangedModule("R100", "obliteratus/old.py", "obliteratus/new.py")] assert MODULE.validate_touched_module_regression(head, base, changes) == [] def test_touched_module_gate_rejects_missing_and_malformed_measurements(): changes = [MODULE.ChangedModule("M", "obliteratus/example.py", "obliteratus/example.py")] assert MODULE.validate_touched_module_regression({}, _module_report(), changes) == [ "coverage report is missing touched module obliteratus/example.py", ] malformed = _module_report() malformed["files"]["obliteratus/example.py"]["summary"]["covered_lines"] = True assert MODULE.validate_touched_module_regression(malformed, _module_report(), changes) == [ "coverage report has invalid covered_lines for touched module obliteratus/example.py", ] def test_touched_module_gate_rejects_missing_base_measurement(): changes = [MODULE.ChangedModule("M", "obliteratus/example.py", "obliteratus/example.py")] assert MODULE.validate_touched_module_regression(_module_report(), {}, changes) == [ "base coverage report is missing touched module obliteratus/example.py", ] def test_touched_module_gate_requires_reviewed_exception_for_deletion(): changes = [MODULE.ChangedModule("D", "obliteratus/example.py", "obliteratus/example.py")] assert MODULE.validate_touched_module_regression({}, _module_report(), changes) == [ "deleted production module obliteratus/example.py requires an explicit reviewed " "coverage-policy exception", ]