comparison lisp/emacs-lisp/ert-x.el @ 112201:c5917804ecad

Add ERT, a tool for automated testing in Emacs Lisp. * Makefile.in, configure.in, doc/misc/Makefile.in, doc/misc/makefile.w32-in: Add ERT. Make "make check" run tests in test/automated. * doc/misc/ert.texi, lisp/emacs-lisp/ert.el, lisp/emacs-lisp/ert-x.el: New files. * test/automated: New directory.
author Christian Ohler <ohler@gnu.org>
date Thu, 13 Jan 2011 03:08:24 +1100
parents
children
comparison
equal deleted inserted replaced
112195:0a2b87003c28 112201:c5917804ecad
1 ;;; ert-x.el --- Staging area for experimental extensions to ERT
2
3 ;; Copyright (C) 2008, 2010, 2011 Free Software Foundation, Inc.
4
5 ;; Author: Lennart Borgman (lennart O borgman A gmail O com)
6 ;; Author: Christian Ohler <ohler@gnu.org>
7
8 ;; This file is part of GNU Emacs.
9
10 ;; This program is free software: you can redistribute it and/or
11 ;; modify it under the terms of the GNU General Public License as
12 ;; published by the Free Software Foundation, either version 3 of the
13 ;; License, or (at your option) any later version.
14 ;;
15 ;; This program is distributed in the hope that it will be useful, but
16 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ;; General Public License for more details.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with this program. If not, see `http://www.gnu.org/licenses/'.
22
23 ;;; Commentary:
24
25 ;; This file includes some extra helper functions to use while writing
26 ;; automated tests with ERT. These have been proposed as extensions
27 ;; to ERT but are not mature yet and likely to change.
28
29 ;;; Code:
30
31 (eval-when-compile
32 (require 'cl))
33 (require 'ert)
34
35
36 ;;; Test buffers.
37
38 (defun ert--text-button (string &rest properties)
39 "Return a string containing STRING as a text button with PROPERTIES.
40
41 See `make-text-button'."
42 (with-temp-buffer
43 (insert string)
44 (apply #'make-text-button (point-min) (point-max) properties)
45 (buffer-string)))
46
47 (defun ert--format-test-buffer-name (base-name)
48 "Compute a test buffer name based on BASE-NAME.
49
50 Helper function for `ert--test-buffers'."
51 (format "*Test buffer (%s)%s*"
52 (or (and (ert-running-test)
53 (ert-test-name (ert-running-test)))
54 "<anonymous test>")
55 (if base-name
56 (format ": %s" base-name)
57 "")))
58
59 (defvar ert--test-buffers (make-hash-table :weakness t)
60 "Table of all test buffers. Keys are the buffer objects, values are t.
61
62 The main use of this table is for `ert-kill-all-test-buffers'.
63 Not all buffers in this table are necessarily live, but all live
64 test buffers are in this table.")
65
66 (define-button-type 'ert--test-buffer-button
67 'action #'ert--test-buffer-button-action
68 'help-echo "mouse-2, RET: Pop to test buffer")
69
70 (defun ert--test-buffer-button-action (button)
71 "Pop to the test buffer that BUTTON is associated with."
72 (pop-to-buffer (button-get button 'ert--test-buffer)))
73
74 (defun ert--call-with-test-buffer (ert--base-name ert--thunk)
75 "Helper function for `ert-with-test-buffer'.
76
77 Create a test buffer with a name based on ERT--BASE-NAME and run
78 ERT--THUNK with that buffer as current."
79 (let* ((ert--buffer (generate-new-buffer
80 (ert--format-test-buffer-name ert--base-name)))
81 (ert--button (ert--text-button (buffer-name ert--buffer)
82 :type 'ert--test-buffer-button
83 'ert--test-buffer ert--buffer)))
84 (puthash ert--buffer 't ert--test-buffers)
85 ;; We don't use `unwind-protect' here since we want to kill the
86 ;; buffer only on success.
87 (prog1 (with-current-buffer ert--buffer
88 (ert-info (ert--button :prefix "Buffer: ")
89 (funcall ert--thunk)))
90 (kill-buffer ert--buffer)
91 (remhash ert--buffer ert--test-buffers))))
92
93 (defmacro* ert-with-test-buffer ((&key ((:name name-form)))
94 &body body)
95 "Create a test buffer and run BODY in that buffer.
96
97 To be used in ERT tests. If BODY finishes successfully, the test
98 buffer is killed; if there is an error, the test buffer is kept
99 around on error for further inspection. Its name is derived from
100 the name of the test and the result of NAME-FORM."
101 (declare (debug ((form) body))
102 (indent 1))
103 `(ert--call-with-test-buffer ,name-form (lambda () ,@body)))
104
105 ;; We use these `put' forms in addition to the (declare (indent)) in
106 ;; the defmacro form since the `declare' alone does not lead to
107 ;; correct indentation before the .el/.elc file is loaded.
108 ;; Autoloading these `put' forms solves this.
109 ;;;###autoload
110 (progn
111 ;; TODO(ohler): Figure out what these mean and make sure they are correct.
112 (put 'ert-with-test-buffer 'lisp-indent-function 1))
113
114 ;;;###autoload
115 (defun ert-kill-all-test-buffers ()
116 "Kill all test buffers that are still live."
117 (interactive)
118 (let ((count 0))
119 (maphash (lambda (buffer dummy)
120 (when (or (not (buffer-live-p buffer))
121 (kill-buffer buffer))
122 (incf count)))
123 ert--test-buffers)
124 (message "%s out of %s test buffers killed"
125 count (hash-table-count ert--test-buffers)))
126 ;; It could be that some test buffers were actually kept alive
127 ;; (e.g., due to `kill-buffer-query-functions'). I'm not sure what
128 ;; to do about this. For now, let's just forget them.
129 (clrhash ert--test-buffers)
130 nil)
131
132
133 ;;; Simulate commands.
134
135 (defun ert-simulate-command (command)
136 ;; FIXME: add unread-events
137 "Simulate calling COMMAND the way the Emacs command loop would call it.
138
139 This effectively executes
140
141 \(apply (car COMMAND) (cdr COMMAND)\)
142
143 and returns the same value, but additionally runs hooks like
144 `pre-command-hook' and `post-command-hook', and sets variables
145 like `this-command' and `last-command'.
146
147 COMMAND should be a list where the car is the command symbol and
148 the rest are arguments to the command.
149
150 NOTE: Since the command is not called by `call-interactively'
151 test for `called-interactively' in the command will fail."
152 (assert (listp command) t)
153 (assert (commandp (car command)) t)
154 (assert (not unread-command-events) t)
155 (let (return-value)
156 ;; For the order of things here see command_loop_1 in keyboard.c.
157 ;;
158 ;; The command loop will reset the command-related variables so
159 ;; there is no reason to let-bind them. They are set here,
160 ;; however, to be able to test several commands in a row and how
161 ;; they affect each other.
162 (setq deactivate-mark nil
163 this-original-command (car command)
164 ;; remap through active keymaps
165 this-command (or (command-remapping this-original-command)
166 this-original-command))
167 (run-hooks 'pre-command-hook)
168 (setq return-value (apply (car command) (cdr command)))
169 (run-hooks 'post-command-hook)
170 (when deferred-action-list
171 (run-hooks 'deferred-action-function))
172 (setq real-last-command (car command)
173 last-command this-command)
174 (when (boundp 'last-repeatable-command)
175 (setq last-repeatable-command real-last-command))
176 (when (and deactivate-mark transient-mark-mode) (deactivate-mark))
177 (assert (not unread-command-events) t)
178 return-value))
179
180 (defun ert-run-idle-timers ()
181 "Run all idle timers (from `timer-idle-list')."
182 (dolist (timer (copy-sequence timer-idle-list))
183 (timer-event-handler timer)))
184
185
186 ;;; Miscellaneous utilities.
187
188 (defun ert-filter-string (s &rest regexps)
189 "Return a copy of S with all matches of REGEXPS removed.
190
191 Elements of REGEXPS may also be two-element lists \(REGEXP
192 SUBEXP\), where SUBEXP is the number of a subexpression in
193 REGEXP. In that case, only that subexpression will be removed
194 rather than the entire match."
195 ;; Use a temporary buffer since replace-match copies strings, which
196 ;; would lead to N^2 runtime.
197 (with-temp-buffer
198 (insert s)
199 (dolist (x regexps)
200 (destructuring-bind (regexp subexp) (if (listp x) x `(,x nil))
201 (goto-char (point-min))
202 (while (re-search-forward regexp nil t)
203 (replace-match "" t t nil subexp))))
204 (buffer-string)))
205
206
207 (defun ert-propertized-string (&rest args)
208 "Return a string with properties as specified by ARGS.
209
210 ARGS is a list of strings and plists. The strings in ARGS are
211 concatenated to produce an output string. In the output string,
212 each string from ARGS will be have the preceding plist as its
213 property list, or no properties if there is no plist before it.
214
215 As a simple example,
216
217 \(ert-propertized-string \"foo \" '(face italic) \"bar\" \" baz\" nil \
218 \" quux\"\)
219
220 would return the string \"foo bar baz quux\" where the substring
221 \"bar baz\" has a `face' property with the value `italic'.
222
223 None of the ARGS are modified, but the return value may share
224 structure with the plists in ARGS."
225 (with-temp-buffer
226 (loop with current-plist = nil
227 for x in args do
228 (etypecase x
229 (string (let ((begin (point)))
230 (insert x)
231 (set-text-properties begin (point) current-plist)))
232 (list (unless (zerop (mod (length x) 2))
233 (error "Odd number of args in plist: %S" x))
234 (setq current-plist x))))
235 (buffer-string)))
236
237
238 (defun ert-call-with-buffer-renamed (buffer-name thunk)
239 "Protect the buffer named BUFFER-NAME from side-effects and run THUNK.
240
241 Renames the buffer BUFFER-NAME to a new temporary name, creates a
242 new buffer named BUFFER-NAME, executes THUNK, kills the new
243 buffer, and renames the original buffer back to BUFFER-NAME.
244
245 This is useful if THUNK has undesirable side-effects on an Emacs
246 buffer with a fixed name such as *Messages*."
247 (lexical-let ((new-buffer-name (generate-new-buffer-name
248 (format "%s orig buffer" buffer-name))))
249 (with-current-buffer (get-buffer-create buffer-name)
250 (rename-buffer new-buffer-name))
251 (unwind-protect
252 (progn
253 (get-buffer-create buffer-name)
254 (funcall thunk))
255 (when (get-buffer buffer-name)
256 (kill-buffer buffer-name))
257 (with-current-buffer new-buffer-name
258 (rename-buffer buffer-name)))))
259
260 (defmacro* ert-with-buffer-renamed ((buffer-name-form) &body body)
261 "Protect the buffer named BUFFER-NAME from side-effects and run BODY.
262
263 See `ert-call-with-buffer-renamed' for details."
264 (declare (indent 1))
265 `(ert-call-with-buffer-renamed ,buffer-name-form (lambda () ,@body)))
266
267
268 (defun ert-buffer-string-reindented (&optional buffer)
269 "Return the contents of BUFFER after reindentation.
270
271 BUFFER defaults to current buffer. Does not modify BUFFER."
272 (with-current-buffer (or buffer (current-buffer))
273 (let ((clone nil))
274 (unwind-protect
275 (progn
276 ;; `clone-buffer' doesn't work if `buffer-file-name' is non-nil.
277 (let ((buffer-file-name nil))
278 (setq clone (clone-buffer)))
279 (with-current-buffer clone
280 (let ((inhibit-read-only t))
281 (indent-region (point-min) (point-max)))
282 (buffer-string)))
283 (when clone
284 (let ((kill-buffer-query-functions nil))
285 (kill-buffer clone)))))))
286
287
288 (provide 'ert-x)
289
290 ;;; ert-x.el ends here