test(titan-iac): widen tracked quality coverage
This commit is contained in:
parent
5ebc320843
commit
387e104359
@ -16,20 +16,27 @@
|
|||||||
],
|
],
|
||||||
"managed_modules": [
|
"managed_modules": [
|
||||||
"ci/scripts/publish_test_metrics.py",
|
"ci/scripts/publish_test_metrics.py",
|
||||||
"services/mailu/scripts/mailu_sync.py",
|
"ci/scripts/publish_test_metrics_quality.py",
|
||||||
"testing/__init__.py",
|
"testing/__init__.py",
|
||||||
"testing/quality_contract.py",
|
"testing/quality_contract.py",
|
||||||
"testing/quality_docs.py",
|
"testing/quality_docs.py",
|
||||||
"testing/quality_hygiene.py",
|
"testing/quality_hygiene.py",
|
||||||
"testing/quality_coverage.py",
|
"testing/quality_coverage.py",
|
||||||
"testing/quality_gate.py"
|
"testing/quality_gate.py",
|
||||||
|
"ci/tests/glue/test_ariadne_schedules.py",
|
||||||
|
"ci/tests/glue/test_glue_metrics.py",
|
||||||
|
"testing/tests/test_publish_test_metrics.py",
|
||||||
|
"testing/tests/test_quality_contract.py",
|
||||||
|
"testing/tests/test_quality_gate.py"
|
||||||
],
|
],
|
||||||
"lint_paths": [
|
"lint_paths": [
|
||||||
"ci/scripts/publish_test_metrics.py",
|
"ci/scripts/publish_test_metrics.py",
|
||||||
|
"ci/scripts/publish_test_metrics_quality.py",
|
||||||
"ci/tests/glue",
|
"ci/tests/glue",
|
||||||
"scripts/tests",
|
"scripts/tests",
|
||||||
"services/comms/scripts/tests",
|
"services/comms/scripts/tests",
|
||||||
"services/mailu/scripts/mailu_sync.py",
|
"services/mailu/scripts/mailu_sync.py",
|
||||||
|
"testing/tests",
|
||||||
"testing"
|
"testing"
|
||||||
],
|
],
|
||||||
"pytest_suites": {
|
"pytest_suites": {
|
||||||
@ -70,6 +77,8 @@
|
|||||||
"hygiene",
|
"hygiene",
|
||||||
"unit",
|
"unit",
|
||||||
"coverage",
|
"coverage",
|
||||||
|
"sonarqube",
|
||||||
|
"ironbank",
|
||||||
"glue"
|
"glue"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -151,6 +160,7 @@
|
|||||||
"minimum_percent": 95.0,
|
"minimum_percent": 95.0,
|
||||||
"tracked_files": [
|
"tracked_files": [
|
||||||
"ci/scripts/publish_test_metrics.py",
|
"ci/scripts/publish_test_metrics.py",
|
||||||
|
"ci/scripts/publish_test_metrics_quality.py",
|
||||||
"testing/quality_contract.py",
|
"testing/quality_contract.py",
|
||||||
"testing/quality_docs.py",
|
"testing/quality_docs.py",
|
||||||
"testing/quality_hygiene.py",
|
"testing/quality_hygiene.py",
|
||||||
|
|||||||
105
testing/tests/test_publish_test_metrics_quality.py
Normal file
105
testing/tests/test_publish_test_metrics_quality.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"""Focused tests for publish_test_metrics quality helper fallbacks."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ci.scripts import publish_test_metrics_quality as quality_helpers
|
||||||
|
|
||||||
|
|
||||||
|
def test_infer_workspace_coverage_percent_handles_candidate_paths_and_parse_failures(tmp_path: Path) -> None:
|
||||||
|
"""Coverage inference should honor explicit XML hints and fail closed on bad XML."""
|
||||||
|
|
||||||
|
build_dir = tmp_path / "build"
|
||||||
|
build_dir.mkdir()
|
||||||
|
|
||||||
|
coverage_xml = build_dir / "custom-coverage.xml"
|
||||||
|
coverage_xml.write_text('<coverage line-rate="0.975" />', encoding="utf-8")
|
||||||
|
summary = {"results": [None, {"name": "coverage", "coverage_xml": str(coverage_xml)}]}
|
||||||
|
assert quality_helpers._infer_workspace_coverage_percent(summary, "build/default.xml") == 97.5
|
||||||
|
|
||||||
|
missing_xml = build_dir / "missing.xml"
|
||||||
|
assert quality_helpers._infer_workspace_coverage_percent({}, str(missing_xml)) == 0.0
|
||||||
|
|
||||||
|
no_rate_xml = build_dir / "no-rate.xml"
|
||||||
|
no_rate_xml.write_text("<coverage />", encoding="utf-8")
|
||||||
|
assert quality_helpers._infer_workspace_coverage_percent({}, str(no_rate_xml)) == 0.0
|
||||||
|
|
||||||
|
bad_xml = build_dir / "bad.xml"
|
||||||
|
bad_xml.write_text("<coverage", encoding="utf-8")
|
||||||
|
assert quality_helpers._infer_workspace_coverage_percent({}, str(bad_xml)) == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_infer_source_lines_over_500_counts_only_string_hygiene_issues() -> None:
|
||||||
|
"""LOC inference should ignore non-lists and only count explicit over-limit strings."""
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"results": [
|
||||||
|
None,
|
||||||
|
{"name": "docs", "issues": ["ignore me"]},
|
||||||
|
{"name": "loc", "issues": "not-a-list"},
|
||||||
|
{
|
||||||
|
"name": "hygiene",
|
||||||
|
"issues": [
|
||||||
|
"file exceeds 500 LOC: alpha.py (501)",
|
||||||
|
42,
|
||||||
|
"naming rule failed: beta.py",
|
||||||
|
"file exceeds 500 LOC: gamma.py (650)",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert quality_helpers._infer_source_lines_over_500(summary) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_check_statuses_uses_fallbacks_for_tests_docs_and_gate_glue() -> None:
|
||||||
|
"""Fallback status synthesis should derive missing checks from related signals."""
|
||||||
|
|
||||||
|
statuses = quality_helpers._build_check_statuses(
|
||||||
|
summary={
|
||||||
|
"results": [
|
||||||
|
{"name": "docs", "status": "ok"},
|
||||||
|
{"name": "smell", "status": "ok"},
|
||||||
|
{"name": "gate", "status": "warning"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tests={"tests": 3, "failures": 1, "errors": 0, "skipped": 0},
|
||||||
|
workspace_line_coverage_percent=0.0,
|
||||||
|
source_lines_over_500=0,
|
||||||
|
sonarqube_report={},
|
||||||
|
supply_chain_report={},
|
||||||
|
supply_chain_required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert statuses["tests"] == "failed"
|
||||||
|
assert statuses["coverage"] == "not_applicable"
|
||||||
|
assert statuses["loc"] == "ok"
|
||||||
|
assert statuses["docs_naming"] == "ok"
|
||||||
|
assert statuses["gate_glue"] == "failed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_check_statuses_preserves_explicit_loc_docs_and_glue_results() -> None:
|
||||||
|
"""Explicit canonical statuses should win over fallback inference."""
|
||||||
|
|
||||||
|
statuses = quality_helpers._build_check_statuses(
|
||||||
|
summary={
|
||||||
|
"results": [
|
||||||
|
{"name": "loc", "status": "ok"},
|
||||||
|
{"name": "docs_naming", "status": "ok"},
|
||||||
|
{"name": "gate_glue", "status": "ok"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tests={"tests": 0, "failures": 0, "errors": 0, "skipped": 0},
|
||||||
|
workspace_line_coverage_percent=96.0,
|
||||||
|
source_lines_over_500=9,
|
||||||
|
sonarqube_report={},
|
||||||
|
supply_chain_report={},
|
||||||
|
supply_chain_required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert statuses["tests"] == "not_applicable"
|
||||||
|
assert statuses["coverage"] == "ok"
|
||||||
|
assert statuses["loc"] == "ok"
|
||||||
|
assert statuses["docs_naming"] == "ok"
|
||||||
|
assert statuses["gate_glue"] == "ok"
|
||||||
76
testing/tests/test_quality_coverage_helpers.py
Normal file
76
testing/tests/test_quality_coverage_helpers.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""Focused coverage-helper tests for titan-iac quality modules."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from testing.quality_coverage import compute_workspace_line_coverage, run_check
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_workspace_line_coverage_handles_missing_xml(tmp_path: Path) -> None:
|
||||||
|
"""Missing coverage XML should produce a zero workspace coverage score."""
|
||||||
|
|
||||||
|
contract = {"coverage": {"tracked_files": ["managed.py"]}}
|
||||||
|
assert compute_workspace_line_coverage(contract, tmp_path, tmp_path / "missing.xml") == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_workspace_line_coverage_averages_present_tracked_files(tmp_path: Path) -> None:
|
||||||
|
"""Workspace coverage should average only tracked files that appear in the report."""
|
||||||
|
|
||||||
|
coverage_xml = tmp_path / "coverage.xml"
|
||||||
|
coverage_xml.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
<coverage>
|
||||||
|
<packages>
|
||||||
|
<package>
|
||||||
|
<classes>
|
||||||
|
<class filename="alpha.py" line-rate="1.0" />
|
||||||
|
<class filename="beta.py" line-rate="0.5" />
|
||||||
|
</classes>
|
||||||
|
</package>
|
||||||
|
</packages>
|
||||||
|
</coverage>
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
contract = {"coverage": {"tracked_files": ["alpha.py", "beta.py", "missing.py"]}}
|
||||||
|
assert compute_workspace_line_coverage(contract, tmp_path, coverage_xml) == 75.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_check_keeps_relative_names_when_source_roots_do_not_match(tmp_path: Path) -> None:
|
||||||
|
"""Relative filenames should remain relative when no declared source root contains them."""
|
||||||
|
|
||||||
|
coverage_xml = tmp_path / "coverage.xml"
|
||||||
|
unmatched_root = tmp_path / "other-root"
|
||||||
|
unmatched_root.mkdir()
|
||||||
|
coverage_xml.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""\
|
||||||
|
<coverage>
|
||||||
|
<sources>
|
||||||
|
<source>{unmatched_root}</source>
|
||||||
|
</sources>
|
||||||
|
<packages>
|
||||||
|
<package>
|
||||||
|
<classes>
|
||||||
|
<class filename="relative.py" line-rate="0.80" />
|
||||||
|
</classes>
|
||||||
|
</package>
|
||||||
|
</packages>
|
||||||
|
</coverage>
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
issues = run_check(
|
||||||
|
{"coverage": {"minimum_percent": 95.0, "tracked_files": ["relative.py"]}},
|
||||||
|
tmp_path,
|
||||||
|
coverage_xml,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert issues == ["coverage below 95.0%: relative.py (80.0%)"]
|
||||||
26
testing/tests/test_quality_hygiene_helpers.py
Normal file
26
testing/tests/test_quality_hygiene_helpers.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""Focused hygiene-helper tests for titan-iac quality modules."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from testing.quality_hygiene import count_files_over_line_limit
|
||||||
|
|
||||||
|
|
||||||
|
def test_count_files_over_line_limit_counts_only_long_matches(tmp_path: Path) -> None:
|
||||||
|
"""Only files beyond the configured limit should contribute to the LOC total."""
|
||||||
|
|
||||||
|
tests_dir = tmp_path / "tests"
|
||||||
|
tests_dir.mkdir()
|
||||||
|
(tests_dir / "test_short.py").write_text("line\n", encoding="utf-8")
|
||||||
|
(tests_dir / "test_long.py").write_text("line\n" * 4, encoding="utf-8")
|
||||||
|
(tests_dir / "notes.txt").write_text("line\n" * 8, encoding="utf-8")
|
||||||
|
|
||||||
|
contract = {
|
||||||
|
"hygiene": {
|
||||||
|
"max_lines": 3,
|
||||||
|
"line_limit_globs": ["tests/*.py"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert count_files_over_line_limit(contract, tmp_path) == 1
|
||||||
Loading…
x
Reference in New Issue
Block a user