ert-x.el 11 KB

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