aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-12 05:12:17 -0500
committerCraig Jennings <c@cjennings.net>2026-05-12 05:12:17 -0500
commita032f45fe30ce300163bc052257c3c5c993c85d5 (patch)
treebd96c3c802a0ed5f3c5b1b758fda6688a2233bff
parentfc966aa764cf30bcd6e75d9a1c3d89558ca95988 (diff)
downloaddotemacs-a032f45fe30ce300163bc052257c3c5c993c85d5.tar.gz
dotemacs-a032f45fe30ce300163bc052257c3c5c993c85d5.zip
test: add terminal coverage summary helper
-rw-r--r--scripts/coverage-summary.el57
-rw-r--r--tests/test-coverage-summary.el66
2 files changed, 123 insertions, 0 deletions
diff --git a/scripts/coverage-summary.el b/scripts/coverage-summary.el
new file mode 100644
index 00000000..4947171f
--- /dev/null
+++ b/scripts/coverage-summary.el
@@ -0,0 +1,57 @@
+;;; coverage-summary.el --- Terminal summary for SimpleCov module coverage -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Batch helper for `make coverage' and `make coverage-summary'.
+;; Reuses coverage-core's SimpleCov parser and whole-project formatter so the
+;; terminal table matches the editor's whole-project coverage semantics.
+
+;;; Code:
+
+(require 'coverage-core)
+
+(defun cj/coverage-summary--copy-lines (lines)
+ "Return a copy of hash table LINES."
+ (let ((copy (make-hash-table :test 'eql)))
+ (when (hash-table-p lines)
+ (maphash (lambda (line value)
+ (puthash line value copy))
+ lines))
+ copy))
+
+(defun cj/coverage-summary--modules-only (table module-dir project-root)
+ "Filter coverage TABLE to files under MODULE-DIR.
+
+Returned keys are relative to PROJECT-ROOT for readable terminal output."
+ (let ((result (make-hash-table :test 'equal))
+ (module-dir (file-name-as-directory (expand-file-name module-dir)))
+ (project-root (file-name-as-directory (expand-file-name project-root))))
+ (maphash
+ (lambda (path lines)
+ (let ((absolute-path (expand-file-name path)))
+ (when (string-prefix-p module-dir absolute-path)
+ (puthash (file-relative-name absolute-path project-root)
+ (cj/coverage-summary--copy-lines lines)
+ result))))
+ table)
+ result))
+
+(defun cj/coverage-summary-text (report-file module-dir project-root)
+ "Return a whole-project coverage summary for MODULE-DIR from REPORT-FILE."
+ (let* ((covered (cj/coverage-summary--modules-only
+ (cj/--coverage-parse-simplecov report-file)
+ module-dir
+ project-root))
+ (executable (cj/coverage-summary--modules-only
+ (cj/--coverage-simplecov-executable-lines report-file)
+ module-dir
+ project-root))
+ (records (cj/--coverage-intersect covered executable)))
+ (cj/--coverage-format-summary records "modules/")))
+
+(defun cj/coverage-print-module-summary (report-file module-dir project-root)
+ "Print a whole-project coverage summary for MODULE-DIR from REPORT-FILE."
+ (princ "\n")
+ (princ (cj/coverage-summary-text report-file module-dir project-root)))
+
+(provide 'coverage-summary)
+;;; coverage-summary.el ends here
diff --git a/tests/test-coverage-summary.el b/tests/test-coverage-summary.el
new file mode 100644
index 00000000..01c9efa0
--- /dev/null
+++ b/tests/test-coverage-summary.el
@@ -0,0 +1,66 @@
+;;; test-coverage-summary.el --- Tests for terminal coverage summary -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Unit tests for the batch helper used by `make coverage-summary'.
+
+;;; Code:
+
+(require 'ert)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(add-to-list 'load-path (expand-file-name "scripts" user-emacs-directory))
+(require 'coverage-summary)
+
+(defun test-coverage-summary--write-json (content)
+ "Write CONTENT to a temp file; return its path."
+ (let ((file (make-temp-file "test-coverage-summary-" nil ".json")))
+ (with-temp-file file
+ (insert content))
+ file))
+
+(ert-deftest test-coverage-summary-modules-only ()
+ "Normal: summary includes modules files and ignores non-module files."
+ (let* ((root (file-name-as-directory (make-temp-file "coverage-root-" t)))
+ (module-a (expand-file-name "modules/a.el" root))
+ (module-b (expand-file-name "modules/b.el" root))
+ (other (expand-file-name "tests/test-a.el" root))
+ (content (format
+ "{\"run\":{\"coverage\":{\"%s\":[1,0,null,1],\"%s\":[0,0],\"%s\":[1,1]}}}"
+ module-a module-b other))
+ (file (test-coverage-summary--write-json content)))
+ (unwind-protect
+ (let ((output (cj/coverage-summary-text
+ file
+ (expand-file-name "modules" root)
+ root)))
+ (should (string-match-p "modules/a\\.el" output))
+ (should (string-match-p "modules/b\\.el" output))
+ (should-not (string-match-p "tests/test-a\\.el" output))
+ (should (string-match-p "2 of 5" output)))
+ (delete-file file)
+ (delete-directory root t))))
+
+(ert-deftest test-coverage-summary-sorts-worst-first ()
+ "Normal: module rows use the same worst-first sorting as the editor summary."
+ (let* ((root (file-name-as-directory (make-temp-file "coverage-root-" t)))
+ (low (expand-file-name "modules/low.el" root))
+ (high (expand-file-name "modules/high.el" root))
+ (content (format
+ "{\"run\":{\"coverage\":{\"%s\":[0,0,1,1],\"%s\":[1,1]}}}"
+ low high))
+ (file (test-coverage-summary--write-json content)))
+ (unwind-protect
+ (let* ((output (cj/coverage-summary-text
+ file
+ (expand-file-name "modules" root)
+ root))
+ (pos-low (string-match "modules/low\\.el" output))
+ (pos-high (string-match "modules/high\\.el" output)))
+ (should pos-low)
+ (should pos-high)
+ (should (< pos-low pos-high)))
+ (delete-file file)
+ (delete-directory root t))))
+
+(provide 'test-coverage-summary)
+;;; test-coverage-summary.el ends here