ert-x.el 10 KB

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