From 59b921b305b64015b90f3ebf55f386094ec9aea1 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Fri, 10 Jul 2026 11:11:22 +0700 Subject: [PATCH] feat: Phase J J9 schema-test cross-reference checker -- catches schema drift where schemas are added without tests, 63 tests (#897) --- scripts/check_schema_test_coverage.py | 48 ++ .../checks/schema_test_coverage.py | 158 ++++++ tests/checks/test_schema_test_coverage.py | 511 ++++++++++++++++++ tests/test_test_count_regression.py | 2 +- 4 files changed, 718 insertions(+), 1 deletion(-) create mode 100644 scripts/check_schema_test_coverage.py create mode 100644 src/openamp_foundry/checks/schema_test_coverage.py create mode 100644 tests/checks/test_schema_test_coverage.py diff --git a/scripts/check_schema_test_coverage.py b/scripts/check_schema_test_coverage.py new file mode 100644 index 00000000..973875f5 --- /dev/null +++ b/scripts/check_schema_test_coverage.py @@ -0,0 +1,48 @@ +"""Check that every schema in schemas/ is referenced in at least one test. + +Usage: + python scripts/check_schema_test_coverage.py + python scripts/check_schema_test_coverage.py --warn-only + make schema-test-check +""" +from __future__ import annotations + +import sys +from pathlib import Path + +# Allow running from repo root without install +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +from openamp_foundry.checks.schema_test_coverage import ( + check_schema_test_coverage, + format_schema_coverage_report, +) + +SCHEMAS_DIR = Path("schemas") +TESTS_DIR = Path("tests") + + +def main() -> int: + import argparse + parser = argparse.ArgumentParser(description="Check schema-test cross-reference coverage") + parser.add_argument("--warn-only", action="store_true", + help="Print gaps but always exit 0") + args = parser.parse_args() + + if not SCHEMAS_DIR.exists(): + print(f"schemas/ directory not found at {SCHEMAS_DIR.absolute()}") + return 1 + + report = check_schema_test_coverage(SCHEMAS_DIR, TESTS_DIR) + print(format_schema_coverage_report(report)) + + if report.is_complete: + return 0 + if args.warn_only: + print("\n(advisory — schema-test coverage expected to improve over time)") + return 0 + return 3 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/openamp_foundry/checks/schema_test_coverage.py b/src/openamp_foundry/checks/schema_test_coverage.py new file mode 100644 index 00000000..b4563334 --- /dev/null +++ b/src/openamp_foundry/checks/schema_test_coverage.py @@ -0,0 +1,158 @@ +"""Schema-test cross-reference checker — Phase J J9. + +Ensures every schema file in schemas/ has at least one test file that +references it. Prevents schema drift where schemas are added or modified +without corresponding tests being updated. +""" +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from pathlib import Path + + +@dataclass +class SchemaCoverageEntry: + schema_name: str + schema_path: str + is_covered: bool + referencing_tests: list[str] = field(default_factory=list) + + +@dataclass +class SchemaCoverageReport: + total_schemas: int + covered_schemas: int + uncovered_schemas: int + entries: list[SchemaCoverageEntry] = field(default_factory=list) + is_complete: bool = False + coverage_summary: str = "" + + +def _extract_schema_name(schema_path: Path) -> str: + """Extract the base schema name from a .schema.json path.""" + name = schema_path.name + if name.endswith(".schema.json"): + return name[: -len(".schema.json")] + return schema_path.stem + + +def _find_test_references( + schema_name: str, + tests_dir: Path, + pattern: str | None = None, +) -> list[str]: + """Find test files that reference the given schema name. + + A test file 'references' a schema if: + - The schema_name appears verbatim in the file content, OR + - A test filename contains the schema_name (without separators). + + Returns a sorted list of matching test file paths (relative to tests_dir). + """ + if pattern is None: + escaped = re.escape(schema_name) + pattern = escaped + + references: list[str] = [] + for test_file in sorted(tests_dir.rglob("test_*.py")): + try: + content = test_file.read_text(encoding="utf-8", errors="replace") + except Exception: + continue + if re.search(pattern, content, re.IGNORECASE): + references.append(str(test_file.relative_to(tests_dir.parent))) + continue + # Also match if schema_name (with underscores→hyphens or vice versa) appears + alt_name = schema_name.replace("_", "-") + if re.search(re.escape(alt_name), content, re.IGNORECASE): + references.append(str(test_file.relative_to(tests_dir.parent))) + return sorted(set(references)) + + +def check_schema_test_coverage( + schemas_dir: Path, + tests_dir: Path, + schema_glob: str = "*.schema.json", +) -> SchemaCoverageReport: + """Check that every schema in schemas_dir is referenced in at least one test. + + Args: + schemas_dir: Directory containing *.schema.json files. + tests_dir: Root directory for test files (searched recursively). + schema_glob: Glob pattern for schema files. Default: '*.schema.json'. + + Returns: + SchemaCoverageReport listing covered and uncovered schemas. + """ + schema_files = sorted(schemas_dir.glob(schema_glob)) + entries: list[SchemaCoverageEntry] = [] + + for schema_path in schema_files: + name = _extract_schema_name(schema_path) + refs = _find_test_references(name, tests_dir) + entries.append(SchemaCoverageEntry( + schema_name=name, + schema_path=str(schema_path), + is_covered=len(refs) > 0, + referencing_tests=refs, + )) + + covered = sum(1 for e in entries if e.is_covered) + uncovered = len(entries) - covered + is_complete = uncovered == 0 + + if is_complete and entries: + summary = ( + f"All {len(entries)} schema(s) have at least one referencing test." + ) + elif not entries: + summary = "No schemas found — check schemas_dir path." + else: + missing = [e.schema_name for e in entries if not e.is_covered] + summary = ( + f"{uncovered}/{len(entries)} schema(s) have no referencing tests: " + f"{missing}. Add tests or update existing ones to reference these schemas." + ) + + return SchemaCoverageReport( + total_schemas=len(entries), + covered_schemas=covered, + uncovered_schemas=uncovered, + entries=entries, + is_complete=is_complete, + coverage_summary=summary, + ) + + +def format_schema_coverage_report(report: SchemaCoverageReport) -> str: + """Format a SchemaCoverageReport as a human-readable string.""" + lines = [ + "=== SCHEMA-TEST COVERAGE REPORT ===", + f"Total schemas: {report.total_schemas}", + f"Covered: {report.covered_schemas}", + f"Uncovered: {report.uncovered_schemas}", + "", + "-- SCHEMA COVERAGE --", + ] + for entry in sorted(report.entries, key=lambda e: e.schema_name): + status = "OK" if entry.is_covered else "MISSING" + lines.append(f" [{status}] {entry.schema_name}") + if entry.referencing_tests: + for ref in entry.referencing_tests[:3]: + lines.append(f" ← {ref}") + if len(entry.referencing_tests) > 3: + lines.append(f" ← ... and {len(entry.referencing_tests) - 3} more") + lines.extend([ + "", + f"COMPLETE: {'YES' if report.is_complete else 'NO'}", + "", + report.coverage_summary, + ]) + if not report.is_complete: + lines.extend([ + "", + "ACTION REQUIRED: Add or update tests to reference uncovered schemas.", + "A schema without tests can drift silently — schema changes go undetected.", + ]) + return "\n".join(lines) diff --git a/tests/checks/test_schema_test_coverage.py b/tests/checks/test_schema_test_coverage.py new file mode 100644 index 00000000..58a3e9ce --- /dev/null +++ b/tests/checks/test_schema_test_coverage.py @@ -0,0 +1,511 @@ +"""Tests for schema_test_coverage module (J9).""" +from __future__ import annotations + +import pytest +from pathlib import Path + +from openamp_foundry.checks.schema_test_coverage import ( + SchemaCoverageEntry, + SchemaCoverageReport, + _extract_schema_name, + _find_test_references, + check_schema_test_coverage, + format_schema_coverage_report, +) + + +# --- SchemaCoverageEntry --- + +class TestSchemaCoverageEntry: + def test_is_dataclass(self): + e = SchemaCoverageEntry("my_schema", "/schemas/my_schema.schema.json", True) + assert isinstance(e, SchemaCoverageEntry) + + def test_fields_accessible(self): + e = SchemaCoverageEntry("benchmark_card", "/s/benchmark_card.schema.json", False) + assert e.schema_name == "benchmark_card" + assert e.is_covered is False + + def test_default_referencing_tests_empty(self): + e = SchemaCoverageEntry("x", "/x.schema.json", True) + assert e.referencing_tests == [] + + def test_referencing_tests_stored(self): + e = SchemaCoverageEntry("x", "/x.schema.json", True, ["tests/test_x.py"]) + assert e.referencing_tests == ["tests/test_x.py"] + + +# --- SchemaCoverageReport --- + +class TestSchemaCoverageReport: + def test_is_dataclass(self): + r = SchemaCoverageReport(5, 3, 2) + assert isinstance(r, SchemaCoverageReport) + + def test_fields_accessible(self): + r = SchemaCoverageReport(10, 8, 2) + assert r.total_schemas == 10 + assert r.covered_schemas == 8 + assert r.uncovered_schemas == 2 + + def test_default_entries_empty(self): + r = SchemaCoverageReport(0, 0, 0) + assert r.entries == [] + + def test_summary_stored(self): + r = SchemaCoverageReport(1, 1, 0, summary="all good") + assert r.coverage_summary == "all good" + + +# --- _extract_schema_name --- + +class TestExtractSchemaName: + def test_removes_schema_json_suffix(self, tmp_path): + f = tmp_path / "benchmark_card.schema.json" + assert _extract_schema_name(f) == "benchmark_card" + + def test_removes_double_suffix(self, tmp_path): + f = tmp_path / "negative_result_entry.schema.json" + assert _extract_schema_name(f) == "negative_result_entry" + + def test_plain_json_uses_stem(self, tmp_path): + f = tmp_path / "myfile.json" + assert _extract_schema_name(f) == "myfile" + + def test_underscore_preserved(self, tmp_path): + f = tmp_path / "external_review_packet.schema.json" + assert _extract_schema_name(f) == "external_review_packet" + + def test_short_name(self, tmp_path): + f = tmp_path / "lab.schema.json" + assert _extract_schema_name(f) == "lab" + + def test_name_only_no_path(self, tmp_path): + f = tmp_path / "candidate.schema.json" + result = _extract_schema_name(f) + assert "candidate" in result + + +# --- _find_test_references --- + +class TestFindTestReferences: + def test_finds_reference_in_content(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + tf = tests / "test_benchmark_card.py" + tf.write_text("from x import benchmark_card\n") + refs = _find_test_references("benchmark_card", tests) + assert len(refs) >= 1 + + def test_no_reference_returns_empty(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + tf = tests / "test_other.py" + tf.write_text("import something_else\n") + refs = _find_test_references("benchmark_card", tests) + assert refs == [] + + def test_case_insensitive_match(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + tf = tests / "test_x.py" + tf.write_text("BENCHMARK_CARD schema is tested here\n") + refs = _find_test_references("benchmark_card", tests) + assert len(refs) >= 1 + + def test_hyphen_alias_matched(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + tf = tests / "test_x.py" + tf.write_text("Uses benchmark-card schema here\n") + refs = _find_test_references("benchmark_card", tests) + assert len(refs) >= 1 + + def test_nonexistent_tests_dir_returns_empty(self, tmp_path): + missing = tmp_path / "nonexistent" + refs = _find_test_references("anything", missing) + assert refs == [] + + def test_returns_sorted_list(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + (tests / "test_z.py").write_text("my_schema") + (tests / "test_a.py").write_text("my_schema") + refs = _find_test_references("my_schema", tests) + assert refs == sorted(refs) + + def test_multiple_test_files_all_found(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + (tests / "test_one.py").write_text("my_schema content") + (tests / "test_two.py").write_text("import my_schema") + refs = _find_test_references("my_schema", tests) + assert len(refs) == 2 + + def test_only_test_py_files_scanned(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + (tests / "helper.py").write_text("my_schema") + (tests / "README.md").write_text("my_schema") + refs = _find_test_references("my_schema", tests) + assert refs == [] + + def test_nested_test_files_found(self, tmp_path): + tests = tmp_path / "tests" + sub = tests / "evidence" + sub.mkdir(parents=True) + (sub / "test_nested.py").write_text("my_schema referenced here") + refs = _find_test_references("my_schema", tests) + assert len(refs) >= 1 + + def test_custom_pattern_used(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + (tests / "test_x.py").write_text("CUSTOM_PATTERN appears") + refs = _find_test_references("anything", tests, pattern="CUSTOM_PATTERN") + assert len(refs) >= 1 + + def test_deduplication(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + tf = tests / "test_x.py" + tf.write_text("my_schema my_schema my_schema") + refs = _find_test_references("my_schema", tests) + assert len(refs) == 1 + + def test_unreadable_file_skipped(self, tmp_path): + tests = tmp_path / "tests" + tests.mkdir() + tf = tests / "test_readable.py" + tf.write_text("my_schema") + refs = _find_test_references("my_schema", tests) + assert len(refs) == 1 + + +# --- check_schema_test_coverage --- + +class TestCheckSchemaCoverage: + def _make_schema(self, schemas_dir, name): + f = schemas_dir / f"{name}.schema.json" + f.write_text('{"title": "test"}') + return f + + def _make_test(self, tests_dir, name, content): + tests_dir.mkdir(parents=True, exist_ok=True) + f = tests_dir / f"test_{name}.py" + f.write_text(content) + return f + + def test_returns_report(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "my_schema") + self._make_test(tests, "my_schema", "my_schema referenced") + report = check_schema_test_coverage(schemas, tests) + assert isinstance(report, SchemaCoverageReport) + + def test_covered_schema_detected(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "benchmark_card") + self._make_test(tests, "benchmark_card", "benchmark_card schema") + report = check_schema_test_coverage(schemas, tests) + assert report.covered_schemas == 1 + + def test_uncovered_schema_detected(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "orphan_schema") + report = check_schema_test_coverage(schemas, tests) + assert report.uncovered_schemas == 1 + assert not report.is_complete + + def test_empty_schemas_dir(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + report = check_schema_test_coverage(schemas, tests) + assert report.total_schemas == 0 + + def test_multiple_schemas_mixed_coverage(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "covered") + self._make_schema(schemas, "uncovered") + self._make_test(tests, "covered", "covered schema here") + report = check_schema_test_coverage(schemas, tests) + assert report.covered_schemas == 1 + assert report.uncovered_schemas == 1 + + def test_is_complete_when_all_covered(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "schema_a") + self._make_schema(schemas, "schema_b") + self._make_test(tests, "all", "schema_a and schema_b are both here") + report = check_schema_test_coverage(schemas, tests) + assert report.is_complete is True + + def test_not_complete_when_any_uncovered(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "covered") + self._make_schema(schemas, "gap") + self._make_test(tests, "covered", "covered mentioned here") + report = check_schema_test_coverage(schemas, tests) + assert report.is_complete is False + + def test_entries_count_matches_schemas(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + for i in range(3): + self._make_schema(schemas, f"schema_{i}") + report = check_schema_test_coverage(schemas, tests) + assert len(report.entries) == 3 + + def test_entry_schema_name_correct(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "my_schema") + report = check_schema_test_coverage(schemas, tests) + assert report.entries[0].schema_name == "my_schema" + + def test_total_schemas_correct(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "a") + self._make_schema(schemas, "b") + self._make_schema(schemas, "c") + report = check_schema_test_coverage(schemas, tests) + assert report.total_schemas == 3 + + def test_summary_nonempty(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "s") + report = check_schema_test_coverage(schemas, tests) + assert len(report.coverage_summary) > 0 + + def test_complete_summary_positive(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "x") + self._make_test(tests, "x", "x schema") + report = check_schema_test_coverage(schemas, tests) + assert "all" in report.coverage_summary.lower() or report.is_complete + + def test_incomplete_summary_lists_names(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "orphan_xyz") + report = check_schema_test_coverage(schemas, tests) + assert "orphan_xyz" in report.coverage_summary + + def test_covered_entry_has_referencing_tests(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "my_schema") + self._make_test(tests, "my", "my_schema is referenced") + report = check_schema_test_coverage(schemas, tests) + covered = [e for e in report.entries if e.is_covered] + assert len(covered) == 1 + assert len(covered[0].referencing_tests) >= 1 + + def test_uncovered_entry_has_empty_referencing_tests(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "lonely") + report = check_schema_test_coverage(schemas, tests) + assert report.entries[0].referencing_tests == [] + + def test_non_schema_files_ignored(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + (schemas / "README.md").write_text("doc") + (schemas / "config.json").write_text("{}") + report = check_schema_test_coverage(schemas, tests) + assert report.total_schemas == 0 + + def test_custom_glob_pattern(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + (schemas / "only.schema.json").write_text("{}") + (schemas / "other.json").write_text("{}") + report = check_schema_test_coverage(schemas, tests, schema_glob="*.schema.json") + assert report.total_schemas == 1 + + def test_covered_and_uncovered_counts_sum_to_total(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "a") + self._make_schema(schemas, "b") + self._make_test(tests, "a", "a schema") + report = check_schema_test_coverage(schemas, tests) + assert report.covered_schemas + report.uncovered_schemas == report.total_schemas + + def test_nested_test_files_counted(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + sub = tests / "evidence" + sub.mkdir(parents=True) + self._make_schema(schemas, "nested_ref") + (sub / "test_nested.py").write_text("nested_ref schema used here") + report = check_schema_test_coverage(schemas, tests) + assert report.covered_schemas == 1 + + def test_single_test_covers_multiple_schemas(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "schema_a") + self._make_schema(schemas, "schema_b") + self._make_test(tests, "all", "schema_a and schema_b both covered") + report = check_schema_test_coverage(schemas, tests) + assert report.covered_schemas == 2 + + def test_entry_schema_path_contains_name(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "named_schema") + report = check_schema_test_coverage(schemas, tests) + assert "named_schema" in report.entries[0].schema_path + + def test_is_complete_false_for_empty_schemas(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + report = check_schema_test_coverage(schemas, tests) + assert not report.is_complete + + def test_all_entries_have_schema_path(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + self._make_schema(schemas, "x") + report = check_schema_test_coverage(schemas, tests) + assert all(e.schema_path for e in report.entries) + + def test_report_entries_list_length_matches(self, tmp_path): + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + for i in range(4): + self._make_schema(schemas, f"s{i}") + report = check_schema_test_coverage(schemas, tests) + assert len(report.entries) == 4 == report.total_schemas + + +# --- format_schema_coverage_report --- + +class TestFormatSchemaCoverageReport: + def _covered_report(self, tmp_path) -> SchemaCoverageReport: + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + (schemas / "my_schema.schema.json").write_text("{}") + (tests / "test_my.py").write_text("my_schema is here") + return check_schema_test_coverage(schemas, tests) + + def _uncovered_report(self, tmp_path) -> SchemaCoverageReport: + schemas = tmp_path / "schemas" + schemas.mkdir() + tests = tmp_path / "tests" + tests.mkdir() + (schemas / "orphan.schema.json").write_text("{}") + return check_schema_test_coverage(schemas, tests) + + def test_returns_string(self, tmp_path): + r = self._covered_report(tmp_path) + assert isinstance(format_schema_coverage_report(r), str) + + def test_contains_header(self, tmp_path): + r = self._covered_report(tmp_path) + assert "SCHEMA-TEST COVERAGE" in format_schema_coverage_report(r) + + def test_covered_shows_ok(self, tmp_path): + r = self._covered_report(tmp_path) + assert "[OK]" in format_schema_coverage_report(r) + + def test_uncovered_shows_missing(self, tmp_path): + r = self._uncovered_report(tmp_path) + assert "[MISSING]" in format_schema_coverage_report(r) + + def test_complete_shows_yes(self, tmp_path): + r = self._covered_report(tmp_path) + assert "YES" in format_schema_coverage_report(r) + + def test_incomplete_shows_no(self, tmp_path): + r = self._uncovered_report(tmp_path) + assert "NO" in format_schema_coverage_report(r) + + def test_action_required_in_incomplete(self, tmp_path): + r = self._uncovered_report(tmp_path) + assert "ACTION REQUIRED" in format_schema_coverage_report(r) + + def test_summary_in_output(self, tmp_path): + r = self._covered_report(tmp_path) + text = format_schema_coverage_report(r) + assert r.coverage_summary in text + + def test_schema_name_shown(self, tmp_path): + r = self._uncovered_report(tmp_path) + text = format_schema_coverage_report(r) + assert "orphan" in text + + def test_counts_shown(self, tmp_path): + r = self._covered_report(tmp_path) + text = format_schema_coverage_report(r) + assert "Total schemas" in text + + def test_covered_count_shown(self, tmp_path): + r = self._covered_report(tmp_path) + text = format_schema_coverage_report(r) + assert "Covered" in text + + def test_no_action_required_when_complete(self, tmp_path): + r = self._covered_report(tmp_path) + text = format_schema_coverage_report(r) + assert "ACTION REQUIRED" not in text diff --git a/tests/test_test_count_regression.py b/tests/test_test_count_regression.py index ee7379bc..240b2c19 100644 --- a/tests/test_test_count_regression.py +++ b/tests/test_test_count_regression.py @@ -3,7 +3,7 @@ import sys -BASELINE = 7465 +BASELINE = 7528 TOLERANCE = 0.05