diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-26 02:54:39 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-26 02:54:39 -0500 |
| commit | 914ab07da513daebd15c7bc65ac334cde5fbb769 (patch) | |
| tree | 1f9876e4c9e9c9517f1ab49a3700a7381b859db9 /tests/test-pearl-coverage-summary.el | |
| parent | 23e4157530cff4ed95e6a9473f8ef9434d453b42 (diff) | |
| download | pearl-914ab07da513daebd15c7bc65ac334cde5fbb769.tar.gz pearl-914ab07da513daebd15c7bc65ac334cde5fbb769.zip | |
feat(coverage): print a terminal coverage summary from the SimpleCov report
`make coverage` only reported the JSON's file size, so reading the real number meant opening the report by hand. I added a self-contained `scripts/coverage-summary.el` that parses the SimpleCov JSON and prints per-file covered/total lines with a percent, a line-weighted project figure, and a source-weighted figure. A tracked source missing from the report counts as 0% rather than dropping out silently, so a source that never got instrumented still shows up.
A new `coverage-summary` target runs it against the last report, and `make coverage` chains it onto the tail of a local run (CI emits coveralls.json, not simplecov.json, so it's skipped there). Covered by `tests/test-pearl-coverage-summary.el`: parser hit/executable/merge-key semantics, the per-file records, and the error paths for a missing or malformed report.
Diffstat (limited to 'tests/test-pearl-coverage-summary.el')
| -rw-r--r-- | tests/test-pearl-coverage-summary.el | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/tests/test-pearl-coverage-summary.el b/tests/test-pearl-coverage-summary.el new file mode 100644 index 0000000..79de30a --- /dev/null +++ b/tests/test-pearl-coverage-summary.el @@ -0,0 +1,163 @@ +;;; test-pearl-coverage-summary.el --- Tests for the coverage summary script -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;; Author: Craig Jennings <c@cjennings.net> + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Unit tests for scripts/coverage-summary.el — the batch helper behind +;; `make coverage-summary'. Drives the SimpleCov parsers, the per-file +;; record builder, and the formatted text on synthetic reports written to +;; temp files (no committed fixtures). Covers the normal percentage path, +;; boundary cases (fully covered, no executable lines, multi-key union), and +;; error cases (missing file, malformed JSON, a tracked source absent from the +;; report counting as 0%). + +;;; Code: + +(require 'ert) +(load (expand-file-name "../scripts/coverage-summary.el") nil t) + +(defconst test-pearl-cov-root "/tmp/pearl-cov-test/" + "Synthetic project root used to anchor source-file paths in fixtures.") + +(defun test-pearl-cov--write (json) + "Write JSON to a temp file and return its path." + (let ((f (make-temp-file "pearl-cov" nil ".json"))) + (with-temp-file f (insert json)) + f)) + +(defun test-pearl-cov--report (hits-array) + "Write a SimpleCov report mapping pearl.el under the test root to HITS-ARRAY. +HITS-ARRAY is a JSON array literal string such as \"[null, 1, 0]\"." + (test-pearl-cov--write + (format "{\"undercover\": {\"coverage\": {\"%spearl.el\": %s}}}" + test-pearl-cov-root hits-array))) + +(defmacro test-pearl-cov--with (json-or-path varname &rest body) + "Bind VARNAME to a report path from JSON-OR-PATH, run BODY, then delete it." + (declare (indent 2)) + `(let ((,varname ,json-or-path)) + (unwind-protect (progn ,@body) + (when (file-exists-p ,varname) (delete-file ,varname))))) + +;;; --- Parsers --- + +(ert-deftest test-pearl-coverage-parse-counts-only-hit-lines () + "`parse-simplecov' records only lines whose hit count is greater than zero." + (test-pearl-cov--with (test-pearl-cov--report "[null, 1, 0, 3, null, 0]") f + (let ((tbl (pearl-coverage--parse-simplecov f)) + (key (expand-file-name "pearl.el" test-pearl-cov-root))) + ;; lines 2 and 4 are hits; 3 and 6 are executable-but-unhit; 1,5 null + (should (= 2 (pearl-coverage--line-count tbl key)))))) + +(ert-deftest test-pearl-coverage-executable-counts-all-numeric-lines () + "`executable-lines' records every numeric entry, hit or not." + (test-pearl-cov--with (test-pearl-cov--report "[null, 1, 0, 3, null, 0]") f + (let ((tbl (pearl-coverage--executable-lines f)) + (key (expand-file-name "pearl.el" test-pearl-cov-root))) + ;; lines 2,3,4,6 are numeric; 1 and 5 are null + (should (= 4 (pearl-coverage--line-count tbl key)))))) + +(ert-deftest test-pearl-coverage-parse-unions-across-test-keys () + "Coverage under multiple top-level keys is unioned (undercover :merge-report)." + (test-pearl-cov--with + (test-pearl-cov--write + (format (concat "{\"a\": {\"coverage\": {\"%spearl.el\": [1, 0, null]}}," + " \"b\": {\"coverage\": {\"%spearl.el\": [0, 1, null]}}}") + test-pearl-cov-root test-pearl-cov-root)) + f + (let ((tbl (pearl-coverage--parse-simplecov f)) + (key (expand-file-name "pearl.el" test-pearl-cov-root))) + ;; line 1 hit in a, line 2 hit in b -> union is 2 covered lines + (should (= 2 (pearl-coverage--line-count tbl key)))))) + +;;; --- Records --- + +(ert-deftest test-pearl-coverage-record-normal-percentage () + "A half-covered file reports covered/total and a 50% figure." + (test-pearl-cov--with (test-pearl-cov--report "[null, 1, 0, 3, null, 0]") f + (let ((r (car (pearl-coverage--records f '("pearl.el") test-pearl-cov-root)))) + (should (plist-get r :present)) + (should (= 2 (plist-get r :covered))) + (should (= 4 (plist-get r :total))) + (should (< 49.9 (plist-get r :percent) 50.1))))) + +(ert-deftest test-pearl-coverage-record-fully-covered-is-100 () + "Every executable line hit reports 100%." + (test-pearl-cov--with (test-pearl-cov--report "[1, 2, 3]") f + (let ((r (car (pearl-coverage--records f '("pearl.el") test-pearl-cov-root)))) + (should (= 3 (plist-get r :covered))) + (should (= 3 (plist-get r :total))) + (should (< 99.9 (plist-get r :percent) 100.1))))) + +(ert-deftest test-pearl-coverage-record-no-executable-lines-is-100 () + "A present file with no executable lines is 100% — nothing left uncovered." + (test-pearl-cov--with (test-pearl-cov--report "[null, null]") f + (let ((r (car (pearl-coverage--records f '("pearl.el") test-pearl-cov-root)))) + (should (plist-get r :present)) + (should (= 0 (plist-get r :total))) + (should (< 99.9 (plist-get r :percent) 100.1))))) + +(ert-deftest test-pearl-coverage-record-missing-source-counts-as-zero () + "A tracked source absent from the report is flagged missing and counts as 0%." + (test-pearl-cov--with + (test-pearl-cov--write + (format "{\"u\": {\"coverage\": {\"%sother.el\": [1, 2]}}}" + test-pearl-cov-root)) + f + (let ((r (car (pearl-coverage--records f '("pearl.el") test-pearl-cov-root)))) + (should-not (plist-get r :present)) + (should (= 0 (plist-get r :covered))) + (should (= 0.0 (plist-get r :percent)))))) + +;;; --- Error handling --- + +(ert-deftest test-pearl-coverage-missing-report-errors () + "Parsing a nonexistent report signals a user-error." + (should-error (pearl-coverage--parse-simplecov "/nonexistent/pearl-cov.json") + :type 'user-error)) + +(ert-deftest test-pearl-coverage-malformed-json-errors () + "Malformed JSON signals a user-error rather than a raw json error." + (test-pearl-cov--with (test-pearl-cov--write "{ this is not json") f + (should-error (pearl-coverage--parse-simplecov f) :type 'user-error))) + +;;; --- Formatted text --- + +(ert-deftest test-pearl-coverage-summary-text-reports-both-numbers () + "The summary names the file, a line-coverage figure, and source coverage." + (test-pearl-cov--with (test-pearl-cov--report "[null, 1, 0, 3, null, 0]") f + (let ((txt (pearl-coverage-summary-text f '("pearl.el") test-pearl-cov-root))) + (should (string-match-p "pearl\\.el" txt)) + (should (string-match-p "Line coverage" txt)) + (should (string-match-p "Source coverage" txt)) + (should (string-match-p "50\\.0%" txt))))) + +(ert-deftest test-pearl-coverage-summary-text-lists-missing-source () + "The summary's missing section names a tracked source absent from the report." + (test-pearl-cov--with + (test-pearl-cov--write + (format "{\"u\": {\"coverage\": {\"%sother.el\": [1, 2]}}}" + test-pearl-cov-root)) + f + (let ((txt (pearl-coverage-summary-text f '("pearl.el") test-pearl-cov-root))) + (should (string-match-p "Not in coverage report" txt)) + (should (string-match-p "pearl\\.el" txt))))) + +(provide 'test-pearl-coverage-summary) +;;; test-pearl-coverage-summary.el ends here |
