Emacs config utilizing prelude as a base
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.

448 lines
18 KiB

  1. ;;
  2. ;; %CopyrightBegin%
  3. ;;
  4. ;; Copyright Ericsson AB 2009-2010. All Rights Reserved.
  5. ;;
  6. ;; The contents of this file are subject to the Erlang Public License,
  7. ;; Version 1.1, (the "License"); you may not use this file except in
  8. ;; compliance with the License. You should have received a copy of the
  9. ;; Erlang Public License along with this software. If not, it can be
  10. ;; retrieved online at http://www.erlang.org/.
  11. ;;
  12. ;; Software distributed under the License is distributed on an "AS IS"
  13. ;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  14. ;; the License for the specific language governing rights and limitations
  15. ;; under the License.
  16. ;;
  17. ;; %CopyrightEnd%
  18. ;;;
  19. ;;; Purpose: Provide EUnit utilities.
  20. ;;;
  21. ;;; Author: Klas Johansson
  22. (eval-when-compile
  23. (require 'cl))
  24. (defvar erlang-eunit-src-candidate-dirs '("../src" ".")
  25. "*Name of directories which to search for source files matching
  26. an EUnit test file. The first directory in the list will be used,
  27. if there is no match.")
  28. (defvar erlang-eunit-test-candidate-dirs '("../test" ".")
  29. "*Name of directories which to search for EUnit test files matching
  30. a source file. The first directory in the list will be used,
  31. if there is no match.")
  32. (defvar erlang-eunit-autosave nil
  33. "*Set to non-nil to automtically save unsaved buffers before running tests.
  34. This is useful, reducing the save-compile-load-test cycle to one keychord.")
  35. (defvar erlang-eunit-recent-info '((mode . nil) (module . nil) (test . nil) (cover . nil))
  36. "Info about the most recent running of an EUnit test representation.")
  37. ;;;
  38. ;;; Switch between src/EUnit test buffers
  39. ;;;
  40. (defun erlang-eunit-toggle-src-and-test-file-other-window ()
  41. "Switch to the src file if the EUnit test file is the current
  42. buffer and vice versa"
  43. (interactive)
  44. (if (erlang-eunit-test-file-p buffer-file-name)
  45. (erlang-eunit-open-src-file-other-window buffer-file-name)
  46. (erlang-eunit-open-test-file-other-window buffer-file-name)))
  47. ;;;
  48. ;;; Open the EUnit test file which corresponds to a src file
  49. ;;;
  50. (defun erlang-eunit-open-test-file-other-window (src-file-path)
  51. "Open the EUnit test file which corresponds to a src file"
  52. (find-file-other-window (erlang-eunit-test-filename src-file-path)))
  53. ;;;
  54. ;;; Open the src file which corresponds to the an EUnit test file
  55. ;;;
  56. (defun erlang-eunit-open-src-file-other-window (test-file-path)
  57. "Open the src file which corresponds to the an EUnit test file"
  58. (find-file-other-window (erlang-eunit-src-filename test-file-path)))
  59. ;;; Return the name and path of the EUnit test file
  60. ;;, (input may be either the source filename itself or the EUnit test filename)
  61. (defun erlang-eunit-test-filename (file-path)
  62. (if (erlang-eunit-test-file-p file-path)
  63. file-path
  64. (erlang-eunit-rewrite-filename file-path erlang-eunit-test-candidate-dirs)))
  65. ;;; Return the name and path of the source file
  66. ;;, (input may be either the source filename itself or the EUnit test filename)
  67. (defun erlang-eunit-src-filename (file-path)
  68. (if (erlang-eunit-src-file-p file-path)
  69. file-path
  70. (erlang-eunit-rewrite-filename file-path erlang-eunit-src-candidate-dirs)))
  71. ;;; Rewrite a filename from the src or test filename to the other
  72. (defun erlang-eunit-rewrite-filename (orig-file-path candidate-dirs)
  73. (or (erlang-eunit-locate-buddy orig-file-path candidate-dirs)
  74. (erlang-eunit-buddy-file-path orig-file-path (car candidate-dirs))))
  75. ;;; Search for a file's buddy file (a source file's EUnit test file,
  76. ;;; or an EUnit test file's source file) in a list of candidate
  77. ;;; directories.
  78. (defun erlang-eunit-locate-buddy (orig-file-path candidate-dirs)
  79. (when candidate-dirs
  80. (let ((buddy-file-path (erlang-eunit-buddy-file-path
  81. orig-file-path
  82. (car candidate-dirs))))
  83. (if (file-readable-p buddy-file-path)
  84. buddy-file-path
  85. (erlang-eunit-locate-buddy orig-file-path (cdr candidate-dirs))))))
  86. (defun erlang-eunit-buddy-file-path (orig-file-path buddy-dir-name)
  87. (let* ((orig-dir-name (file-name-directory orig-file-path))
  88. (buddy-dir-name (file-truename
  89. (filename-join orig-dir-name buddy-dir-name)))
  90. (buddy-base-name (erlang-eunit-buddy-basename orig-file-path)))
  91. (filename-join buddy-dir-name buddy-base-name)))
  92. ;;; Return the basename of the buddy file:
  93. ;;; /tmp/foo/src/x.erl --> x_tests.erl
  94. ;;; /tmp/foo/test/x_tests.erl --> x.erl
  95. (defun erlang-eunit-buddy-basename (file-path)
  96. (let ((src-module-name (erlang-eunit-source-module-name file-path)))
  97. (cond
  98. ((erlang-eunit-src-file-p file-path)
  99. (concat src-module-name "_tests.erl"))
  100. ((erlang-eunit-test-file-p file-path)
  101. (concat src-module-name ".erl")))))
  102. ;;; Checks whether a file is a source file or not
  103. (defun erlang-eunit-src-file-p (file-path)
  104. (not (erlang-eunit-test-file-p file-path)))
  105. ;;; Checks whether a file is a EUnit test file or not
  106. (defun erlang-eunit-test-file-p (file-path)
  107. (erlang-eunit-string-match-p "^\\(.+\\)_tests.erl$" file-path))
  108. ;;; Return the module name of the source file
  109. ;;; /tmp/foo/src/x.erl --> x
  110. ;;; /tmp/foo/test/x_tests.erl --> x
  111. (defun erlang-eunit-source-module-name (file-path)
  112. (interactive)
  113. (let ((module-name (erlang-eunit-module-name file-path)))
  114. (if (string-match "^\\(.+\\)_tests$" module-name)
  115. (substring module-name (match-beginning 1) (match-end 1))
  116. module-name)))
  117. ;;; Return the module name of the file
  118. ;;; /tmp/foo/src/x.erl --> x
  119. ;;; /tmp/foo/test/x_tests.erl --> x_tests
  120. (defun erlang-eunit-module-name (file-path)
  121. (interactive)
  122. (file-name-sans-extension (file-name-nondirectory file-path)))
  123. ;;; Older emacsen don't have string-match-p.
  124. (defun erlang-eunit-string-match-p (regexp string &optional start)
  125. (if (fboundp 'string-match-p) ;; appeared in emacs 23
  126. (string-match-p regexp string start)
  127. (save-match-data ;; fallback for earlier versions of emacs
  128. (string-match regexp string start))))
  129. ;;; Join filenames
  130. (defun filename-join (dir file)
  131. (if (or (= (elt file 0) ?/)
  132. (= (car (last (append dir nil))) ?/))
  133. (concat dir file)
  134. (concat dir "/" file)))
  135. ;;; Get info about the most recent running of EUnit
  136. (defun erlang-eunit-recent (key)
  137. (cdr (assq key erlang-eunit-recent-info)))
  138. ;;; Record info about the most recent running of EUnit
  139. ;;; Known modes are 'module-mode and 'test-mode
  140. (defun erlang-eunit-record-recent (mode module test)
  141. (setcdr (assq 'mode erlang-eunit-recent-info) mode)
  142. (setcdr (assq 'module erlang-eunit-recent-info) module)
  143. (setcdr (assq 'test erlang-eunit-recent-info) test))
  144. ;;; Record whether the most recent running of EUnit included cover
  145. ;;; compilation
  146. (defun erlang-eunit-record-recent-compile (under-cover)
  147. (setcdr (assq 'cover erlang-eunit-recent-info) under-cover))
  148. ;;; Determine options for EUnit.
  149. (defun erlang-eunit-opts ()
  150. (if current-prefix-arg ", [verbose]" ""))
  151. ;;; Determine current test function
  152. (defun erlang-eunit-current-test ()
  153. (save-excursion
  154. (erlang-end-of-function 1)
  155. (erlang-beginning-of-function 1)
  156. (erlang-name-of-function)))
  157. (defun erlang-eunit-simple-test-p (test-name)
  158. (if (erlang-eunit-string-match-p "^\\(.+\\)_test$" test-name) t nil))
  159. (defun erlang-eunit-test-generator-p (test-name)
  160. (if (erlang-eunit-string-match-p "^\\(.+\\)_test_$" test-name) t nil))
  161. ;;; Run one EUnit test
  162. (defun erlang-eunit-run-test (module-name test-name)
  163. (let ((command
  164. (cond ((erlang-eunit-simple-test-p test-name)
  165. (format "eunit:test({%s, %s}%s)."
  166. module-name test-name (erlang-eunit-opts)))
  167. ((erlang-eunit-test-generator-p test-name)
  168. (format "eunit:test({generator, %s, %s}%s)."
  169. module-name test-name (erlang-eunit-opts)))
  170. (t (format "%% WARNING: '%s' is not a test function" test-name)))))
  171. (erlang-eunit-record-recent 'test-mode module-name test-name)
  172. (erlang-eunit-inferior-erlang-send-command command)))
  173. ;;; Run EUnit tests for the current module
  174. (defun erlang-eunit-run-module-tests (module-name)
  175. (let ((command (format "eunit:test(%s%s)." module-name (erlang-eunit-opts))))
  176. (erlang-eunit-record-recent 'module-mode module-name nil)
  177. (erlang-eunit-inferior-erlang-send-command command)))
  178. (defun erlang-eunit-compile-and-run-recent ()
  179. "Compile the source and test files and repeat the most recent EUnit test run.
  180. With prefix arg, compiles for debug and runs tests with the verbose flag set."
  181. (interactive)
  182. (case (erlang-eunit-recent 'mode)
  183. ('test-mode
  184. (erlang-eunit-compile-and-test
  185. 'erlang-eunit-run-test (list (erlang-eunit-recent 'module)
  186. (erlang-eunit-recent 'test))))
  187. ('module-mode
  188. (erlang-eunit-compile-and-test
  189. 'erlang-eunit-run-module-tests (list (erlang-eunit-recent 'module))
  190. (erlang-eunit-recent 'cover)))
  191. (t (error "EUnit has not yet been run. Please run a test first."))))
  192. (defun erlang-eunit-cover-compile ()
  193. "Cover compile current module."
  194. (interactive)
  195. (let* ((erlang-compile-extra-opts
  196. (append (list 'debug_info) erlang-compile-extra-opts))
  197. (module-name
  198. (erlang-add-quotes-if-needed
  199. (erlang-eunit-module-name buffer-file-name)))
  200. (compile-command
  201. (format "cover:compile_beam(%s)." module-name)))
  202. (erlang-compile)
  203. (if (erlang-eunit-last-compilation-successful-p)
  204. (erlang-eunit-inferior-erlang-send-command compile-command))))
  205. (defun erlang-eunit-analyze-coverage ()
  206. "Analyze the data collected by cover tool for the module in the
  207. current buffer.
  208. Assumes that the module has been cover compiled prior to this
  209. call. This function will do two things: print the number of
  210. covered and uncovered functions in the erlang shell and display a
  211. new buffer called *<module name> coverage* which shows the source
  212. code along with the coverage analysis results."
  213. (interactive)
  214. (let* ((module-name (erlang-add-quotes-if-needed
  215. (erlang-eunit-module-name buffer-file-name)))
  216. (tmp-filename (make-temp-file "cover"))
  217. (analyze-command (format "cover:analyze_to_file(%s, \"%s\"). "
  218. module-name tmp-filename))
  219. (buf-name (format "*%s coverage*" module-name)))
  220. (erlang-eunit-inferior-erlang-send-command analyze-command)
  221. ;; The purpose of the following snippet is to get the result of the
  222. ;; analysis from a file into a new buffer (or an old, if one with
  223. ;; the specified name already exists). Also we want the erlang-mode
  224. ;; *and* view-mode to be enabled.
  225. (save-excursion
  226. (let ((buf (get-buffer-create (format "*%s coverage*" module-name))))
  227. (set-buffer buf)
  228. (setq buffer-read-only nil)
  229. (insert-file-contents tmp-filename nil nil nil t)
  230. (if (= (buffer-size) 0)
  231. (kill-buffer buf)
  232. ;; FIXME: this would be a good place to enable (emacs-mode)
  233. ;; to get some nice syntax highlighting in the
  234. ;; coverage report, but it doesn't play well with
  235. ;; flymake. Leave it off for now.
  236. (view-buffer buf))))
  237. (delete-file tmp-filename)))
  238. (defun erlang-eunit-compile-and-run-current-test ()
  239. "Compile the source and test files and run the current EUnit test.
  240. With prefix arg, compiles for debug and runs tests with the verbose flag set."
  241. (interactive)
  242. (let ((module-name (erlang-add-quotes-if-needed
  243. (erlang-eunit-module-name buffer-file-name)))
  244. (test-name (erlang-eunit-current-test)))
  245. (erlang-eunit-compile-and-test
  246. 'erlang-eunit-run-test (list module-name test-name))))
  247. (defun erlang-eunit-compile-and-run-module-tests ()
  248. "Compile the source and test files and run all EUnit tests in the module.
  249. With prefix arg, compiles for debug and runs tests with the verbose flag set."
  250. (interactive)
  251. (let ((module-name (erlang-add-quotes-if-needed
  252. (erlang-eunit-source-module-name buffer-file-name))))
  253. (erlang-eunit-compile-and-test
  254. 'erlang-eunit-run-module-tests (list module-name))))
  255. ;;; Compile source and EUnit test file and finally run EUnit tests for
  256. ;;; the current module
  257. (defun erlang-eunit-compile-and-test (test-fun test-args &optional under-cover)
  258. "Compile the source and test files and run the EUnit test suite.
  259. If under-cover is set to t, the module under test is compile for
  260. code coverage analysis. If under-cover is left out or not set,
  261. coverage analysis is disabled. The result of the code coverage
  262. is both printed to the erlang shell (the number of covered vs
  263. uncovered functions in a module) and written to a buffer called
  264. *<module> coverage* (which shows the source code for the module
  265. and the number of times each line is covered).
  266. With prefix arg, compiles for debug and runs tests with the verbose flag set."
  267. (erlang-eunit-record-recent-compile under-cover)
  268. (let ((src-filename (erlang-eunit-src-filename buffer-file-name))
  269. (test-filename (erlang-eunit-test-filename buffer-file-name)))
  270. ;; The purpose of out-maneuvering `save-some-buffers', as is done
  271. ;; below, is to ask the question about saving buffers only once,
  272. ;; instead of possibly several: one for each file to compile,
  273. ;; for instance for both x.erl and x_tests.erl.
  274. (save-some-buffers erlang-eunit-autosave)
  275. (flet ((save-some-buffers (&optional any) nil))
  276. ;; Compilation of the source file is mandatory (the file must
  277. ;; exist, otherwise the procedure is aborted). Compilation of the
  278. ;; test file on the other hand, is optional, since eunit tests may
  279. ;; be placed in the source file instead. Any compilation error
  280. ;; will prevent the subsequent steps to be run (hence the `and')
  281. (and (erlang-eunit-compile-file src-filename under-cover)
  282. (if (file-readable-p test-filename)
  283. (erlang-eunit-compile-file test-filename)
  284. t)
  285. (apply test-fun test-args)
  286. (if under-cover
  287. (save-excursion
  288. (set-buffer (find-file-noselect src-filename))
  289. (erlang-eunit-analyze-coverage)))))))
  290. (defun erlang-eunit-compile-and-run-module-tests-under-cover ()
  291. "Compile the source and test files and run the EUnit test suite and measure
  292. code coverage.
  293. With prefix arg, compiles for debug and runs tests with the verbose flag set."
  294. (interactive)
  295. (let ((module-name (erlang-add-quotes-if-needed
  296. (erlang-eunit-source-module-name buffer-file-name))))
  297. (erlang-eunit-compile-and-test
  298. 'erlang-eunit-run-module-tests (list module-name) t)))
  299. (defun erlang-eunit-compile-file (file-path &optional under-cover)
  300. (if (file-readable-p file-path)
  301. (save-excursion
  302. (set-buffer (find-file-noselect file-path))
  303. ;; In order to run a code coverage analysis on a
  304. ;; module, we have two options:
  305. ;;
  306. ;; * either compile the module with cover:compile instead of the
  307. ;; regular compiler
  308. ;;
  309. ;; * or first compile the module with the regular compiler (but
  310. ;; *with* debug_info) and then compile it for coverage
  311. ;; analysis using cover:compile_beam.
  312. ;;
  313. ;; We could accomplish the first by changing the
  314. ;; erlang-compile-erlang-function to cover:compile, but there's
  315. ;; a risk that that's used for other purposes. Therefore, a
  316. ;; safer alternative (although with more steps) is to add
  317. ;; debug_info to the list of compiler options and go for the
  318. ;; second alternative.
  319. (if under-cover
  320. (erlang-eunit-cover-compile)
  321. (erlang-compile))
  322. (erlang-eunit-last-compilation-successful-p))
  323. (let ((msg (format "Could not read %s" file-path)))
  324. (erlang-eunit-inferior-erlang-send-command
  325. (format "%% WARNING: %s" msg))
  326. (error msg))))
  327. (defun erlang-eunit-last-compilation-successful-p ()
  328. (save-excursion
  329. (set-buffer inferior-erlang-buffer)
  330. (goto-char compilation-parsing-end)
  331. (erlang-eunit-all-list-elems-fulfill-p
  332. (lambda (re) (let ((continue t)
  333. (result t))
  334. (while continue ; ignore warnings, stop at errors
  335. (if (re-search-forward re (point-max) t)
  336. (if (erlang-eunit-is-compilation-warning)
  337. t
  338. (setq result nil)
  339. (setq continue nil))
  340. (setq result t)
  341. (setq continue nil)))
  342. result))
  343. (mapcar (lambda (e) (car e)) erlang-error-regexp-alist))))
  344. (defun erlang-eunit-is-compilation-warning ()
  345. (erlang-eunit-string-match-p
  346. "[0-9]+: Warning:"
  347. (buffer-substring (line-beginning-position) (line-end-position))))
  348. (defun erlang-eunit-all-list-elems-fulfill-p (pred list)
  349. (let ((matches-p t))
  350. (while (and list matches-p)
  351. (if (not (funcall pred (car list)))
  352. (setq matches-p nil))
  353. (setq list (cdr list)))
  354. matches-p))
  355. ;;; Evaluate a command in an erlang buffer
  356. (defun erlang-eunit-inferior-erlang-send-command (command)
  357. "Evaluate a command in an erlang buffer."
  358. (interactive "P")
  359. (inferior-erlang-prepare-for-input)
  360. (inferior-erlang-send-command command)
  361. (sit-for 0) ;; redisplay
  362. (inferior-erlang-wait-prompt))
  363. ;;;====================================================================
  364. ;;; Key bindings
  365. ;;;====================================================================
  366. (defconst erlang-eunit-key-bindings
  367. '(("\C-c\C-et" erlang-eunit-toggle-src-and-test-file-other-window)
  368. ("\C-c\C-ek" erlang-eunit-compile-and-run-module-tests)
  369. ("\C-c\C-ej" erlang-eunit-compile-and-run-current-test)
  370. ("\C-c\C-el" erlang-eunit-compile-and-run-recent)
  371. ("\C-c\C-ec" erlang-eunit-compile-and-run-module-tests-under-cover)
  372. ("\C-c\C-ev" erlang-eunit-cover-compile)
  373. ("\C-c\C-ea" erlang-eunit-analyze-coverage)))
  374. (defun erlang-eunit-add-key-bindings ()
  375. (dolist (binding erlang-eunit-key-bindings)
  376. (erlang-eunit-bind-key (car binding) (cadr binding))))
  377. (defun erlang-eunit-bind-key (key function)
  378. (erlang-eunit-ensure-keymap-for-key key)
  379. (local-set-key key function))
  380. (defun erlang-eunit-ensure-keymap-for-key (key-seq)
  381. (let ((prefix-keys (butlast (append key-seq nil)))
  382. (prefix-seq ""))
  383. (while prefix-keys
  384. (setq prefix-seq (concat prefix-seq (make-string 1 (car prefix-keys))))
  385. (setq prefix-keys (cdr prefix-keys))
  386. (if (not (keymapp (lookup-key (current-local-map) prefix-seq)))
  387. (local-set-key prefix-seq (make-sparse-keymap))))))
  388. (add-hook 'erlang-mode-hook 'erlang-eunit-add-key-bindings)
  389. (provide 'erlang-eunit)
  390. ;; erlang-eunit ends here