4
;; Copyright Ericsson AB 2009. All Rights Reserved.
4
;; Copyright Ericsson AB 2009-2010. All Rights Reserved.
6
6
;; The contents of this file are subject to the Erlang Public License,
7
7
;; Version 1.1, (the "License"); you may not use this file except in
8
8
;; compliance with the License. You should have received a copy of the
9
9
;; Erlang Public License along with this software. If not, it can be
10
10
;; retrieved online at http://www.erlang.org/.
12
12
;; Software distributed under the License is distributed on an "AS IS"
13
13
;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
14
;; the License for the specific language governing rights and limitations
15
15
;; under the License.
19
19
;;; Purpose: Provide EUnit utilities.
21
21
;;; Author: Klas Johansson
23
(defvar erlang-eunit-separate-src-and-test-directories t
24
"*Whether or not to keep source and EUnit test files in separate directories")
26
(defvar erlang-eunit-src-candidate-dirs '("../src" ".")
27
"*Name of directories which to search for source files matching
28
an EUnit test file. The first directory in the list will be used,
29
if there is no match.")
31
(defvar erlang-eunit-test-candidate-dirs '("../test" ".")
32
"*Name of directories which to search for EUnit test files matching
33
a source file. The first directory in the list will be used,
34
if there is no match.")
36
(defvar erlang-eunit-autosave nil
37
"*Set to non-nil to automtically save unsaved buffers before running tests.
38
This is useful, reducing the save-compile-load-test cycle to one keychord.")
40
(defvar erlang-eunit-recent-info '((mode . nil) (module . nil) (test . nil) (cover . nil))
41
"Info about the most recent running of an EUnit test representation.")
27
44
;;; Switch between src/EUnit test buffers
52
68
;;; Return the name and path of the EUnit test file
53
69
;;, (input may be either the source filename itself or the EUnit test filename)
54
70
(defun erlang-eunit-test-filename (file-path)
55
(erlang-eunit-rewrite-filename file-path "test" "_tests"))
71
(if (erlang-eunit-test-file-p file-path)
73
(erlang-eunit-rewrite-filename file-path erlang-eunit-test-candidate-dirs)))
57
75
;;; Return the name and path of the source file
58
76
;;, (input may be either the source filename itself or the EUnit test filename)
59
77
(defun erlang-eunit-src-filename (file-path)
60
(erlang-eunit-rewrite-filename file-path "src" ""))
78
(if (erlang-eunit-src-file-p file-path)
80
(erlang-eunit-rewrite-filename file-path erlang-eunit-src-candidate-dirs)))
62
82
;;; Rewrite a filename from the src or test filename to the other
63
(defun erlang-eunit-rewrite-filename (orig-file-path dest-dirname dest-suffix)
64
(let* ((root-dir-name (erlang-eunit-file-root-dir-name orig-file-path))
65
(src-module-name (erlang-eunit-source-module-name orig-file-path))
66
(dest-base-name (concat src-module-name dest-suffix ".erl"))
67
(dest-dir-name-1 (file-name-directory orig-file-path))
68
(dest-dir-name-2 (filename-join root-dir-name dest-dirname))
69
(dest-file-name-1 (filename-join dest-dir-name-1 dest-base-name))
70
(dest-file-name-2 (filename-join dest-dir-name-2 dest-base-name)))
71
;; This function tries to be a bit intelligent:
72
;; * if there already is a test (or source) file in the same
73
;; directory as a source (or test) file, it'll be picked
74
;; * if there already is a test (or source) file in a separate
75
;; test (or src) directory, it'll be picked
76
;; * otherwise it'll resort to whatever alternative (same or
77
;; separate directories) that the user has chosen
78
(cond ((file-readable-p dest-file-name-1)
80
((file-readable-p dest-file-name-2)
82
(erlang-eunit-separate-src-and-test-directories
83
(defun erlang-eunit-rewrite-filename (orig-file-path candidate-dirs)
84
(or (erlang-eunit-locate-buddy orig-file-path candidate-dirs)
85
(erlang-eunit-buddy-file-path orig-file-path (car candidate-dirs))))
87
;;; Search for a file's buddy file (a source file's EUnit test file,
88
;;; or an EUnit test file's source file) in a list of candidate
90
(defun erlang-eunit-locate-buddy (orig-file-path candidate-dirs)
92
(let ((buddy-file-path (erlang-eunit-buddy-file-path
94
(car candidate-dirs))))
95
(if (file-readable-p buddy-file-path)
97
(erlang-eunit-locate-buddy orig-file-path (cdr candidate-dirs))))))
99
(defun erlang-eunit-buddy-file-path (orig-file-path buddy-dir-name)
100
(let* ((orig-dir-name (file-name-directory orig-file-path))
101
(buddy-dir-name (file-truename
102
(filename-join orig-dir-name buddy-dir-name)))
103
(buddy-base-name (erlang-eunit-buddy-basename orig-file-path)))
104
(filename-join buddy-dir-name buddy-base-name)))
106
;;; Return the basename of the buddy file:
107
;;; /tmp/foo/src/x.erl --> x_tests.erl
108
;;; /tmp/foo/test/x_tests.erl --> x.erl
109
(defun erlang-eunit-buddy-basename (file-path)
110
(let ((src-module-name (erlang-eunit-source-module-name file-path)))
112
((erlang-eunit-src-file-p file-path)
113
(concat src-module-name "_tests.erl"))
114
((erlang-eunit-test-file-p file-path)
115
(concat src-module-name ".erl")))))
117
;;; Checks whether a file is a source file or not
118
(defun erlang-eunit-src-file-p (file-path)
119
(not (erlang-eunit-test-file-p file-path)))
87
121
;;; Checks whether a file is a EUnit test file or not
88
122
(defun erlang-eunit-test-file-p (file-path)
93
127
;;; /tmp/foo/test/x_tests.erl --> x
94
128
(defun erlang-eunit-source-module-name (file-path)
96
(let* ((file-name (file-name-nondirectory file-path))
97
(base-name (file-name-sans-extension file-name)))
98
(if (string-match "^\\(.+\\)_tests$" base-name)
99
(substring base-name (match-beginning 1) (match-end 1))
102
;;; Return the directory name which is common to both src and test
103
;;; /tmp/foo/src/x.erl --> /tmp/foo
104
;;; /tmp/foo/test/x_tests.erl --> /tmp/foo
105
(defun erlang-eunit-file-root-dir-name (file-path)
106
(erlang-eunit-dir-parent-dirname (file-name-directory file-path)))
108
;;; Return the parent directory name of a directory
109
;;; /tmp/foo/ --> /tmp
110
;;; /tmp/foo --> /tmp
111
(defun erlang-eunit-dir-parent-dirname (dir-name)
112
(file-name-directory (directory-file-name dir-name)))
130
(let ((module-name (erlang-eunit-module-name file-path)))
131
(if (string-match "^\\(.+\\)_tests$" module-name)
132
(substring module-name (match-beginning 1) (match-end 1))
135
;;; Return the module name of the file
136
;;; /tmp/foo/src/x.erl --> x
137
;;; /tmp/foo/test/x_tests.erl --> x_tests
138
(defun erlang-eunit-module-name (file-path)
140
(file-name-sans-extension (file-name-nondirectory file-path)))
114
142
;;; Older emacsen don't have string-match-p.
115
143
(defun erlang-eunit-string-match-p (regexp string &optional start)
125
153
(concat dir file)
126
154
(concat dir "/" file)))
156
;;; Get info about the most recent running of EUnit
157
(defun erlang-eunit-recent (key)
158
(cdr (assq key erlang-eunit-recent-info)))
160
;;; Record info about the most recent running of EUnit
161
;;; Known modes are 'module-mode and 'test-mode
162
(defun erlang-eunit-record-recent (mode module test)
163
(setcdr (assq 'mode erlang-eunit-recent-info) mode)
164
(setcdr (assq 'module erlang-eunit-recent-info) module)
165
(setcdr (assq 'test erlang-eunit-recent-info) test))
167
;;; Record whether the most recent running of EUnit included cover
169
(defun erlang-eunit-record-recent-compile (under-cover)
170
(setcdr (assq 'cover erlang-eunit-recent-info) under-cover))
172
;;; Determine options for EUnit.
173
(defun erlang-eunit-opts ()
174
(if current-prefix-arg ", [verbose]" ""))
176
;;; Determine current test function
177
(defun erlang-eunit-current-test ()
179
(erlang-end-of-function 1)
180
(erlang-beginning-of-function 1)
181
(erlang-name-of-function)))
183
(defun erlang-eunit-simple-test-p (test-name)
184
(if (erlang-eunit-string-match-p "^\\(.+\\)_test$" test-name) t nil))
186
(defun erlang-eunit-test-generator-p (test-name)
187
(if (erlang-eunit-string-match-p "^\\(.+\\)_test_$" test-name) t nil))
189
;;; Run one EUnit test
190
(defun erlang-eunit-run-test (module-name test-name)
192
(cond ((erlang-eunit-simple-test-p test-name)
193
(format "eunit:test({%s, %s}%s)."
194
module-name test-name (erlang-eunit-opts)))
195
((erlang-eunit-test-generator-p test-name)
196
(format "eunit:test({generator, %s, %s}%s)."
197
module-name test-name (erlang-eunit-opts)))
198
(t (format "%% WARNING: '%s' is not a test function" test-name)))))
199
(erlang-eunit-record-recent 'test-mode module-name test-name)
200
(erlang-eunit-inferior-erlang-send-command command)))
128
202
;;; Run EUnit tests for the current module
129
(defun erlang-eunit-run-tests ()
130
"Run the EUnit test suite for the current module.
132
With prefix arg, runs tests with the verbose flag set."
134
(let* ((module-name (erlang-add-quotes-if-needed
135
(erlang-eunit-source-module-name buffer-file-name)))
136
(opts (if current-prefix-arg ", [verbose]" ""))
137
(command (format "eunit:test(%s%s)." module-name opts)))
203
(defun erlang-eunit-run-module-tests (module-name)
204
(let ((command (format "eunit:test(%s%s)." module-name (erlang-eunit-opts))))
205
(erlang-eunit-record-recent 'module-mode module-name nil)
138
206
(erlang-eunit-inferior-erlang-send-command command)))
208
(defun erlang-eunit-compile-and-run-recent ()
209
"Compile the source and test files and repeat the most recent EUnit test run.
211
With prefix arg, compiles for debug and runs tests with the verbose flag set."
213
(case (erlang-eunit-recent 'mode)
215
(erlang-eunit-compile-and-test
216
'erlang-eunit-run-test (list (erlang-eunit-recent 'module)
217
(erlang-eunit-recent 'test))))
219
(erlang-eunit-compile-and-test
220
'erlang-eunit-run-module-tests (list (erlang-eunit-recent 'module))
221
(erlang-eunit-recent 'cover)))
222
(t (error "EUnit has not yet been run. Please run a test first."))))
224
(defun erlang-eunit-cover-compile ()
225
"Cover compile current module."
227
(let* ((erlang-compile-extra-opts
228
(append (list 'debug_info) erlang-compile-extra-opts))
230
(erlang-add-quotes-if-needed
231
(erlang-eunit-module-name buffer-file-name)))
233
(format "cover:compile_beam(%s)." module-name)))
235
(if (erlang-eunit-last-compilation-successful-p)
236
(erlang-eunit-inferior-erlang-send-command compile-command))))
238
(defun erlang-eunit-analyze-coverage ()
239
"Analyze the data collected by cover tool for the module in the
242
Assumes that the module has been cover compiled prior to this
243
call. This function will do two things: print the number of
244
covered and uncovered functions in the erlang shell and display a
245
new buffer called *<module name> coverage* which shows the source
246
code along with the coverage analysis results."
248
(let* ((module-name (erlang-add-quotes-if-needed
249
(erlang-eunit-module-name buffer-file-name)))
250
(tmp-filename (make-temp-file "cover"))
251
(analyze-command (format "cover:analyze_to_file(%s, \"%s\"). "
252
module-name tmp-filename))
253
(buf-name (format "*%s coverage*" module-name)))
254
(erlang-eunit-inferior-erlang-send-command analyze-command)
255
;; The purpose of the following snippet is to get the result of the
256
;; analysis from a file into a new buffer (or an old, if one with
257
;; the specified name already exists). Also we want the erlang-mode
258
;; *and* view-mode to be enabled.
260
(let ((buf (get-buffer-create (format "*%s coverage*" module-name))))
262
(setq buffer-read-only nil)
263
(insert-file-contents tmp-filename nil nil nil t)
264
(if (= (buffer-size) 0)
266
;; FIXME: this would be a good place to enable (emacs-mode)
267
;; to get some nice syntax highlighting in the
268
;; coverage report, but it doesn't play well with
269
;; flymake. Leave it off for now.
271
(delete-file tmp-filename)))
273
(defun erlang-eunit-compile-and-run-current-test ()
274
"Compile the source and test files and run the current EUnit test.
276
With prefix arg, compiles for debug and runs tests with the verbose flag set."
278
(let ((module-name (erlang-add-quotes-if-needed
279
(erlang-eunit-module-name buffer-file-name)))
280
(test-name (erlang-eunit-current-test)))
281
(erlang-eunit-compile-and-test
282
'erlang-eunit-run-test (list module-name test-name))))
284
(defun erlang-eunit-compile-and-run-module-tests ()
285
"Compile the source and test files and run all EUnit tests in the module.
287
With prefix arg, compiles for debug and runs tests with the verbose flag set."
289
(let ((module-name (erlang-add-quotes-if-needed
290
(erlang-eunit-source-module-name buffer-file-name))))
291
(erlang-eunit-compile-and-test
292
'erlang-eunit-run-module-tests (list module-name))))
140
294
;;; Compile source and EUnit test file and finally run EUnit tests for
141
295
;;; the current module
142
(defun erlang-eunit-compile-and-run-tests ()
143
"Compile the source and test files and run the EUnit test suite.
296
(defun erlang-eunit-compile-and-test (test-fun test-args &optional under-cover)
297
"Compile the source and test files and run the EUnit test suite.
299
If under-cover is set to t, the module under test is compile for
300
code coverage analysis. If under-cover is left out or not set,
301
coverage analysis is disabled. The result of the code coverage
302
is both printed to the erlang shell (the number of covered vs
303
uncovered functions in a module) and written to a buffer called
304
*<module> coverage* (which shows the source code for the module
305
and the number of times each line is covered).
145
306
With prefix arg, compiles for debug and runs tests with the verbose flag set."
307
(erlang-eunit-record-recent-compile under-cover)
147
308
(let ((src-filename (erlang-eunit-src-filename buffer-file-name))
148
309
(test-filename (erlang-eunit-test-filename buffer-file-name)))
159
320
;; test file on the other hand, is optional, since eunit tests may
160
321
;; be placed in the source file instead. Any compilation error
161
322
;; will prevent the subsequent steps to be run (hence the `and')
162
(and (erlang-eunit-compile-file src-filename)
323
(and (erlang-eunit-compile-file src-filename under-cover)
163
324
(if (file-readable-p test-filename)
164
325
(erlang-eunit-compile-file test-filename)
166
(erlang-eunit-run-tests)))))
168
(defun erlang-eunit-compile-file (file-path)
327
(apply test-fun test-args)
330
(set-buffer (find-file-noselect src-filename))
331
(erlang-eunit-analyze-coverage)))))))
333
(defun erlang-eunit-compile-and-run-module-tests-under-cover ()
334
"Compile the source and test files and run the EUnit test suite and measure
337
With prefix arg, compiles for debug and runs tests with the verbose flag set."
339
(let ((module-name (erlang-add-quotes-if-needed
340
(erlang-eunit-source-module-name buffer-file-name))))
341
(erlang-eunit-compile-and-test
342
'erlang-eunit-run-module-tests (list module-name) t)))
344
(defun erlang-eunit-compile-file (file-path &optional under-cover)
169
345
(if (file-readable-p file-path)
171
(set-buffer (find-file-noselect file-path))
173
(erlang-eunit-last-compilation-successful-p))
347
(set-buffer (find-file-noselect file-path))
348
;; In order to run a code coverage analysis on a
349
;; module, we have two options:
351
;; * either compile the module with cover:compile instead of the
354
;; * or first compile the module with the regular compiler (but
355
;; *with* debug_info) and then compile it for coverage
356
;; analysis using cover:compile_beam.
358
;; We could accomplish the first by changing the
359
;; erlang-compile-erlang-function to cover:compile, but there's
360
;; a risk that that's used for other purposes. Therefore, a
361
;; safer alternative (although with more steps) is to add
362
;; debug_info to the list of compiler options and go for the
363
;; second alternative.
365
(erlang-eunit-cover-compile)
367
(erlang-eunit-last-compilation-successful-p))
174
368
(let ((msg (format "Could not read %s" file-path)))
175
(erlang-eunit-inferior-erlang-send-command
369
(erlang-eunit-inferior-erlang-send-command
176
370
(format "%% WARNING: %s" msg))
179
373
(defun erlang-eunit-last-compilation-successful-p ()
181
375
(set-buffer inferior-erlang-buffer)
222
416
;;;====================================================================
224
(defvar erlang-eunit-toggle-src-and-test-file-other-window-key "\C-c\C-et"
225
"*Key to which the `erlang-eunit-toggle-src-and-test-file-other-window'
226
function will be bound.")
227
(defvar erlang-eunit-compile-and-run-tests-key "\C-c\C-ek"
228
"*Key to which the `erlang-eunit-compile-and-run-tests'
229
function will be bound.")
418
(defconst erlang-eunit-key-bindings
419
'(("\C-c\C-et" erlang-eunit-toggle-src-and-test-file-other-window)
420
("\C-c\C-ek" erlang-eunit-compile-and-run-module-tests)
421
("\C-c\C-ej" erlang-eunit-compile-and-run-current-test)
422
("\C-c\C-el" erlang-eunit-compile-and-run-recent)
423
("\C-c\C-ec" erlang-eunit-compile-and-run-module-tests-under-cover)
424
("\C-c\C-ev" erlang-eunit-cover-compile)
425
("\C-c\C-ea" erlang-eunit-analyze-coverage)))
231
427
(defun erlang-eunit-add-key-bindings ()
232
(erlang-eunit-ensure-keymap-for-key
233
erlang-eunit-toggle-src-and-test-file-other-window-key)
234
(local-set-key erlang-eunit-toggle-src-and-test-file-other-window-key
235
'erlang-eunit-toggle-src-and-test-file-other-window)
236
(erlang-eunit-ensure-keymap-for-key
237
erlang-eunit-compile-and-run-tests-key)
238
(local-set-key erlang-eunit-compile-and-run-tests-key
239
'erlang-eunit-compile-and-run-tests))
428
(dolist (binding erlang-eunit-key-bindings)
429
(erlang-eunit-bind-key (car binding) (cadr binding))))
431
(defun erlang-eunit-bind-key (key function)
432
(erlang-eunit-ensure-keymap-for-key key)
433
(local-set-key key function))
241
435
(defun erlang-eunit-ensure-keymap-for-key (key-seq)
242
436
(let ((prefix-keys (butlast (append key-seq nil)))