Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

479 lines
16 KiB

  1. ;;; test-simple.el --- Simple Unit Test Framework for Emacs Lisp -*- lexical-binding: t -*-
  2. ;; Rewritten from Phil Hagelberg's behave.el by rocky
  3. ;; Copyright (C) 2015, 2016, 2017 Free Software Foundation, Inc
  4. ;; Author: Rocky Bernstein <rocky@gnu.org>
  5. ;; URL: http://github.com/rocky/emacs-test-simple
  6. ;; Keywords: unit-test
  7. ;; Package-Requires: ((cl-lib "0"))
  8. ;; Version: 1.3.0
  9. ;; This program is free software: you can redistribute it and/or
  10. ;; modify it under the terms of the GNU General Public License as
  11. ;; published by the Free Software Foundation, either version 3 of the
  12. ;; License, or (at your option) any later version.
  13. ;; This program is distributed in the hope that it will be useful, but
  14. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. ;; General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with this program. If not, see
  19. ;; <http://www.gnu.org/licenses/>.
  20. ;;; Commentary:
  21. ;; test-simple.el is:
  22. ;;
  23. ;; * Simple. No need for
  24. ;; - context macros,
  25. ;; - enclosing specifications,
  26. ;; - required test tags.
  27. ;;
  28. ;; But if you want, you still can enclose tests in a local scope,
  29. ;; add customized assert failure messages, or add summary messages
  30. ;; before a group of tests.
  31. ;;
  32. ;; * Accommodates both interactive and non-interactive use.
  33. ;; - For interactive use, one can use `eval-last-sexp', `eval-region',
  34. ;; and `eval-buffer'. One can `edebug' the code.
  35. ;; - For non-interactive use, run:
  36. ;; emacs --batch --no-site-file --no-splash --load <test-lisp-code.el>
  37. ;;
  38. ;; Here is an example using gcd.el found in the examples directory.
  39. ;;
  40. ;; (require 'test-simple)
  41. ;; (test-simple-start) ;; Zero counters and start the stop watch.
  42. ;;
  43. ;; ;; Use (load-file) below because we want to always to read the source.
  44. ;; ;; Also, we don't want no stinking compiled source.
  45. ;; (assert-t (load-file "./gcd.el")
  46. ;; "Can't load gcd.el - are you in the right directory?" )
  47. ;;
  48. ;; (note "degenerate cases")
  49. ;;
  50. ;; (assert-nil (gcd 5 -1) "using positive numbers")
  51. ;; (assert-nil (gcd -4 1) "using positive numbers, switched order")
  52. ;; (assert-raises error (gcd "a" 32)
  53. ;; "Passing a string value should raise an error")
  54. ;;
  55. ;; (note "GCD computations")
  56. ;; (assert-equal 1 (gcd 3 5) "gcd(3,5)")
  57. ;; (assert-equal 8 (gcd 8 32) "gcd(8,32)")
  58. ;; (end-tests) ;; Stop the clock and print a summary
  59. ;;
  60. ;; Edit (with Emacs of course) gcd-tests.el and run M-x eval-current-buffer
  61. ;;
  62. ;; You should see in buffer *test-simple*:
  63. ;;
  64. ;; gcd-tests.el
  65. ;; ......
  66. ;; 0 failures in 6 assertions (0.002646 seconds)
  67. ;;
  68. ;; Now let us try from a command line:
  69. ;;
  70. ;; $ emacs --batch --no-site-file --no-splash --load gcd-tests.el
  71. ;; Loading /src/external-vcs/emacs-test-simple/example/gcd.el (source)...
  72. ;; *scratch*
  73. ;; ......
  74. ;; 0 failures in 6 assertions (0.000723 seconds)
  75. ;;; To do:
  76. ;; FIXME: Namespace is all messed up!
  77. ;; Main issues: more expect predicates
  78. (require 'time-date)
  79. ;;; Code:
  80. ;; Press C-x C-e at the end of the next line configure the program in GNU emacs
  81. ;; for building via "make" to get set up.
  82. ;; (compile (format "EMACSLOADPATH=:%s ./autogen.sh" "."))
  83. ;; After that you can run:
  84. ;; (compile "make check")
  85. (require 'cl-lib)
  86. (defgroup test-simple nil
  87. "Simple Unit Test Framework for Emacs Lisp"
  88. :group 'lisp)
  89. (defcustom test-simple-runner-interface (if (fboundp 'bpr-spawn)
  90. 'bpr-spawn
  91. 'compile)
  92. "Function with one string argument when running tests non-interactively.
  93. Command line started with `emacs --batch' is passed as the argument.
  94. `bpr-spawn', which is in bpr package, is preferable because of no window popup.
  95. If bpr is not installed, fall back to `compile'."
  96. :type 'function
  97. :group 'test-simple)
  98. (defcustom test-simple-runner-key "C-x C-z"
  99. "Key to run non-interactive test after defining command line by `test-simple-run'."
  100. :type 'string
  101. :group 'test-simple)
  102. (defvar test-simple-debug-on-error nil
  103. "If non-nil raise an error on the first failure.")
  104. (defvar test-simple-verbosity 0
  105. "The greater the number the more verbose output.")
  106. (cl-defstruct test-info
  107. description ;; description of last group of tests
  108. (assert-count 0) ;; total number of assertions run
  109. (failure-count 0) ;; total number of failures seen
  110. (start-time (current-time)) ;; Time run started
  111. )
  112. (defvar test-simple-info (make-test-info)
  113. "Variable to store testing information for a buffer.")
  114. (defun note (description &optional test-info)
  115. "Add a name to a group of tests."
  116. (if (getenv "USE_TAP")
  117. (test-simple-msg (format "# %s" description) 't)
  118. (if (> test-simple-verbosity 0)
  119. (test-simple-msg (concat "\n" description) 't))
  120. (unless test-info
  121. (setq test-info test-simple-info))
  122. (setf (test-info-description test-info) description)
  123. ))
  124. ;;;###autoload
  125. (defmacro test-simple-start (&optional test-start-msg)
  126. `(test-simple-clear nil
  127. (or ,test-start-msg
  128. (if (and (functionp '__FILE__) (__FILE__))
  129. (file-name-nondirectory (__FILE__))
  130. (buffer-name)))
  131. ))
  132. ;;;###autoload
  133. (defun test-simple-clear (&optional test-info test-start-msg)
  134. "Initialize and reset everything to run tests.
  135. You should run this before running any assertions. Running more than once
  136. clears out information from the previous run."
  137. (interactive)
  138. (unless test-info
  139. (setq test-info test-simple-info))
  140. (setf (test-info-description test-info) "none set")
  141. (setf (test-info-start-time test-info) (current-time))
  142. (setf (test-info-assert-count test-info) 0)
  143. (setf (test-info-failure-count test-info) 0)
  144. (with-current-buffer (get-buffer-create "*test-simple*")
  145. (let ((old-read-only inhibit-read-only))
  146. (setq inhibit-read-only 't)
  147. (delete-region (point-min) (point-max))
  148. (if test-start-msg (insert (format "%s\n" test-start-msg)))
  149. (setq inhibit-read-only old-read-only)))
  150. (unless noninteractive
  151. (message "Test-Simple: test information cleared")))
  152. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  153. ;; Assertion tests
  154. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  155. (defmacro assert-raises (error-condition body &optional fail-message)
  156. (let ((fail-message (or fail-message
  157. (format "assert-raises did not get expected %s"
  158. error-condition))))
  159. (list 'condition-case nil
  160. (list 'progn body
  161. (list 'assert-t nil fail-message))
  162. (list error-condition '(assert-t t)))))
  163. (defun assert-op (op expected actual &optional fail-message test-info)
  164. "Expectation is that ACTUAL should be equal to EXPECTED."
  165. (unless test-info (setq test-info test-simple-info))
  166. (cl-incf (test-info-assert-count test-info))
  167. (if (not (funcall op actual expected))
  168. (let* ((fail-message
  169. (if fail-message
  170. (format "Message: %s" fail-message)
  171. ""))
  172. (expect-message
  173. (format "\n Expected: %S\n Got: %S" expected actual))
  174. (test-info-mess
  175. (if (boundp 'test-info)
  176. (test-info-description test-info)
  177. "unset")))
  178. (test-simple--add-failure (format "assert-%s" op) test-info-mess
  179. (concat fail-message expect-message)))
  180. (test-simple--ok-msg fail-message)))
  181. (defun assert-equal (expected actual &optional fail-message test-info)
  182. "Expectation is that ACTUAL should be equal to EXPECTED."
  183. (assert-op 'equal expected actual fail-message test-info))
  184. (defun assert-eq (expected actual &optional fail-message test-info)
  185. "Expectation is that ACTUAL should be EQ to EXPECTED."
  186. (assert-op 'eql expected actual fail-message test-info))
  187. (defun assert-eql (expected actual &optional fail-message test-info)
  188. "Expectation is that ACTUAL should be EQL to EXPECTED."
  189. (assert-op 'eql expected actual fail-message test-info))
  190. (defun assert-matches (expected-regexp actual &optional fail-message test-info)
  191. "Expectation is that ACTUAL should match EXPECTED-REGEXP."
  192. (unless test-info (setq test-info test-simple-info))
  193. (cl-incf (test-info-assert-count test-info))
  194. (if (not (string-match expected-regexp actual))
  195. (let* ((fail-message
  196. (if fail-message
  197. (format "\n\tMessage: %s" fail-message)
  198. ""))
  199. (expect-message
  200. (format "\tExpected Regexp: %s\n\tGot: %s"
  201. expected-regexp actual))
  202. (test-info-mess
  203. (if (boundp 'test-info)
  204. (test-info-description test-info)
  205. "unset")))
  206. (test-simple--add-failure "assert-equal" test-info-mess
  207. (concat expect-message fail-message)))
  208. (progn (test-simple-msg ".") t)))
  209. (defun assert-t (actual &optional fail-message test-info)
  210. "expectation is that ACTUAL is not nil."
  211. (assert-nil (not actual) fail-message test-info))
  212. (defun assert-nil (actual &optional fail-message test-info)
  213. "expectation is that ACTUAL is nil. FAIL-MESSAGE is an optional
  214. additional message to be displayed."
  215. (unless test-info (setq test-info test-simple-info))
  216. (cl-incf (test-info-assert-count test-info))
  217. (if actual
  218. (let* ((fail-message
  219. (if fail-message
  220. (format "\n\tMessage: %s" fail-message)
  221. ""))
  222. (test-info-mess
  223. (if (boundp 'test-simple-info)
  224. (test-info-description test-simple-info)
  225. "unset")))
  226. (test-simple--add-failure "assert-nil" test-info-mess
  227. fail-message test-info))
  228. (test-simple--ok-msg fail-message)))
  229. (defun test-simple--add-failure (type test-info-msg fail-msg
  230. &optional test-info)
  231. (unless test-info (setq test-info test-simple-info))
  232. (cl-incf (test-info-failure-count test-info))
  233. (let ((failure-msg
  234. (format "\nDescription: %s, type %s\n%s" test-info-msg type fail-msg))
  235. )
  236. (save-excursion
  237. (test-simple--not-ok-msg fail-msg)
  238. (test-simple-msg failure-msg 't)
  239. (unless noninteractive
  240. (if test-simple-debug-on-error
  241. (signal 'test-simple-assert-failed failure-msg)
  242. ;;(message failure-msg)
  243. )))))
  244. (defun end-tests (&optional test-info)
  245. "Give a tally of the tests run."
  246. (interactive)
  247. (unless test-info (setq test-info test-simple-info))
  248. (test-simple-describe-failures test-info)
  249. (cond (noninteractive
  250. (set-buffer "*test-simple*")
  251. (cond ((getenv "USE_TAP")
  252. (princ (format "%s\n" (buffer-string)))
  253. )
  254. (t ;; non-TAP goes to stderr (backwards compatibility)
  255. (message "%s" (buffer-substring (point-min) (point-max)))
  256. )))
  257. (t ;; interactive
  258. (switch-to-buffer-other-window "*test-simple*")
  259. )))
  260. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  261. ;; Reporting
  262. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  263. (defun test-simple-msg(msg &optional newline)
  264. (switch-to-buffer "*test-simple*")
  265. (let ((inhibit-read-only t))
  266. (insert msg)
  267. (if newline (insert "\n"))
  268. (switch-to-buffer nil)
  269. ))
  270. (defun test-simple--ok-msg (fail-message &optional test-info)
  271. (unless test-info (setq test-info test-simple-info))
  272. (let ((msg (if (getenv "USE_TAP")
  273. (if (equal fail-message "")
  274. (format "ok %d\n" (test-info-assert-count test-info))
  275. (format "ok %d - %s\n"
  276. (test-info-assert-count test-info)
  277. fail-message))
  278. ".")))
  279. (test-simple-msg msg))
  280. 't)
  281. (defun test-simple--not-ok-msg (_fail-message &optional test-info)
  282. (unless test-info (setq test-info test-simple-info))
  283. (let ((msg (if (getenv "USE_TAP")
  284. (format "not ok %d\n" (test-info-assert-count test-info))
  285. "F")))
  286. (test-simple-msg msg))
  287. nil)
  288. (defun test-simple-summary-line(info)
  289. (let*
  290. ((failures (test-info-failure-count info))
  291. (asserts (test-info-assert-count info))
  292. (problems (concat (number-to-string failures) " failure"
  293. (unless (= 1 failures) "s")))
  294. (tests (concat (number-to-string asserts) " assertion"
  295. (unless (= 1 asserts) "s")))
  296. (elapsed-time (time-since (test-info-start-time info)))
  297. )
  298. (if (getenv "USE_TAP")
  299. (format "1..%d" asserts)
  300. (format "\n%s in %s (%g seconds)" problems tests
  301. (float-time elapsed-time))
  302. )))
  303. (defun test-simple-describe-failures(&optional test-info)
  304. (unless test-info (setq test-info test-simple-info))
  305. (goto-char (point-max))
  306. (test-simple-msg (test-simple-summary-line test-info)))
  307. ;;;###autoload
  308. (defun test-simple-run (&rest command-line-formats)
  309. "Register command line to run tests non-interactively and bind key to run test.
  310. After calling this function, you can run test by key specified by `test-simple-runner-key'.
  311. It is preferable to write at the first line of test files as a comment, e.g,
  312. ;;;; (test-simple-run \"emacs -batch -L %s -l %s\" (file-name-directory (locate-library \"test-simple.elc\")) buffer-file-name)
  313. Calling this function interactively, COMMAND-LINE-FORMATS is set above."
  314. (interactive)
  315. (setq command-line-formats
  316. (or command-line-formats
  317. (list "emacs -batch -L %s -l %s"
  318. (file-name-directory (locate-library "test-simple.elc"))
  319. buffer-file-name)))
  320. (let ((func (lambda ()
  321. (interactive)
  322. (funcall test-simple-runner-interface
  323. (apply 'format command-line-formats)))))
  324. (global-set-key (kbd test-simple-runner-key) func)
  325. (funcall func)))
  326. (defun test-simple-noninteractive-kill-emacs-hook ()
  327. "Emacs exits abnormally when noninteractive test fails."
  328. (when (and noninteractive test-simple-info
  329. (<= 1 (test-info-failure-count test-simple-info)))
  330. (let (kill-emacs-hook)
  331. (kill-emacs 1))))
  332. (when noninteractive
  333. (add-hook 'kill-emacs-hook 'test-simple-noninteractive-kill-emacs-hook))
  334. ;;;; ChangeLog:
  335. ;; 2017-05-25 rocky <rocky@gnu.org>
  336. ;;
  337. ;; Merge commit '604942d36021a8b14877a0a640234a09c79e0927'
  338. ;;
  339. ;; 2016-03-03 rocky <rocky@gnu.org>
  340. ;;
  341. ;; Version 1.2.0 Sync with github
  342. ;;
  343. ;; github syohex:
  344. ;; * Switch from Carton to Cask
  345. ;;
  346. ;; rubikitch@ruby-lang.org:
  347. ;; * test-simple.el: test-simple-run: make it a command.
  348. ;; * README.md: Mention test-simple-run
  349. ;; * example/gcd-tests.el: gcd-tests.el: Add test-simple-run comment line
  350. ;; * test-simple.el: Emacs exits abnormally when noninteractive test fails.
  351. ;; * test-simple.el: New function `test-simple-run': register test You can
  352. ;; run tests easily by pressing C-x C-z.
  353. ;; * test-basic.el: fix botched joke
  354. ;;
  355. ;; 2016-03-03 rocky <rocky@gnu.org>
  356. ;;
  357. ;; Merge branch 'master' of git.sv.gnu.org:/srv/git/emacs/elpa
  358. ;;
  359. ;; 2015-04-26 Stefan Monnier <monnier@iro.umontreal.ca>
  360. ;;
  361. ;; * packages/test-simple/test-simple.el: Use cl-lib
  362. ;;
  363. ;; (test-simple-msg): Let-bind inhibit-read-only directly.
  364. ;; (test-simple--ok-msg, test-simple--not-ok-msg): Rename from ok-msg and
  365. ;; not-ok-msg.
  366. ;; * packages/test-simple/ChangeLog: Remove empty file.
  367. ;;
  368. ;; 2015-03-31 rocky <rocky@gnu.org>
  369. ;;
  370. ;; Merge commit 'bb13df55aa357538f95c3a8a28cac18533f5d164'
  371. ;;
  372. ;; 2015-02-16 rocky <rocky@gnu.org>
  373. ;;
  374. ;; Merge commit '3fd5ea161e41d94902ef499b41f7032ef07f6430'
  375. ;;
  376. ;; 2015-02-15 rocky <rocky@gnu.org>
  377. ;;
  378. ;; Merge commit 'ec7ba4f2dbae0901724483de5868127a1cbc38e9'
  379. ;;
  380. ;; 2015-02-15 rocky <rocky@gnu.org>
  381. ;;
  382. ;; Merge commit '7fe5510edce15f5733552bb4d9de4f5ab1e0de76'
  383. ;;
  384. ;; 2015-02-15 rocky <rocky@gnu.org>
  385. ;;
  386. ;; Add 'packages/test-simple/' from commit
  387. ;; '75eea25bae04d8e5e3e835a2770f02f0ff4602c4'
  388. ;;
  389. ;; git-subtree-dir: packages/test-simple git-subtree-mainline:
  390. ;; bfb36f072e1d8b382639bd5cc6087fb0c963894b git-subtree-split:
  391. ;; 75eea25bae04d8e5e3e835a2770f02f0ff4602c4
  392. ;;
  393. ;; 2015-02-15 rocky <rocky@gnu.org>
  394. ;;
  395. ;; Oops - should have added in branch externals/<pkg>
  396. ;;
  397. ;; 2015-02-15 rocky <rocky@gnu.org>
  398. ;;
  399. ;; Add 'packages/test-simple/' from commit
  400. ;; '75eea25bae04d8e5e3e835a2770f02f0ff4602c4'
  401. ;;
  402. ;; git-subtree-dir: packages/test-simple git-subtree-mainline:
  403. ;; b3736acc55750eb13c8d21579ce022bc5a077568 git-subtree-split:
  404. ;; 75eea25bae04d8e5e3e835a2770f02f0ff4602c4
  405. ;;
  406. ;; 2015-02-15 rocky <rocky@gnu.org>
  407. ;;
  408. ;; Remove realgud and dependents as a git submodule
  409. ;;
  410. ;; 2015-02-15 rocky <rocky@gnu.org>
  411. ;;
  412. ;; New gud replacement package: realgud (a front end interface to
  413. ;; debuggers).
  414. ;;
  415. ;; This package requires:
  416. ;;
  417. ;; * loc-changes - location marks in buffers
  418. ;; * load-relative - load emacs lisp relative to emacs source
  419. ;; * test-simple - simple test framework.
  420. ;;
  421. ;; Using .gitmodules in this way is a bit of an experiment. Currently these
  422. ;; four projects are separate github repositories.
  423. ;;
  424. (provide 'test-simple)
  425. ;;; test-simple.el ends here