blob: 4929a743b0e9691a7dfa29adbba0077c3c9425ce (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
|