aboutsummaryrefslogtreecommitdiff
path: root/modules/coverage-core.el
Commit message (Collapse)AuthorAgeFilesLines
* docs(load-graph): classify dev, diff, help, lint, and VC modulesCraig Jennings13 days1-0/+9
| | | | | | Fifth classification batch: the development-workflow entry points and package config — coverage-core, coverage-elisp, dev-fkeys, diff-config, help-config, help-utils, flycheck-config, test-runner, vc-config. I annotated each header, added a Batch 5 table to the inventory, and extended the validation allowlist. 42 of 102 modules are now classified. Two more hidden dependencies turned up, both about cj/custom-keymap. dev-fkeys repeats the custom-buffer-file boundp shim for its C-; P binding. flycheck-config binds (:map cj/custom-keymap ...) through use-package without requiring keybindings, so it fails to load standalone. Both recorded for the Phase 2 dependency pass.
* refactor(system-lib): extract cj/process-output-or-error and ↵Craig Jennings2026-05-101-15/+3
| | | | | | | | | | | | cj/git-output-or-error from coverage-core Phase 2.3 of utility-consolidation. `cj/--coverage-git-string' was a generic argv-based runner ("run program, return stdout, raise user-error on non-zero with status+output in the message") trapped inside coverage-core. Lift the generic shape into `cj/process-output-or-error' and add `cj/git-output-or-error' as a one-line wrapper that supplies "git" as the program. Both live in system-lib.el. Future callers I have in mind: reconcile-open-repos shell-style git calls (the high-priority data-safety task), vc-config clipboard cloning, mail integrations that touch git for commit signatures. Six Normal/Boundary/Error tests cover success/no-args/non-zero-exit for the generic runner, the user-error message content (program name, exit status, trimmed output), and the git wrapper's program argument routing. Migrate coverage-core's `cj/--coverage-git-merge-base' and `cj/--coverage-git-diff' to call the new git wrapper. Drop the local `cj/--coverage-git-string' definition. Add `(require \='system-lib)' to coverage-core.el per the Phase 2 exit criterion.
* refactor: invoke git via argv in coverage diff helpersCraig Jennings2026-05-031-14/+44
| | | | | | | | | | | | `coverage-core.el` was running git through `shell-command-to-string`, which has two practical problems for central tooling: shell parsing surfaces (especially the `$(git merge-base ...)` substitution), and silent failure modes when git exits non-zero (the bad output just becomes empty parse results). I extracted three small helpers. `cj/--coverage-git-string` runs git via `process-file` against a temp buffer and signals `user-error` on non-zero exit, with the argv, status, and trimmed output included. `cj/--coverage-git-merge-base` does its own `git merge-base HEAD <base>` invocation. `cj/--coverage-git-diff` is the diff wrapper that always appends `--unified=0`. `cj/--coverage-changed-lines` now uses `pcase` over the scope symbol and composes the helpers. Branch-vs-main and branch-vs-parent compute the merge-base in a separate call before running `git diff <merge-base>..HEAD`, with no shell substitution involved. One behavior change is worth flagging. A git failure used to disappear into an empty hash table. It now signals a `user-error` with the failing command, exit status, and git's stderr output. Tests: I added two argv-boundary cases (working-tree and branch-vs-parent both assert the exact argv list seen) plus a non-zero-exit case that asserts the user-error path. The existing `test-coverage-core--command.el` smoke test gets its `shell-command-to-string` stub upgraded to a `process-file` stub.
* feat(coverage): add whole-project scope to cj/coverage-reportCraig Jennings2026-04-231-6/+110
| | | | | | | | | | | | | | | Adds a fifth entry to the scope completing-read menu: "Whole project — all executable lines". Uses the existing cj/coverage-report flow, so the user still hits F7 and picks from the menu; the command dispatches based on the chosen scope. Two new pure helpers back the scope: - cj/--coverage-simplecov-executable-lines parses the simplecov JSON and returns every executable line per file (both hit lines and 0-hit lines, excluding null/non-executable entries). Symmetric with cj/--coverage-parse-simplecov, which returns only hit lines. - cj/--coverage-format-summary renders intersect records as a per-file percentage summary sorted ascending by coverage (worst-covered first). Used instead of the line-detail format-report because an entire project's uncovered lines would be thousands of entries. cj/--coverage-read-and-display now branches on scope: whole-project feeds executable-lines as the "changed" input to intersect; diff-aware scopes still shell git diff as before. cj/--coverage-render-to-buffer branches similarly to pick the format helper. Tests cover the two new helpers: Normal (basic extraction, sorted output, percentages), Boundary (all-null coverage, multiple test-name keys unioned, empty records, not-tracked files excluded), and Error (missing file signals user-error). Verified end-to-end on the current .coverage/simplecov.json: 2717 of 4559 lines covered across 44 files, sorted from keybindings.el at 0% up through high-coverage modules.
* feat(coverage): add cj/coverage-report command and F7 bindingCraig Jennings2026-04-231-0/+108
| | | | | | | | | | | | | | | | | | | Completes the coverage v1 user-facing path. cj/coverage-report is the interactive entry point: 1. Resolves the backend for the current project (honoring cj/coverage-backend from .dir-locals.el). 2. Prompts for a git-diff scope via completing-read (Working tree, Staged, Branch vs parent, Branch vs main). 3. Reads the cached simplecov report, intersects with the diff, renders records into a *Coverage Report* buffer. 4. If the report doesn't exist, prompts to run coverage first. With a prefix argument, re-runs regardless. The report buffer uses cj/coverage-report-mode, a compilation-mode derivative. Uncovered-line entries are formatted as path:line: uncovered so the standard gnu compilation-error-regexp-alist picks them up for next-error navigation. That means M-g n, M-g p, and C-x backtick walk through uncovered lines from any buffer without switching focus. F7 is bound to the command globally, matching the F-key layout ticket's design (F4 compile+run, F5 debug, F6 test, F7 coverage). Added to init.el: (require 'coverage-core) + (require 'coverage-elisp). Tests cover the pure scope-label helpers (label to symbol, symbol to label, roundtrip) plus a smoke test that exercises the full command with stubbed backend, stubbed completing-read, stubbed shell-command-to-string, and a prepared simplecov fixture. Coverage v1 is now functionally complete: make coverage produces the report, F7 drives the interactive flow.
* feat(coverage): add format-report helper for the report bufferCraig Jennings2026-04-231-0/+70
| | | | | | | | | | | | | | Pure helper that renders intersect records into the text shown in the coverage report buffer. Takes the list of per-file plists from cj/--coverage-intersect and a scope label, returns the formatted string. Output has three sections depending on what's present: - "Uncovered lines" — one line per uncovered line, formatted as "<path>:<line>: uncovered" so compilation-mode's default regex picks them up for next-error navigation. - "Not tracked" — files changed in the diff but absent from the coverage data (READMEs, test files, config). - "Fully covered" — tracked files where every changed line is covered. Files with empty :changed-lines (deletion-only hunks) are omitted. Summary counts cover only tracked files, so an all-README change shows "0 of 0" rather than a misleading percentage over nothing. Tests cover Normal (partial, fully covered, mixed sections), Boundary (empty records, 100% coverage with no uncovered section, only-not-tracked case, deletion-only exclusion), and the output format that next-error relies on.
* feat(coverage): wire make coverage target + simplecov pipelineCraig Jennings2026-04-221-41/+45
| | | | | | | | | | | | | | | | Completes the coverage v1 pipeline by adding the Makefile target, the undercover driver script, the exclusion list, and the .gitignore entry. Uses simplecov JSON rather than LCOV as the collection format. The LCOV vs simplecov choice: Undercover's :merge-report t option only supports simplecov. Since the pipeline runs tests per-file (matching test-unit's isolation pattern) and accumulates coverage across runs, merge-report is required. LCOV is better-supported by external coverage viewers, but for a primarily interactive workflow the on-disk format is an internal detail. Other moves in this commit: - Renamed cj/--coverage-parse-lcov to cj/--coverage-parse-simplecov and rewrote its tests for the JSON schema. Same signature, same semantics (file to set of covered lines), different parser. - Renamed the backend protocol's :lcov-path key to :report-path, format-neutral and matching the renamed cj/--coverage-elisp-report-path function. - The coverage target deletes modules/*.elc before running so undercover can instrument the .el sources. Without this, byte-compiled versions shadow the instrumentation and only a handful of pre-loaded modules end up with coverage data. - Excluded tests/test-all-comp-errors.el from make coverage runs. That test byte-compiles every module, which fails under undercover's instrumentation. Excluded only from coverage. Normal make test still runs it. - Updated docs/design/coverage.org to reflect the simplecov pivot with a historical note on why we moved off LCOV. Verified end-to-end: make coverage produces .coverage/simplecov.json with 2717 of 4559 executable lines hit across 44 tracked modules.
* feat(coverage): add backend registryCraig Jennings2026-04-221-0/+53
| | | | | | | | | | | | | | | The coverage-core module now has a registry protocol so per-language backends can plug in without touching the core. A backend is a plist with :name, :detect, :run, and :lcov-path. cj/coverage-register-backend appends to cj/coverage-backends, or replaces an existing entry with the same :name at its original position (so first-registered wins on ties). cj/--coverage-backend-for-project resolves which backend applies to a project root. Resolution order: 1. An OVERRIDE argument (typically buffer-local cj/coverage-backend from .dir-locals.el) wins if supplied, and errors if it names an unregistered backend. 2. Otherwise, walk the registry in order and return the first backend whose :detect returns non-nil for the given root. Tests cover Normal (register and retrieve, re-register replaces in place, first detect wins), Boundary (empty registry, no match, override bypasses detect, detect receives the root), and Error (override names an unknown backend). With the registry in place, the elisp backend (and later python / typescript / go) can self-register on load without any changes to coverage-core.
* feat(coverage): add intersect helper to combine LCOV with diffCraig Jennings2026-04-221-0/+52
| | | | | | | | | | Third and final pure helper for the coverage-report command. Takes the hash tables produced by parse-lcov and parse-diff-output and returns per-file records ready for the report buffer. Output is a list of plists sorted by file path. Each record has :path, :changed-lines, :covered-lines, :uncovered-lines, and :tracked. A file that appears in the diff but not in the LCOV data is :tracked nil with both line lists empty. That way the reporter can distinguish "coverage isn't looking at this file" (README edits, test files, config) from "tests didn't exercise this code." Tests cover Normal (all covered, partial, multiple files sorted), Boundary (file not tracked, tracked file with no covered lines, empty changed-lines from deletion-only hunks, empty inputs), and Error (nil inputs return an empty list instead of erroring). With this helper in place, the core data pipeline is complete: LCOV file + git diff scope go in, per-file records come out. Next up is the backend registry and the elisp backend, then the cj/coverage-report command ties it all together.
* feat(coverage): add changed-lines helper and diff parserCraig Jennings2026-04-221-0/+64
| | | | | | | | | | | | Second of three pure helpers for the coverage-report command. cj/--coverage-parse-diff-output is pure. It takes a git unified-diff string and returns a hash table of file to set of added or modified line numbers (based on the +new_start,new_count hunk headers). Files with deletion-only hunks appear in the result with an empty set, so reporters can distinguish "coverage not tracked" from "no changes touched this file." cj/--coverage-changed-lines wraps that parser with scope dispatch. Scopes are working-tree, staged, branch-vs-main, and branch-vs-parent. Branch-vs-parent takes an optional BASE arg; if omitted, falls back to @{upstream}. Unknown scopes signal user-error. Tests cover Normal (single hunk, multiple files), Boundary (new file via @@ -0,0, deletion-only, binary markers, single-line hunks without a count, empty input), and Error (malformed hunk headers skipped; unknown scope). Git invocation is stubbed via cl-letf in the smoke test so the parser logic is exercised without shelling out. Part of the coverage-core work per docs/design/coverage.org.
* feat(coverage): add cj/--coverage-parse-lcov helperCraig Jennings2026-04-221-0/+58
First of three pure helpers for the coverage-report command. Reads an LCOV file and returns a hash table of file to set of covered line numbers. Only the SF, DA, and end_of_record fields are interpreted. Other LCOV fields (FN, FNDA, LF, LH, BRDA) are ignored. Malformed DA lines are skipped silently so partial runs still yield usable data. Tests cover Normal (single file, multiple files, mixed hit counts), Boundary (empty file, spaces in path, extra fields, all-zero hits), and Error (missing file, malformed DA lines). Part of the coverage-core work per docs/design/coverage.org.