aboutsummaryrefslogtreecommitdiff
path: root/languages/elisp/tests
diff options
context:
space:
mode:
Diffstat (limited to 'languages/elisp/tests')
-rw-r--r--languages/elisp/tests/test-coverage-summary.el173
1 files changed, 173 insertions, 0 deletions
diff --git a/languages/elisp/tests/test-coverage-summary.el b/languages/elisp/tests/test-coverage-summary.el
new file mode 100644
index 0000000..5be03b3
--- /dev/null
+++ b/languages/elisp/tests/test-coverage-summary.el
@@ -0,0 +1,173 @@
+;;; test-coverage-summary.el --- Tests for the bundle coverage-summary kernel -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; ERT tests for languages/elisp/claude/scripts/coverage-summary.el.
+;;
+;; The kernel is the missing-file detection and the unit-weighted project
+;; number; the per-file table is incidental. Tests build a throwaway project
+;; tree (source files on disk) plus a SimpleCov JSON report, then assert the
+;; kernel's numbers against hand-computed values.
+;;
+;; Normal / Boundary / Error coverage for each pure function.
+
+;;; Code:
+
+(require 'ert)
+(require 'json)
+
+;; Load the script under test relative to this file, so the test runs from any cwd.
+(load (expand-file-name
+ "../claude/scripts/coverage-summary.el"
+ (file-name-directory (or load-file-name buffer-file-name)))
+ nil t)
+
+;; --- fixtures --------------------------------------------------------------
+
+(defun cs-test--write (path contents)
+ "Write CONTENTS to PATH, creating parent dirs."
+ (make-directory (file-name-directory path) t)
+ (with-temp-file path (insert contents)))
+
+(defmacro cs-test--with-project (spec &rest body)
+ "Build a temp project from SPEC and run BODY with `root', `src', `report' bound.
+
+SPEC is a plist:
+ :sources alist of (relpath . contents) source files written under src/
+ :report the SimpleCov JSON string written to .coverage/simplecov.json
+ (the literal string \"%SRC%\" is replaced with the absolute src dir)."
+ (declare (indent 1))
+ `(let* ((root (file-name-as-directory (make-temp-file "cs-proj-" t)))
+ (src (expand-file-name "src/" root))
+ (report (expand-file-name ".coverage/simplecov.json" root)))
+ (unwind-protect
+ (progn
+ (dolist (pair (plist-get ,spec :sources))
+ (cs-test--write (expand-file-name (car pair) src) (cdr pair)))
+ (cs-test--write
+ report
+ (replace-regexp-in-string
+ "%SRC%" (directory-file-name src) (plist-get ,spec :report) t t))
+ ,@body)
+ (delete-directory root t))))
+
+(defun cs-test--report (files)
+ "Build a SimpleCov JSON string for FILES.
+FILES is an alist of (relpath . line-vector-literal), where each line vector
+is a JSON array string like \"[1, 0, null]\"."
+ (concat
+ "{\"undercover.el\": {\"timestamp\": 0, \"coverage\": {"
+ (mapconcat
+ (lambda (pair)
+ (format "\"%%SRC%%/%s\": %s" (car pair) (cdr pair)))
+ files ", ")
+ "}}}"))
+
+;; --- parse: covered / total ------------------------------------------------
+
+(ert-deftest cs-parse-counts-covered-and-executable ()
+ "covered = entries > 0; total = numeric entries; null is non-executable."
+ (cs-test--with-project
+ (list :sources '(("a.el" . ";; a"))
+ :report (cs-test--report '(("a.el" . "[null, 1, 0, 3, null, 0]"))))
+ (let* ((table (cj/coverage-summary--parse-file report))
+ (rec (gethash (expand-file-name "a.el" src) table)))
+ ;; entries: null,1,0,3,null,0 -> numeric {1,0,3,0}=4 total, >0 {1,3}=2 covered
+ (should (equal rec '(2 . 4))))))
+
+(ert-deftest cs-parse-unions-multiple-suites ()
+ "Coverage data unions across multiple top-level suite keys."
+ (cs-test--with-project
+ (list :sources '(("a.el" . ";; a"))
+ :report (concat
+ "{\"s1\": {\"coverage\": {\"%SRC%/a.el\": [1, 0, 0]}},"
+ " \"s2\": {\"coverage\": {\"%SRC%/a.el\": [0, 1, 0]}}}"))
+ (let* ((table (cj/coverage-summary--parse-file report))
+ (rec (gethash (expand-file-name "a.el" src) table)))
+ ;; union of hits at line 1 (s1) and line 2 (s2): 2 covered of 3 total
+ (should (equal rec '(2 . 3))))))
+
+(ert-deftest cs-parse-missing-report-signals ()
+ "A nonexistent report file signals user-error."
+ (should-error (cj/coverage-summary--parse-file "/no/such/report.json")
+ :type 'user-error))
+
+(ert-deftest cs-parse-malformed-json-signals ()
+ "Malformed JSON signals user-error rather than a raw json error."
+ (cs-test--with-project
+ (list :sources '(("a.el" . ";; a")) :report "{not json")
+ (should-error (cj/coverage-summary--parse-file report) :type 'user-error)))
+
+;; --- file percentage -------------------------------------------------------
+
+(ert-deftest cs-file-pct-normal ()
+ (should (= 50.0 (cj/coverage-summary--file-pct 1 2))))
+
+(ert-deftest cs-file-pct-no-executable-lines-is-100 ()
+ "A tracked file with zero executable lines has nothing left uncovered."
+ (should (= 100.0 (cj/coverage-summary--file-pct 0 0))))
+
+(ert-deftest cs-file-pct-fully-covered ()
+ (should (= 100.0 (cj/coverage-summary--file-pct 4 4))))
+
+;; --- missing-file detection (the kernel) -----------------------------------
+
+(ert-deftest cs-missing-finds-ondisk-file-absent-from-report ()
+ "A source file on disk but not in the report is reported missing."
+ (cs-test--with-project
+ (list :sources '(("tracked.el" . ";; t") ("untested.el" . ";; u"))
+ :report (cs-test--report '(("tracked.el" . "[1, 1]"))))
+ (let* ((table (cj/coverage-summary--under-dir
+ (cj/coverage-summary--parse-file report) src root))
+ (tracked (let (ks) (maphash (lambda (k _v) (push k ks)) table) ks))
+ (missing (cj/coverage-summary--missing tracked src root)))
+ (should (equal missing (list (file-relative-name
+ (expand-file-name "src/untested.el" root)
+ root)))))))
+
+(ert-deftest cs-missing-empty-when-all-tracked ()
+ "No missing files when every source file appears in the report."
+ (cs-test--with-project
+ (list :sources '(("a.el" . ";; a"))
+ :report (cs-test--report '(("a.el" . "[1]"))))
+ (let* ((table (cj/coverage-summary--under-dir
+ (cj/coverage-summary--parse-file report) src root))
+ (tracked (let (ks) (maphash (lambda (k _v) (push k ks)) table) ks))
+ (missing (cj/coverage-summary--missing tracked src root)))
+ (should (null missing)))))
+
+;; --- project number (unit-weighted, missing as 0%) -------------------------
+
+(ert-deftest cs-project-pct-unit-weighted-with-missing-as-zero ()
+ "Project number averages per-file pct over tracked+missing; missing=0%."
+ (cs-test--with-project
+ (list :sources '(("full.el" . ";; f") ("half.el" . ";; h")
+ ("untested.el" . ";; u"))
+ :report (cs-test--report '(("full.el" . "[1, 1]")
+ ("half.el" . "[1, 0]"))))
+ ;; full=100%, half=50%, untested missing=0% -> (100+50+0)/3 = 50.0
+ (should (= 50.0 (cj/coverage-summary--project-pct report src root)))))
+
+(ert-deftest cs-project-pct-no-missing ()
+ "With every file tracked, the number is the plain unit average."
+ (cs-test--with-project
+ (list :sources '(("full.el" . ";; f") ("half.el" . ";; h"))
+ :report (cs-test--report '(("full.el" . "[1, 1]")
+ ("half.el" . "[1, 0]"))))
+ ;; (100 + 50) / 2 = 75.0
+ (should (= 75.0 (cj/coverage-summary--project-pct report src root)))))
+
+;; --- end-to-end text -------------------------------------------------------
+
+(ert-deftest cs-text-reports-missing-and-project-number ()
+ "The rendered summary names the missing file and the project percentage."
+ (cs-test--with-project
+ (list :sources '(("a.el" . ";; a") ("orphan.el" . ";; o"))
+ :report (cs-test--report '(("a.el" . "[1, 0]"))))
+ (let ((text (cj/coverage-summary-text report src root)))
+ (should (string-match-p "orphan\\.el" text))
+ (should (string-match-p "count as 0%" text))
+ ;; a.el = 50%, orphan missing = 0% -> 25.0%
+ (should (string-match-p "25\\.0%" text)))))
+
+(provide 'test-coverage-summary)
+;;; test-coverage-summary.el ends here