1 ;;; mh-unit.el --- Unit tests for MH-E
3 ;; Copyright (C) 2003 Bill Wohler
5 ;; Author: Bill Wohler <wohler@newt.com>
6 ;; Maintainer: Bill Wohler <wohler@newt.com>
10 ;; This file is part of MH-E.
12 ;; MH-E is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; MH-E is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with MH-E; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
29 ;; Currently, this file contains unit tests that are useful when releasing
30 ;; software. I have a dream that we can add unit tests to actually test code.
32 ;; To use, add the following to your .emacs and then run "M-x mh-unit".
34 ;; (autoload 'mh-unit "mh-unit")
43 (defvar mh-unit-files '("mh-alias.el" "mh-comp.el" "mh-customize.el" "mh-e.el"
44 "mh-funcs.el" "mh-identity.el" "mh-inc.el"
45 "mh-index.el" "mh-junk.el" "mh-loaddefs.el"
46 "mh-mime.el" "mh-pick.el" "mh-seq.el" "mh-speed.el"
47 "mh-utils.el" "mh-xemacs-compat.el"
48 "mh-xemacs-icons.el"))
51 "Run unit test on MH-E.
52 Currently, the tests are all release related, including:
53 - Run `lm-verify' on all files.
54 - Run `checkdoc' on all files.
55 - Removing trailing space on all files (per GNU Emacs conventions)."
57 (dolist (file mh-unit-files)
58 (let ((buffer-exists (find-buffer-visiting file)))
60 ;; Previous versions of lm-verify did not handle multiple-line
61 ;; copyrights which we have as of MH-E version 7.3.
62 (if (and (>= emacs-major-version 21)
63 (>= emacs-minor-version 3))
64 (let ((lm-out (lm-verify file)))
68 (mh-unit-prune-trailing-spaces)
69 (if (not buffer-exists)
74 (defun mh-unit-prune-trailing-spaces ()
75 "Remove all trailing spaces in buffer."
77 (goto-char (point-min))
78 (while (re-search-forward "[ \t]+$" nil t)
79 (delete-region (match-beginning 0) (match-end 0)))))
83 ;;; Find possibly dead code...
85 (defvar mh-unit-call-graph (make-hash-table))
86 (defvar mh-unit-root-functions (make-hash-table))
87 (defvar mh-unit-function-definition (make-hash-table))
88 (defvar mh-unit-fix-point-interation-count 0)
89 (defvar mh-unit-autoload-regexp
90 "[ \t\n]*\\(;.*\n\\|\014\n\\|\n\\)*;;;###autoload\n"
91 "Regexp to recognize an autoload cookie.")
93 (defun mh-unit-construct-call-graph ()
94 "Construct call graph for MH-E functions.
95 The hash maps `mh-unit-call-graph' and `mh-unit-function-definition' are
97 (clrhash mh-unit-call-graph)
98 (clrhash mh-unit-root-functions)
99 (clrhash mh-unit-function-definition)
100 (message "Constructing call graph ...")
101 (loop for file in (remove "mh-loaddefs.el" mh-unit-files)
103 (message "Reading %s ..." file)
104 (ignore-errors (insert-file-contents-literally file))
105 (goto-char (point-min))
106 (loop with eof = (make-symbol "eof")
107 for autoloadp = (looking-at mh-unit-autoload-regexp)
108 for expr = (condition-case nil (read (current-buffer))
110 for defunp = (and (consp expr) (eq (car expr) 'defun))
111 for defmacrop = (and (consp expr) (eq (car expr) 'defmacro))
112 for defcustomp = (and (consp expr)
113 (eq (car expr) 'defcustom))
114 for defvarp = (and (consp expr) (eq (car expr) 'defvar))
115 for deffacep = (and (consp expr) (eq (car expr) 'defface))
119 (setf (gethash (cadr expr) mh-unit-root-functions) t))
120 (when (or defunp defmacrop)
121 (setf (gethash (cadr expr) mh-unit-function-definition)
123 (mh-unit-update-call-graph
124 (and (or defunp defmacrop) (cadr expr))
125 (cond ((or defunp defmacrop defcustomp defvarp)
129 finally do (message "Constructing call graph ...done")))
131 (defun mh-unit-find-all-used-functions ()
132 "Find all used functions.
133 Compute a fixed point to find the set of all called functions. The process is
134 guaranteed to produce a conservative approximation."
135 (message "Finding all used functions ...")
136 (setq mh-unit-fix-point-interation-count 0)
137 (let* ((init (copy-hash-table mh-unit-root-functions))
138 (next (mh-unit-called-functions init)))
139 (while (> (hash-table-count next) (hash-table-count init))
141 (setq next (mh-unit-called-functions init)))
144 (defun mh-unit-called-functions (set)
145 "Find all the functions that are called by elements of SET.
146 The returned set includes all the elements of SET and all functions that are
147 directly called by members of SET."
148 (message "Iteration %s ..." (incf mh-unit-fix-point-interation-count))
149 (loop with result = (make-hash-table)
150 for x being the hash-keys of set
151 do (setf (gethash x result) t)
152 (loop for y in (gethash x mh-unit-call-graph)
153 do (setf (gethash y result) t))
154 finally return result))
156 (defun mh-unit-find-all-unused-functions ()
157 "Find all the functions that have been defined but never used in MH-E."
159 (mh-unit-construct-call-graph)
160 (let ((used-functions (mh-unit-find-all-used-functions))
161 (results-by-file (make-hash-table))
163 (loop for x being the hash-keys of mh-unit-function-definition
164 unless (gethash x used-functions)
165 do (push x (gethash (gethash x mh-unit-function-definition)
167 (with-current-buffer (get-buffer-create "*MH-E Unit Results*")
169 (loop for file being the hash-keys of results-by-file
172 (loop for x in (gethash file results-by-file)
173 do (insert " " (symbol-name x) "\n") (incf count))
175 (if (equal (hash-table-count results-by-file) 0)
176 (message "No unused functions in MH-E")
177 (message "Found %s unused functions in %s files"
178 count (hash-table-count results-by-file))
179 (display-buffer "*MH-E Unit Results*"))))
181 (defun mh-unit-update-call-graph (node expr)
182 "Add edges to function call graph.
183 The body of NODE is EXPR. If NODE is nil, then EXPR is a top level expression.
184 An edge is added from NODE to every possible function in EXPR."
185 (cond ((and (atom expr) node) (push expr (gethash node mh-unit-call-graph)))
186 ((atom expr) (setf (gethash expr mh-unit-root-functions) t))
187 (t (loop for x in expr do (mh-unit-update-call-graph node x)))))
192 ;;; indent-tabs-mode: nil
193 ;;; sentence-end-double-space: nil
196 ;;; mh-unit.el ends here