aboutsummaryrefslogtreecommitdiff
path: root/modules/coverage-core.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/coverage-core.el')
-rw-r--r--modules/coverage-core.el86
1 files changed, 45 insertions, 41 deletions
diff --git a/modules/coverage-core.el b/modules/coverage-core.el
index f1e8ae88..2209b2f7 100644
--- a/modules/coverage-core.el
+++ b/modules/coverage-core.el
@@ -16,7 +16,7 @@
(defvar cj/coverage-backends nil
"Registry of coverage backends in priority order.
-Each entry is a plist with at least :name, :detect, :run, and :lcov-path.
+Each entry is a plist with at least :name, :detect, :run, and :report-path.
Use `cj/coverage-register-backend' to add or replace an entry.")
(defvar-local cj/coverage-backend nil
@@ -26,7 +26,7 @@ function in registration order. Typically set buffer-locally via
`.dir-locals.el' to pin a specific backend.")
(defun cj/coverage-register-backend (backend)
- "Register BACKEND, a plist with :name, :detect, :run, :lcov-path.
+ "Register BACKEND, a plist with :name, :detect, :run, :report-path.
Appends to `cj/coverage-backends' at the end, or replaces the existing
entry with the same :name in its current position."
(let ((name (plist-get backend :name)))
@@ -65,46 +65,50 @@ Returns the backend plist, or nil when no backend matches."
(funcall (plist-get backend :detect) root))
cj/coverage-backends))))
-(defun cj/--coverage-parse-lcov (file)
- "Parse FILE as LCOV and return a hash table of covered lines.
+(defun cj/--coverage-parse-simplecov (file)
+ "Parse FILE as a simplecov JSON report and return covered lines per file.
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."
+keys are line numbers (integers) with a hit count greater than zero.
+Lines marked nil (not executable) or 0 (executable but not hit) are
+excluded.
+
+Simplecov JSON structure is:
+ { <test-name>: { \"coverage\": { <path>: [null | 0 | int, ...] } } }
+
+When the JSON contains multiple top-level test-name keys, coverage
+data is unioned across them; useful for files produced with undercover's
+`:merge-report t' option that accumulate runs under a shared key, and
+also for defensive handling of unexpected multi-key shapes.
+
+Signals `user-error' if FILE does not exist or contains malformed JSON."
(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)))
+ (user-error "Simplecov report not found: %s" file))
+ (require 'json)
+ (let* ((json-object-type 'hash-table)
+ (json-array-type 'list)
+ (json-key-type 'string)
+ (data (condition-case err
+ (json-read-file file)
+ (error (user-error "Malformed simplecov JSON in %s: %s"
+ file (error-message-string err)))))
+ (result (make-hash-table :test 'equal)))
+ (maphash
+ (lambda (_test-name section)
+ (when (hash-table-p section)
+ (let ((coverage (gethash "coverage" section)))
+ (when (hash-table-p coverage)
+ (maphash
+ (lambda (path hits-list)
+ (let ((lines (or (gethash path result)
+ (make-hash-table :test 'eql)))
+ (line-num 1))
+ (dolist (hits hits-list)
+ (when (and (numberp hits) (> hits 0))
+ (puthash line-num t lines))
+ (setq line-num (1+ line-num)))
+ (puthash path lines result)))
+ coverage)))))
+ data)
result))
(defconst cj/--coverage-hunk-header-regexp
@@ -180,7 +184,7 @@ Signals `user-error' for any other SCOPE."
(defun cj/--coverage-intersect (covered changed)
"Combine COVERED (LCOV) with CHANGED (git diff) into per-file records.
COVERED and CHANGED are each hash tables from file path to a hash table
-of line numbers (as built by `cj/--coverage-parse-lcov' and
+of line numbers (as built by `cj/--coverage-parse-simplecov' and
`cj/--coverage-parse-diff-output'). Either may be nil, in which case
the result is an empty list.