diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-22 17:03:33 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-22 17:03:33 -0500 |
| commit | 1097878bcb45a1c68ec5f9e44a727c2dd7e45725 (patch) | |
| tree | e39a9f4ca91363c9d4fbe2af2c3d04c4c708267d /modules/coverage-core.el | |
| parent | 83ac3201023f8736c234da27a0642f21786adcfc (diff) | |
| download | dotemacs-1097878bcb45a1c68ec5f9e44a727c2dd7e45725.tar.gz dotemacs-1097878bcb45a1c68ec5f9e44a727c2dd7e45725.zip | |
feat(coverage): add cj/--coverage-parse-lcov helper
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.
Diffstat (limited to 'modules/coverage-core.el')
| -rw-r--r-- | modules/coverage-core.el | 58 |
1 files changed, 58 insertions, 0 deletions
diff --git a/modules/coverage-core.el b/modules/coverage-core.el new file mode 100644 index 00000000..4a3112ce --- /dev/null +++ b/modules/coverage-core.el @@ -0,0 +1,58 @@ +;;; coverage-core.el --- Coverage reporting engine and backend registry -*- lexical-binding: t; coding: utf-8; -*- +;; author: Craig Jennings <c@cjennings.net> + +;;; Commentary: +;; Language-agnostic core for diff-aware coverage reporting. +;; +;; Reads an LCOV file, shells to git diff at a selectable scope, +;; intersects the results, and displays a report buffer. Languages +;; plug in via the backend registry (see `cj/coverage-backends'). +;; +;; See docs/design/coverage.org for the design rationale. + +;;; Code: + +(defun cj/--coverage-parse-lcov (file) + "Parse FILE as LCOV and return a hash table of covered lines. +Keys are source-file paths (strings). Values are hash tables whose +keys are line numbers (integers) that had a hit count greater than +zero. Only the SF, DA, and end_of_record fields are read; other +LCOV fields are ignored. Malformed DA lines are skipped silently. +Signals `user-error' if FILE does not exist." + (unless (file-exists-p file) + (user-error "LCOV file not found: %s" file)) + (let ((result (make-hash-table :test 'equal)) + (current-file nil) + (current-lines nil)) + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (while (not (eobp)) + (let ((line (buffer-substring-no-properties + (line-beginning-position) (line-end-position)))) + (cond + ((string-prefix-p "SF:" line) + (setq current-file (substring line 3)) + (setq current-lines (make-hash-table :test 'eql))) + ((string-prefix-p "DA:" line) + (when current-lines + (let* ((rest (substring line 3)) + (parts (split-string rest ",")) + (line-str (car parts)) + (hits-str (cadr parts)) + (line-num (and line-str (string-match-p "\\`[0-9]+\\'" line-str) + (string-to-number line-str))) + (hits (and hits-str (string-match-p "\\`[0-9]+\\'" hits-str) + (string-to-number hits-str)))) + (when (and line-num hits (> hits 0)) + (puthash line-num t current-lines))))) + ((string= line "end_of_record") + (when current-file + (puthash current-file current-lines result)) + (setq current-file nil + current-lines nil)))) + (forward-line 1))) + result)) + +(provide 'coverage-core) +;;; coverage-core.el ends here |
