-
Notifications
You must be signed in to change notification settings - Fork 26
/
minitest.el
355 lines (295 loc) · 12.4 KB
/
minitest.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
;;; minitest.el --- An Emacs mode for ruby minitest files
;; Copyright © 2013-2015 Arthur Nogueira Neves
;; Author: Arthur Neves
;; URL: https://github.com/arthurnn/minitest-emacs
;; Version: 0.10.0
;; Package-Requires: ((dash "1.0.0"))
;; This file is NOT part of GNU Emacs.
;;; Change Log:
;; 0.10.0 - No longer explicitly set compilation-scroll-output
;;; Code:
(require 'dash)
(require 'ansi-color)
(defcustom minitest-keymap-prefix (kbd "C-c ,")
"Minitest keymap prefix."
:group 'minitest
:type 'string)
(defcustom minitest-use-zeus-when-possible t
"When t and .zeus.sock is present, run tests with 'zeus'."
:type 'boolean
:group 'minitest)
(defcustom minitest-use-bundler t
"minitest mode should use bundler?"
:type 'boolean
:group 'minitest)
(defcustom minitest-use-spring nil
"Use spring as the default runner"
:type 'boolean
:group 'minitest)
(defcustom minitest-use-rails nil
"Use `bin/rails test' as the default runner.
This is intended for use with Rails versions 5+."
:type 'boolean
:group 'minitest)
(defcustom minitest-use-docker nil
"Execute test command inside `minitest-docker-container' with `minitest-docker-command'"
:type 'boolean
:group 'minitest)
(defcustom minitest-docker-command '("docker-compose" "exec")
"Command to execute tests with docker"
:type 'list
:group 'minitest)
(defcustom minitest-docker-container nil
"Specify the name of the docker container to target"
:type 'string
:group 'minitest)
(defcustom minitest-default-env nil
"Default env vars for minitest"
:type 'string
:group 'minitest)
(defcustom minitest-default-command '("ruby" "-Ilib:test:spec")
"Default command for minitest"
:type 'list
:group 'minitest)
(defcustom minitest-spring-command '("spring" "rake" "test")
"Spring command for minitest"
:type 'list
:group 'minitest)
(defcustom minitest-test-directory-name "test"
"The folder name within `minitest-project-root' that holds the tests"
:type 'string
:group 'minitest)
(defcustom minitest-source-directory-names '("app" "lib")
"The folder names within `minitest-project-root' that hold the source code"
:type 'list
:group 'minitest)
(defun minitest-buffer-name (file-or-dir)
(concat "*Minitest " file-or-dir "*"))
(defun minitest-test-command ()
(let ((command (cond (minitest-use-spring minitest-spring-command)
((minitest-zeus-p) '("zeus" "test"))
(minitest-use-rails '("bin/rails" "test"))
(t minitest-default-command))))
(if minitest-use-docker (append minitest-docker-command (list minitest-docker-container) command)
command)))
(defun minitest-bundler-command ()
(cond (minitest-use-bundler '("bundle" "exec"))
(t nil)))
(defun minitest-project-root ()
"Retrieve the root directory of a project if available.
The current directory is assumed to be the project's root otherwise."
(or (->> '("Rakefile" "Gemfile")
(--map (locate-dominating-file default-directory it))
(-remove #'null)
(car))
(error "You're not into a project")))
(defun minitest-zeus-p ()
(and minitest-use-zeus-when-possible
(file-exists-p (concat (minitest-project-root) ".zeus.sock"))))
(define-derived-mode minitest-compilation-mode compilation-mode ""
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer))
(defun colorize-compilation-buffer ()
(read-only-mode 1)
(ansi-color-apply-on-region (point-min) (point-max))
(read-only-mode -1))
(defvar minitest--last-command nil
"Variable to store the last command running.")
(defun minitest--run-command (command &optional file-name)
(if (fboundp 'rvm-activate-corresponding-ruby)
(rvm-activate-corresponding-ruby))
(let ((default-directory (minitest-project-root))
(actual-command (concat (or minitest-default-env "") " " command)))
(setq minitest--last-command (list command file-name))
(compilation-start
actual-command
'minitest-compilation-mode
(lambda (arg) (minitest-buffer-name (or file-name ""))))))
(defun minitest--file-command (&optional post-command)
"Run COMMAND on currently visited file."
(let ((file-name (file-relative-name (buffer-file-name) (minitest-project-root))))
(if file-name
(minitest-run-file file-name post-command)
(error "Buffer is not visiting a file"))))
(defun minitest--test-name-flag (test-name)
(let ((flag (format "-n/%s/" test-name)))
(cond (minitest-use-spring (concat "TESTOPTS=" flag))
(t flag))))
(defvar minitest--test-regexps
'("\\(test\\) ['\"]\\([^\"]+?\\)['\"]"
"def \\(test\\)_\\([_A-Za-z0-9]+\\)"
"\\(it\\) \"\\([^\"]+?\\)\""
"\\(it\\) '\\([^\"]+?\\)'")
"List of regular expressions for minitest test definition patterns.")
(defun minitest--match-point (re)
"Searches for a regular expression backwards from end of the current line.
Sets the match-string and returns the location of point where the match begins or nil."
(save-excursion
(save-restriction
(widen)
(end-of-line)
(re-search-backward re nil t))))
(defun minitest--extract-test ()
"Finds the nearest test name matching one of the `minitest--test-regexps'.
Returns a (CMD . NAME) pair or nil."
(let* ((matches (delete nil (mapcar 'minitest--match-point minitest--test-regexps)))
(distances (mapcar (lambda (pos) (- (point) pos)) matches)))
(if distances
(let ((closest (cl-position (apply 'min distances) distances)))
(minitest--match-point (nth closest minitest--test-regexps))
`(,(match-string 1) . ,(match-string 2))))))
(defun minitest--verify-single-with-regex ()
(let ((test (minitest--extract-test)))
(if test
(minitest--file-command (minitest--test-name-flag (minitest--post-command test)))
(error "No test found. Make sure you are on a file that has `def test_foo` or `test \"foo\"`"))))
(defun minitest--verify-single-rails ()
"Runs `bin/rails test path/to/test_file.rb:NN' with the current line number."
(let ((line-number (line-number-at-pos (point)))
(file-name (file-relative-name (buffer-file-name) (minitest-project-root))))
(minitest-run-file (format "%s:%s" file-name line-number))))
(defun minitest-verify-all ()
"Run all tests."
(interactive)
(minitest--run-command
(mapconcat 'shell-quote-argument
(-flatten
(--remove (eq nil it)
(list (minitest-bundler-command) "rake"))) " ")))
(defun minitest-verify ()
"Run on current file."
(interactive)
(minitest--file-command))
(defun minitest-verify-single ()
"Run the test closest to the cursor, searching backwards."
(interactive)
(if minitest-use-rails (minitest--verify-single-rails)
(minitest--verify-single-with-regex)))
(defun minitest--post-command (test)
(let ((name (cdr test)))
(if (string= (car test) "it")
name
(format "%s" (replace-regexp-in-string "[\s#:]" "_" name)))))
(defun minitest-rerun ()
"Run the last command"
(interactive)
(if minitest--last-command
(apply #'minitest--run-command minitest--last-command)
(error "There is no previous command to run")))
(defun minitest-run-file (file-name &optional post-command)
"Run the given file"
(let ((bundle (minitest-bundler-command))
(command (minitest-test-command)))
(minitest--run-command
(mapconcat 'shell-quote-argument
(-flatten
(--remove (eq nil it)
(list bundle command file-name post-command))) " ")
file-name)))
;;; Minor mode
(defvar minitest-mode-map
(let ((map (make-sparse-keymap)))
(let ((prefix-map (make-sparse-keymap)))
(define-key prefix-map (kbd "a") 'minitest-verify-all)
(define-key prefix-map (kbd "v") 'minitest-verify)
(define-key prefix-map (kbd "s") 'minitest-verify-single)
(define-key prefix-map (kbd "r") 'minitest-rerun)
(define-key prefix-map (kbd "t") 'minitest-toggle-test-and-target)
(define-key map minitest-keymap-prefix prefix-map))
map)
"Keymap for minitest-mode.")
;;;###autoload
(define-minor-mode minitest-mode
"Minor mode for *_test (minitest) files"
:lighter " Minitest"
:keymap minitest-mode-map
:group 'minitest
(if minitest-mode
(progn
(when (boundp 'yas-extra-modes)
(if (fboundp 'yas-activate-extra-mode)
;; Yasnippet 0.8.1+
(yas-activate-extra-mode 'minitest-mode)
(make-local-variable 'yas-extra-modes)
(add-to-list 'yas-extra-modes 'minitest-mode)
(yas--load-pending-jits)))))
)
(defvar minitest-snippets-dir
(let ((current-file-name (or load-file-name (buffer-file-name))))
(expand-file-name "snippets" (file-name-directory current-file-name)))
"The directory containing minitest snippets.")
(defun minitest-install-snippets ()
"Add `minitest-snippets-dir' to `yas-snippet-dirs' and load\
snippets from it."
(let ((yasnippet-available (require 'yasnippet nil t)))
(if yasnippet-available
(progn
(add-to-list 'yas-snippet-dirs minitest-snippets-dir t)
(yas-load-directory minitest-snippets-dir)))))
(defconst minitest-test-file-name-re "\\(_\\|-\\)test\\.rb\\'"
"The regex to identify test files.")
(defun minitest-test-file-p (file-name)
"Returns true if the specified file name is a test."
(numberp (string-match minitest-test-file-name-re file-name)))
(defun minitest-buffer-is-test-p ()
"Return true if the current buffer is a test."
(and (buffer-file-name)
(minitest-test-file-p (buffer-file-name))))
;;;###autoload
(defun minitest-toggle-test-and-target ()
"Switch to the test or the target file for the current buffer.
If the current buffer is visiting a test file, switches to the
target, otherwise the test."
(interactive)
(find-file (minitest--test-or-target)))
(defun minitest--test-or-target ()
(if (minitest-buffer-is-test-p)
(minitest--target-file-for (buffer-file-name))
(minitest--test-file-for (buffer-file-name))))
(defun minitest--test-file-for (a-file-name)
"Find test for the specified file."
(if (minitest-test-file-p a-file-name)
a-file-name
(let* ((replace-regex "^\\.\\./[^/]+/")
(test-directory (concat (minitest-project-root) minitest-test-directory-name))
(relative-file-name (file-relative-name a-file-name test-directory)))
(minitest--testize-file-name (expand-file-name (replace-regexp-in-string replace-regex "" relative-file-name)
test-directory)))))
(defun minitest--target-file-for (a-test-file-name)
"Find the target for A-TEST-FILE-NAME."
(cl-loop for extension in (list "rb" "rake")
for candidate = (minitest--targetize-file-name a-test-file-name
extension)
for filename = (cl-loop for dir in (cons "." minitest-source-directory-names)
for target = (replace-regexp-in-string
(concat "/" minitest-test-directory-name "/")
(concat "/" dir "/")
candidate)
if (file-exists-p target)
return target)
if filename
return filename))
(defun minitest--testize-file-name (a-file-name)
"Return A-FILE-NAME but converted in to a test file name."
(concat
(file-name-directory a-file-name)
(replace-regexp-in-string "\\(\\.\\(rb\\|rake\\)\\)?$" "_test.rb" (file-name-nondirectory a-file-name))))
(defun minitest--targetize-file-name (a-file-name extension)
"Return A-FILE-NAME but converted into a non-test file name with EXTENSION."
(concat (file-name-directory a-file-name)
(minitest--file-name-with-default-extension
(replace-regexp-in-string "_test\\.rb" (concat "." extension)
(file-name-nondirectory a-file-name)))))
(defun minitest--file-name-with-default-extension (a-file-name)
"Add .rb file extension to A-FILE-NAME if it does not already have an extension."
(if (file-name-extension a-file-name)
a-file-name ;; file has a extension already so do nothing
(concat a-file-name ".rb")))
;;;###autoload
(defun minitest-enable-appropriate-mode ()
(if (minitest-buffer-is-test-p)
(minitest-mode)))
;;;###autoload
(dolist (hook '(ruby-mode-hook enh-ruby-mode-hook))
(add-hook hook 'minitest-enable-appropriate-mode))
(provide 'minitest)
;;; minitest.el ends here