diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-06 10:31:30 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-06 10:31:30 -0500 |
| commit | 95dbb5abdbb746cf5da9f7926740d17205ac8d55 (patch) | |
| tree | 0e807d43d8f8ce32b3790efc716c433d35ceca3c /tests/test-duet-complexity.el | |
| parent | 6ecd1e9bf1e3d0cdd3861077318541e193ca4532 (diff) | |
| download | duet-95dbb5abdbb746cf5da9f7926740d17205ac8d55.tar.gz duet-95dbb5abdbb746cf5da9f7926740d17205ac8d55.zip | |
build: add Eask, test harness, and dev tooling
I brought the skeleton up to a working package baseline (Phase 0 in the design spec). Eask defines the package and its dev deps. A root Makefile delegates test targets to tests/Makefile and adds compile, coverage, lint, doctor, and clean, matching the layout the other packages use.
deps installs both halves DUET needs: the Emacs Lisp deps via eask, and the transport CLIs (rsync, rclone, lftp, unison) via the system package manager, so a contributor's environment is ready before the code that shells out to them.
make complexity runs a small homegrown McCabe branch counter (scripts/duet-complexity.el). No off-the-shelf tool measures Emacs Lisp: lizard doesn't support it and codemetrics is an interactive overlay, so DUET owns one. The counting is pure and covered by Normal/Boundary/Error tests. The budget is soft and the target is advisory.
The ERT harness (bootstrap, check-deps, per-file undercover coverage) and a smoke test prove the loop works end to end.
Diffstat (limited to 'tests/test-duet-complexity.el')
| -rw-r--r-- | tests/test-duet-complexity.el | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/tests/test-duet-complexity.el b/tests/test-duet-complexity.el new file mode 100644 index 0000000..4929a74 --- /dev/null +++ b/tests/test-duet-complexity.el @@ -0,0 +1,126 @@ +;;; test-duet-complexity.el --- Tests for the duet complexity scanner -*- 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/duet-complexity.el — the homegrown McCabe branch +;; counter behind `make complexity'. The counting logic is pure (operates on +;; already-read forms), so Normal/Boundary/Error cases need no I/O; only the +;; file-scanning tests touch a temp file. + +;;; Code: + +(require 'ert) +(require 'duet-complexity + (expand-file-name "../scripts/duet-complexity.el" + (file-name-directory (or load-file-name buffer-file-name)))) + +;;; Normal cases — base count and single constructs + +(ert-deftest test-duet-complexity-empty-body-is-one () + "A function with no decision points has complexity 1." + (should (= 1 (duet-complexity-of-form '(defun f ()))))) + +(ert-deftest test-duet-complexity-single-if-is-two () + "One `if' adds a single decision point." + (should (= 2 (duet-complexity-of-form '(defun f (x) (if x 1 2)))))) + +(ert-deftest test-duet-complexity-when-unless-each-add-one () + "`when' and `unless' each add one decision point." + (should (= 2 (duet-complexity-of-form '(defun f (x) (when x 1))))) + (should (= 2 (duet-complexity-of-form '(defun f (x) (unless x 1)))))) + +(ert-deftest test-duet-complexity-loops-add-one () + "Looping forms each add one decision point." + (should (= 2 (duet-complexity-of-form '(defun f (xs) (dolist (x xs) x))))) + (should (= 2 (duet-complexity-of-form '(defun f (n) (dotimes (i n) i))))) + (should (= 2 (duet-complexity-of-form '(defun f (x) (while x (setq x nil))))))) + +(ert-deftest test-duet-complexity-cond-one-per-clause () + "`cond' adds one decision point per clause." + (should (= 4 (duet-complexity-of-form + '(defun f (x) (cond (a 1) (b 2) (t 3))))))) + +(ert-deftest test-duet-complexity-nested-decisions-accumulate () + "A nested branch inside another counts in addition to the outer one." + (should (= 3 (duet-complexity-of-form + '(defun f (x) (when x (if x 1 2))))))) + +;;; Boundary cases — boolean operators, quoting, pattern matching + +(ert-deftest test-duet-complexity-and-adds-operands-minus-one () + "`and' adds one decision point per short-circuit (operands minus one)." + (should (= 3 (duet-complexity-of-form '(defun f (a b c) (and a b c)))))) + +(ert-deftest test-duet-complexity-or-two-operands-adds-one () + "`or' with two operands adds a single decision point." + (should (= 2 (duet-complexity-of-form '(defun f (a b) (or a b)))))) + +(ert-deftest test-duet-complexity-quoted-data-is-not-counted () + "Branch-looking forms inside a quote are data, not control flow." + (should (= 1 (duet-complexity-of-form '(defun f () '(if a b c)))))) + +(ert-deftest test-duet-complexity-pcase-counts-clauses-not-patterns () + "`pcase' counts one per clause; an `or' pattern is not a boolean `or'." + (should (= 4 (duet-complexity-of-form + '(defun f (x) (pcase x ((or 1 2) 'a) (3 'b) (_ 'c))))))) + +(ert-deftest test-duet-complexity-condition-case-counts-handlers () + "`condition-case' adds one decision point per handler." + (should (= 3 (duet-complexity-of-form + '(defun f () (condition-case err (foo) (error 1) (quit 2))))))) + +(ert-deftest test-duet-complexity-handles-defmacro-and-cl-defun () + "Defun-like heads other than `defun' are measured the same way." + (should (= 2 (duet-complexity-of-form '(defmacro f (x) (if x 1 2))))) + (should (= 2 (duet-complexity-of-form '(cl-defun f (x) (if x 1 2)))))) + +;;; File scanning + +(ert-deftest test-duet-complexity-scan-file-returns-name-score-pairs () + "Scanning a file returns one (NAME . COMPLEXITY) pair per defun-like form." + (let ((file (make-temp-file "duet-cx" nil ".el" + "(defun simple () 1)\n(defun branchy (x) (if x (when x 1) 2))\n"))) + (unwind-protect + (let ((results (duet-complexity-scan-file file))) + (should (equal 1 (cdr (assq 'simple results)))) + (should (equal 3 (cdr (assq 'branchy results)))) + (should (= 2 (length results)))) + (delete-file file)))) + +(ert-deftest test-duet-complexity-scan-file-ignores-non-defuns () + "Top-level forms that are not defun-like are skipped by the scanner." + (let ((file (make-temp-file "duet-cx" nil ".el" + "(defvar x 1)\n(require 'foo)\n(defun real (y) (if y 1 2))\n"))) + (unwind-protect + (let ((results (duet-complexity-scan-file file))) + (should (= 1 (length results))) + (should (equal 2 (cdr (assq 'real results))))) + (delete-file file)))) + +;;; Threshold gate + +(ert-deftest test-duet-complexity-over-threshold-filters () + "Only functions above the threshold are returned." + (let ((results '((a . 3) (b . 11) (c . 10) (d . 15)))) + (should (equal '((b . 11) (d . 15)) + (duet-complexity-over-threshold results 10))))) + +(provide 'test-duet-complexity) +;;; test-duet-complexity.el ends here |
