From af478a42b18c4d5e0712c4cb43036126d36c56b5 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 31 May 2026 12:31:35 -0500 Subject: feat(python): add coverage-summary to the Python bundle Second language in the coverage-summary fan-out, after the Elisp pilot. Same kernel: a module no test imports never appears in coverage.py's report, so a line-weighted total skips it silently and the suite looks healthier than it is. This counts every source file on disk that's absent from the report as 0% and weights the project number by file, so untested modules stay visible. The script at languages/python/claude/scripts/coverage-summary.py parses coverage.py's JSON (files[path].summary.covered_lines / num_statements), resolves report paths against the report's directory since coverage records them relative to where it ran, and recurses the source dir for *.py. Unlike the Elisp version it doesn't print a per-file table, because coverage.py's own coverage report already does. The script adds the missing-file accounting that report lacks. It uses only the standard library, parsing the report rather than importing coverage. The Python run confirmed the plumbing from the pilot is genuinely generic. install-lang and sync deliver the script and the project-owned coverage-makefile.txt with no Python-specific code. The one gap I had to close: the Python bundle shipped without a gitignore-add.txt, so the .claude/ footprint wasn't ignored and the script would have been committable. Added one mirroring the Elisp footprint plus Python artifacts (__pycache__, .coverage, coverage.json). make test gained a languages/*/tests/test_*.py discovery path alongside the existing Elisp ERT one. Tests: 12 pytest covering the parser, the file-weighted number, and the missing-file detection including subpackage recursion, plus an install-lang check that the script lands in the gitignored footprint. I proved it against a report matching coverage.py's documented schema and the CLI end to end, but not against a live coverage json run, because coverage.py isn't installed in this repo's env. The first project to adopt it should sanity-check against a real report. --- languages/python/coverage-makefile.txt | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 languages/python/coverage-makefile.txt (limited to 'languages/python/coverage-makefile.txt') diff --git a/languages/python/coverage-makefile.txt b/languages/python/coverage-makefile.txt new file mode 100644 index 0000000..5764120 --- /dev/null +++ b/languages/python/coverage-makefile.txt @@ -0,0 +1,46 @@ +# Python coverage — Makefile fragment + setup recommendation +# +# This file is owned by the project, not the rulesets bundle. The bundle never +# edits your Makefile. Copy the two targets below into your own Makefile (and +# adjust the variables at the top), then delete this file or keep it as a note. +# +# What you get: +# make coverage runs the test suite under coverage.py, writes a JSON +# report, and prints coverage.py's own per-file table +# make coverage-summary prints a file-weighted project number and — the point +# — every source file on disk that no test imported, +# counted as 0%. +# +# Why the summary matters: a module no test imports never appears in coverage.py's +# output, so a line-weighted total silently skips it. The summary weights by file +# and counts a missing file as 0%, so untested modules stay visible. It does not +# reimplement the per-file table — `coverage report` already prints that. +# +# --------------------------------------------------------------------------- +# Prerequisite: coverage.py +# +# pip install coverage # or pytest-cov, if you prefer the pytest plugin +# +# The summary script itself needs nothing beyond the standard library — it parses +# the JSON report, it does not import coverage. +# --------------------------------------------------------------------------- + +# Variables — adjust to your layout. +PYTHON ?= python3 +SOURCE_DIR ?= src +COVERAGE_FILE ?= coverage.json +# The summary script ships with the bundle under .claude/scripts/ (gitignored). +COVERAGE_SUMMARY ?= .claude/scripts/coverage-summary.py + +coverage: + @$(PYTHON) -m coverage run --source=$(SOURCE_DIR) -m pytest + @$(PYTHON) -m coverage json -o $(COVERAGE_FILE) + @$(PYTHON) -m coverage report + @$(MAKE) coverage-summary + +coverage-summary: + @if [ ! -f $(COVERAGE_FILE) ]; then \ + echo "[!] No coverage file at $(COVERAGE_FILE). Run 'make coverage' first."; \ + exit 1; \ + fi + @$(PYTHON) $(COVERAGE_SUMMARY) $(COVERAGE_FILE) $(SOURCE_DIR) $(CURDIR) -- cgit v1.2.3