From e38448d6fa697e44f739311c6ff1e4a5a9230224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:22:58 +0200 Subject: [PATCH 1/9] quality: add source-scoped changed-executable-line coverage report (#651) --- scripts/changed_line_coverage.py | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 scripts/changed_line_coverage.py diff --git a/scripts/changed_line_coverage.py b/scripts/changed_line_coverage.py new file mode 100644 index 000000000..19225e577 --- /dev/null +++ b/scripts/changed_line_coverage.py @@ -0,0 +1,132 @@ +"""Informational changed-executable-line coverage for the two #651 B1 pilots. + +This is deliberately *not* total/PR-wide coverage: only lines in the explicitly +instrumented files count. Non-executable diff lines are excluded and unavailable +data is reported as unavailable, never as a fabricated 0% or 100%. +""" + +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path + +HUNK = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@") + + +def added_lines(patch: str) -> dict[str, set[int]]: + """Return new-side added line numbers from Git's zero-context diff.""" + result: dict[str, set[int]] = {} + current: str | None = None + for line in patch.splitlines(): + if line.startswith("+++ "): + name = line[4:] + current = name[2:] if name.startswith("b/") else None + elif current is not None: + match = HUNK.match(line) + if match: + start = int(match.group(1)) + count = int(match.group(2)) if match.group(2) is not None else 1 + if count: + result.setdefault(current, set()).update(range(start, start + count)) + return result + + +def _find_entry(report: dict, source: str, kind: str) -> dict | None: + files = report["files"] if kind == "coveragepy" else { + key: value for key, value in report.items() if key != "total" + } + matches = [ + value for key, value in files.items() + if key.replace("\\", "/") == source + or key.replace("\\", "/").endswith("/" + source) + ] + if len(matches) > 1: + raise ValueError(f"Ambiguous coverage source: {source}") + return matches[0] if matches else None + + +def executable_lines(entry: dict, kind: str) -> tuple[set[int], set[int]] | None: + """Return (executable, covered) lines, or None if detail is unavailable.""" + if kind == "coveragepy": + if "executed_lines" not in entry or "missing_lines" not in entry: + return None + covered = set(map(int, entry["executed_lines"])) + missing = set(map(int, entry["missing_lines"])) + return covered | missing, covered + lines = entry.get("l") + if not isinstance(lines, dict) or not lines: + return None + return {int(n) for n in lines}, {int(n) for n, hits in lines.items() if hits > 0} + + +def changed_line_report( + patch: str, report: dict, kind: str, sources: list[str] +) -> str: + """Write a bounded Markdown summary without treating comments as misses.""" + touched = added_lines(patch) + rows = [ + "### Changed executable lines (instrumented pilot modules only)", + "", + "| Module | Added executable lines covered |", + "| --- | ---: |", + ] + covered_total = measured_total = 0 + unavailable = False + for source in sources: + added = touched.get(source, set()) + if not added: + rows.append(f"| \`{source}\` | No added lines in this pilot |") + continue + entry = _find_entry(report, source, kind) + detail = executable_lines(entry, kind) if entry is not None else None + if detail is None: + unavailable = True + rows.append(f"| \`{source}\` | Unavailable (no line-level coverage data) |") + continue + executable, covered = detail + eligible = added & executable + hit = len(eligible & covered) + covered_total += hit + measured_total += len(eligible) + if eligible: + rows.append(f"| \`{source}\` | {hit}/{len(eligible)} ({hit / len(eligible):.0%}) |") + else: + rows.append(f"| \`{source}\` | No added executable lines |") + overall = ( + "Unavailable for some changed pilot files" + if unavailable else + f"{covered_total}/{measured_total} ({covered_total / measured_total:.0%})" + if measured_total else "No added executable lines in pilot scope" + ) + rows.extend([ + f"| **Total (measurable pilot lines only)** | **{overall}** |", + "", + "Informational; no threshold. Counts only newly added executable lines in", + "the selected pilot modules, not total PR coverage, and excludes comments,", + "blank lines, deleted lines, uninstrumented modules and branch outcomes.", + "An unavailable source is never silently counted as covered.", + "", + ]) + return "\n".join(rows) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--format", choices=("coveragepy", "istanbul"), required=True) + parser.add_argument("--report", type=Path, required=True) + parser.add_argument("--diff", type=Path, required=True) + parser.add_argument("--source", action="append", required=True) + parser.add_argument("--summary-file", type=Path, required=True) + args = parser.parse_args() + patch = args.diff.read_text(encoding="utf-8") + report = json.loads(args.report.read_text(encoding="utf-8")) + summary = changed_line_report(patch, report, args.format, args.source) + print(summary) + with args.summary_file.open("a", encoding="utf-8") as output: + output.write(summary) + + +if __name__ == "__main__": + main() From 37fc26fac5d6fa9d3f624673f3c521f415def783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:23:18 +0200 Subject: [PATCH 2/9] quality: render plain Markdown code spans in coverage summary (#651) --- scripts/changed_line_coverage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/changed_line_coverage.py b/scripts/changed_line_coverage.py index 19225e577..9a3f6c2b7 100644 --- a/scripts/changed_line_coverage.py +++ b/scripts/changed_line_coverage.py @@ -77,13 +77,13 @@ def changed_line_report( for source in sources: added = touched.get(source, set()) if not added: - rows.append(f"| \`{source}\` | No added lines in this pilot |") + rows.append(f"| `{source}` | No added lines in this pilot |") continue entry = _find_entry(report, source, kind) detail = executable_lines(entry, kind) if entry is not None else None if detail is None: unavailable = True - rows.append(f"| \`{source}\` | Unavailable (no line-level coverage data) |") + rows.append(f"| `{source}` | Unavailable (no line-level coverage data) |") continue executable, covered = detail eligible = added & executable @@ -91,9 +91,9 @@ def changed_line_report( covered_total += hit measured_total += len(eligible) if eligible: - rows.append(f"| \`{source}\` | {hit}/{len(eligible)} ({hit / len(eligible):.0%}) |") + rows.append(f"| `{source}` | {hit}/{len(eligible)} ({hit / len(eligible):.0%}) |") else: - rows.append(f"| \`{source}\` | No added executable lines |") + rows.append(f"| `{source}` | No added executable lines |") overall = ( "Unavailable for some changed pilot files" if unavailable else From 43c085e5ff1516f29ea12a995d888018b516f3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:23:41 +0200 Subject: [PATCH 3/9] test: cover diff hunks and Python/Vitest changed-line semantics (#651) --- tests/test_changed_line_coverage.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/test_changed_line_coverage.py diff --git a/tests/test_changed_line_coverage.py b/tests/test_changed_line_coverage.py new file mode 100644 index 000000000..f2b6f1339 --- /dev/null +++ b/tests/test_changed_line_coverage.py @@ -0,0 +1,93 @@ +"""Synthetic, deterministic regression tests for focused changed-line coverage.""" + +from __future__ import annotations + +import unittest + +from scripts.changed_line_coverage import added_lines, changed_line_report, executable_lines + +PYTHON_SOURCE = "backend/domain/text_identity.py" +TS_SOURCE = "frontend-app/src/workspace/tree.ts" + + +def _diff(path: str, first: int, count: int) -> str: + return ( + f"diff --git a/{path} b/{path}\n" + f"--- a/{path}\n+++ b/{path}\n" + f"@@ -1,0 +{first},{count} @@\n" + + "".join("+line\n" for _ in range(count)) + ) + + +class ChangedLineCoverageTests(unittest.TestCase): + def test_zero_context_hunks_multiple_files_and_deletions(self): + patch = ( + _diff(PYTHON_SOURCE, 8, 3) + + f"@@ -16 +19 @@\n+replacement\n" + + f"diff --git a/docs/readme.md b/docs/readme.md\n" + + "--- a/docs/readme.md\n+++ b/docs/readme.md\n" + + "@@ -10 +10,0 @@\n-deleted\n" + + _diff(TS_SOURCE, 30, 1) + ) + self.assertEqual( + added_lines(patch), + {PYTHON_SOURCE: {8, 9, 10, 19}, TS_SOURCE: {30}}, + ) + + def test_python_ignores_nonexecutables_and_reports_partial_coverage(self): + patch = _diff(PYTHON_SOURCE, 8, 4) + report = { + "files": { + PYTHON_SOURCE: { + "executed_lines": [8, 14], + "missing_lines": [10, 15], + } + } + } + result = changed_line_report(patch, report, "coveragepy", [PYTHON_SOURCE]) + self.assertIn("1/2 (50%)", result) + self.assertIn("excludes comments", result) + + def test_istanbul_report_uses_line_hits_and_path_suffix(self): + patch = _diff(TS_SOURCE, 30, 4) + report = { + "/runner/work/prks/" + TS_SOURCE: { + "l": {"30": 5, "31": 0, "33": 2, "40": 0}, + }, + "total": {"lines": {}}, + } + result = changed_line_report(patch, report, "istanbul", [TS_SOURCE]) + self.assertIn("2/3 (67%)", result) + + def test_unchanged_pilot_sources_do_not_claim_zero_coverage(self): + report = {"files": {}} + result = changed_line_report(_diff("docs/note.md", 1, 1), report, "coveragepy", [PYTHON_SOURCE]) + self.assertIn("No added executable lines in pilot scope", result) + self.assertNotIn("(100%)", result) + + def test_missing_changed_file_coverage_is_explicitly_unavailable(self): + result = changed_line_report( + _diff(PYTHON_SOURCE, 2, 1), {"files": {}}, "coveragepy", [PYTHON_SOURCE] + ) + self.assertIn("Unavailable for some changed pilot files", result) + self.assertNotIn("0/1", result) + + def test_coverage_without_line_level_detail_is_unavailable(self): + self.assertIsNone(executable_lines({"covered_lines": 1}, "coveragepy")) + self.assertIsNone(executable_lines({"lines": {"pct": 100}}, "istanbul")) + + def test_ambiguous_coverage_paths_raise(self): + report = { + "files": { + "/runner/one/" + PYTHON_SOURCE: {"executed_lines": [], "missing_lines": [2]}, + "/runner/two/" + PYTHON_SOURCE: {"executed_lines": [], "missing_lines": [2]}, + } + } + with self.assertRaisesRegex(ValueError, "Ambiguous"): + changed_line_report( + _diff(PYTHON_SOURCE, 2, 1), report, "coveragepy", [PYTHON_SOURCE] + ) + + +if __name__ == "__main__": + unittest.main() From 945215ebb79e761a8ef90b0da93b2c357ad5a04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:24:04 +0200 Subject: [PATCH 4/9] ci: report changed executable lines for scoped Python coverage pilot (#651) --- .github/workflows/python-coverage-pilot.yml | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/python-coverage-pilot.yml b/.github/workflows/python-coverage-pilot.yml index 2460ac150..f4e3e339a 100644 --- a/.github/workflows/python-coverage-pilot.yml +++ b/.github/workflows/python-coverage-pilot.yml @@ -10,6 +10,8 @@ on: - "backend/research_markup.py" - "tests/test_text_identity.py" - "tests/fixtures/text_identity_vectors.json" + - "scripts/changed_line_coverage.py" + - "tests/test_changed_line_coverage.py" workflow_dispatch: permissions: @@ -28,6 +30,8 @@ jobs: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # PR merge first parent is the baseline for changed executable lines. + fetch-depth: 2 persist-credentials: false - name: Set up Python @@ -83,3 +87,24 @@ jobs: with Path(os.environ["GITHUB_STEP_SUMMARY"]).open("a", encoding="utf-8") as out: out.write(summary) PY + + - name: Report newly changed executable lines (informational) + shell: bash + run: | + set -euo pipefail + python -m unittest tests.test_changed_line_coverage + if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then + echo "No changed-line comparison on manual coverage runs." >> "$GITHUB_STEP_SUMMARY" + elif git rev-parse --verify 'HEAD^1^{commit}' >/dev/null 2>&1; then + patch="$RUNNER_TEMP/prks-python-changed.diff" + git diff --no-ext-diff --unified=0 HEAD^1 HEAD -- \ + backend/domain/text_identity.py > "$patch" + python scripts/changed_line_coverage.py \ + --format coveragepy \ + --report "$RUNNER_TEMP/prks-coverage.json" \ + --diff "$patch" \ + --source backend/domain/text_identity.py \ + --summary-file "$GITHUB_STEP_SUMMARY" + else + echo "Changed-line comparison unavailable (missing PR merge parent); no ratio inferred." >> "$GITHUB_STEP_SUMMARY" + fi From ac85e1f28b5e22e9c13832ded984c6ff5955bac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:24:23 +0200 Subject: [PATCH 5/9] ci: report changed executable lines for scoped Vitest coverage pilot (#651) --- .github/workflows/vitest-coverage-pilot.yml | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/workflows/vitest-coverage-pilot.yml b/.github/workflows/vitest-coverage-pilot.yml index 4e7dc17e3..73fc84e5e 100644 --- a/.github/workflows/vitest-coverage-pilot.yml +++ b/.github/workflows/vitest-coverage-pilot.yml @@ -22,6 +22,8 @@ on: - "frontend-app/vite.config.ts" - "frontend-app/package.json" - "frontend-app/package-lock.json" + - "scripts/changed_line_coverage.py" + - "tests/test_changed_line_coverage.py" workflow_dispatch: permissions: @@ -40,6 +42,8 @@ jobs: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + # PR merge first parent is the changed-line comparison baseline. + fetch-depth: 2 persist-credentials: false - name: Set up Node.js @@ -68,6 +72,7 @@ jobs: --coverage.include='src/{lifecycle/tab-leave,lifecycle/owner-resource,query/retry,workspace/transitions,workspace/invariants,workspace/tree,workspace/derive,workspace/commands,workspace/snapshot}.ts' \ --coverage.reporter=text \ --coverage.reporter=json-summary \ + --coverage.reporter=json \ --coverage.reportsDirectory="$RUNNER_TEMP/prks-vitest-coverage" node --input-type=module <<'NODE' import { readFileSync, appendFileSync } from 'node:fs' @@ -128,3 +133,41 @@ jobs: console.log(summary) appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary, 'utf8') NODE + + - name: Report newly changed executable lines (informational) + working-directory: frontend-app + shell: bash + run: | + set -euo pipefail + python3 -m unittest discover -s ../tests -p test_changed_line_coverage.py + if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then + echo "No changed-line comparison on manual coverage runs." >> "$GITHUB_STEP_SUMMARY" + elif git rev-parse --verify 'HEAD^1^{commit}' >/dev/null 2>&1; then + patch="$RUNNER_TEMP/prks-vitest-changed.diff" + git diff --no-ext-diff --unified=0 HEAD^1 HEAD -- \ + frontend-app/src/lifecycle/tab-leave.ts \ + frontend-app/src/lifecycle/owner-resource.ts \ + frontend-app/src/query/retry.ts \ + frontend-app/src/workspace/transitions.ts \ + frontend-app/src/workspace/invariants.ts \ + frontend-app/src/workspace/tree.ts \ + frontend-app/src/workspace/derive.ts \ + frontend-app/src/workspace/commands.ts \ + frontend-app/src/workspace/snapshot.ts > "$patch" + python3 ../scripts/changed_line_coverage.py \ + --format istanbul \ + --report "$RUNNER_TEMP/prks-vitest-coverage/coverage-final.json" \ + --diff "$patch" \ + --source frontend-app/src/lifecycle/tab-leave.ts \ + --source frontend-app/src/lifecycle/owner-resource.ts \ + --source frontend-app/src/query/retry.ts \ + --source frontend-app/src/workspace/transitions.ts \ + --source frontend-app/src/workspace/invariants.ts \ + --source frontend-app/src/workspace/tree.ts \ + --source frontend-app/src/workspace/derive.ts \ + --source frontend-app/src/workspace/commands.ts \ + --source frontend-app/src/workspace/snapshot.ts \ + --summary-file "$GITHUB_STEP_SUMMARY" + else + echo "Changed-line comparison unavailable (missing PR merge parent); no ratio inferred." >> "$GITHUB_STEP_SUMMARY" + fi From 2fd9e29d3530c187a5c89b2d2b2f0b7ef455257d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:24:38 +0200 Subject: [PATCH 6/9] test: make changed-line fixtures importable from either workflow cwd (#651) --- tests/test_changed_line_coverage.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_changed_line_coverage.py b/tests/test_changed_line_coverage.py index f2b6f1339..063b497e0 100644 --- a/tests/test_changed_line_coverage.py +++ b/tests/test_changed_line_coverage.py @@ -2,7 +2,13 @@ from __future__ import annotations +import sys import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) from scripts.changed_line_coverage import added_lines, changed_line_report, executable_lines From b9b78cc11dc21a935fe60eda757238e52991dff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:24:51 +0200 Subject: [PATCH 7/9] ci: diff from repository root in Vitest coverage reporter (#651) --- .github/workflows/vitest-coverage-pilot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/vitest-coverage-pilot.yml b/.github/workflows/vitest-coverage-pilot.yml index 73fc84e5e..66cae1662 100644 --- a/.github/workflows/vitest-coverage-pilot.yml +++ b/.github/workflows/vitest-coverage-pilot.yml @@ -142,9 +142,9 @@ jobs: python3 -m unittest discover -s ../tests -p test_changed_line_coverage.py if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then echo "No changed-line comparison on manual coverage runs." >> "$GITHUB_STEP_SUMMARY" - elif git rev-parse --verify 'HEAD^1^{commit}' >/dev/null 2>&1; then + elif git -C .. rev-parse --verify 'HEAD^1^{commit}' >/dev/null 2>&1; then patch="$RUNNER_TEMP/prks-vitest-changed.diff" - git diff --no-ext-diff --unified=0 HEAD^1 HEAD -- \ + git -C .. diff --no-ext-diff --unified=0 HEAD^1 HEAD -- \ frontend-app/src/lifecycle/tab-leave.ts \ frontend-app/src/lifecycle/owner-resource.ts \ frontend-app/src/query/retry.ts \ From 4a0c8437c12280a5cd54fea97d831ebef9ad4b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:26:34 +0200 Subject: [PATCH 8/9] quality: read actual Vitest Istanbul statement maps when line map is absent (#651) --- scripts/changed_line_coverage.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/changed_line_coverage.py b/scripts/changed_line_coverage.py index 9a3f6c2b7..70d76a55b 100644 --- a/scripts/changed_line_coverage.py +++ b/scripts/changed_line_coverage.py @@ -55,10 +55,24 @@ def executable_lines(entry: dict, kind: str) -> tuple[set[int], set[int]] | None covered = set(map(int, entry["executed_lines"])) missing = set(map(int, entry["missing_lines"])) return covered | missing, covered + # Vitest's V8 provider uses Istanbul's JSON reporter. Unlike older + # Istanbul formats, coverage-final.json generally has statementMap/s + # but no precomputed line map. Combine statement hits on their start + # lines, using max so an uncovered statement cannot erase a covered one. + statements, hits = entry.get("statementMap"), entry.get("s") + if isinstance(statements, dict) and isinstance(hits, dict) and statements: + lines: dict[int, int] = {} + for statement_id, location in statements.items(): + if statement_id not in hits: + return None + line = int(location["start"]["line"]) + lines[line] = max(lines.get(line, 0), int(hits[statement_id])) + return set(lines), {line for line, count in lines.items() if count > 0} + # Older Istanbul JSON representations may include line hits directly. lines = entry.get("l") if not isinstance(lines, dict) or not lines: return None - return {int(n) for n in lines}, {int(n) for n, hits in lines.items() if hits > 0} + return {int(n) for n in lines}, {int(n) for n, count in lines.items() if count > 0} def changed_line_report( From 4bcd4acc30e1d0e976a9397916eb9efbdb81a996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Perovi=C4=87?= <46610174+Fooftilly@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:26:49 +0200 Subject: [PATCH 9/9] test: reproduce real Istanbul coverage-final statement maps (#651) --- tests/test_changed_line_coverage.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_changed_line_coverage.py b/tests/test_changed_line_coverage.py index 063b497e0..f0c9ecb4b 100644 --- a/tests/test_changed_line_coverage.py +++ b/tests/test_changed_line_coverage.py @@ -65,6 +65,32 @@ def test_istanbul_report_uses_line_hits_and_path_suffix(self): result = changed_line_report(patch, report, "istanbul", [TS_SOURCE]) self.assertIn("2/3 (67%)", result) + def test_vitest_actual_coverage_final_statement_map_without_line_hits(self): + # Istanbul's JSON reporter usually supplies statementMap + s, not l. + patch = _diff(TS_SOURCE, 30, 4) + report = { + "/runner/prks/" + TS_SOURCE: { + "statementMap": { + "0": {"start": {"line": 30, "column": 0}, "end": {"line": 30, "column": 8}}, + "1": {"start": {"line": 31, "column": 0}, "end": {"line": 31, "column": 8}}, + "2": {"start": {"line": 31, "column": 9}, "end": {"line": 31, "column": 20}}, + "3": {"start": {"line": 33, "column": 0}, "end": {"line": 33, "column": 8}}, + }, + "s": {"0": 4, "1": 0, "2": 3, "3": 0}, + } + } + result = changed_line_report(patch, report, "istanbul", [TS_SOURCE]) + self.assertIn("2/3 (67%)", result) + + def test_missing_istanbul_statement_hits_are_unavailable(self): + incomplete = { + "statementMap": { + "0": {"start": {"line": 30, "column": 0}, "end": {"line": 30, "column": 3}}, + }, + "s": {}, + } + self.assertIsNone(executable_lines(incomplete, "istanbul")) + def test_unchanged_pilot_sources_do_not_claim_zero_coverage(self): report = {"files": {}} result = changed_line_report(_diff("docs/note.md", 1, 1), report, "coveragepy", [PYTHON_SOURCE])