1
;;; rails-test.el --- tests integration with the compile library
2
3
;; Copyright (C) 2006 Dmitry Galinsky <dima dot exe at gmail dot com>
4
5
;; Authors: Dmitry Galinsky <dima dot exe at gmail dot com>,
6
7
;; Keywords: ruby rails languages oop
8
;; $URL: svn+ssh://rubyforge/var/svn/emacs-rails/trunk/rails-ws.el $
9
;; $Id: rails-ws.el 140 2007-03-27 23:33:36Z dimaexe $
10
11
;;; License
12
13
;; This program is free software; you can redistribute it and/or
14
;; modify it under the terms of the GNU General Public License
15
;; as published by the Free Software Foundation; either version 2
16
;; of the License, or (at your option) any later version.
17
18
;; This program is distributed in the hope that it will be useful,
19
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
;; GNU General Public License for more details.
22
23
;; You should have received a copy of the GNU General Public License
24
;; along with this program; if not, write to the Free Software
25
;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26
27
;;; Code:
28
29
30
(defcustom rails-test:quiet 't
31
  "Do not show test progress in minibuffer."
32
  :group 'rails
33
  :type 'boolean
34
  :tag "Rails Quiet Tests")
35
36
(defcustom rails-test:rake-test-all-task-name "test"
37
  "Rake task name to run all tests."
38
  :group 'rails
39
  :type 'string)
40
41
(defvar rails-test:history nil)
42
43
44
(defconst rails-test:result-regexp
45
  "\\([0-9]+ tests, [0-9]+ assertions, \\([0-9]+\\) failures, \\([0-9]+\\) errors\\)")
46
47
(defconst rails-test:progress-regexp
48
  "^[.EF]+$")
49
50
(defvar rails-test:font-lock-keywords
51
  '(("\\([0-9]+ tests, [0-9]+ assertions, 0 failures, 0 errors\\)"
52
     1 compilation-info-face)
53
    ("\\([0-9]+ tests, [0-9]+ assertions, [0-9]+ failures, [0-9]+ errors\\)"
54
     1 compilation-line-face)
55
    ("`\\([a-z0-9_]+\\)'"
56
     1 font-lock-function-name-face t)
57
    ("^\s+\\([0-9]+)\s+\\(Error\\|Failure\\):\\)"
58
     1 compilation-line-face)
59
    ("^\\(test_[a-z0-9_]+\\)(\\([a-zA-Z0-9:]+\\)):?$"
60
     (1 font-lock-function-name-face)
61
     (2 font-lock-type-face))
62
    ("^[.EF]+$" . compilation-info-face)))
63
64
(defun rails-test:line-regexp (&optional append prepend)
65
  (concat
66
   append
67
   "\\[?/?\\(\\(?:app\\|config\\|lib\\|test\\|spec\\|vendor\\)?/[^ \f\n\r\t\v]+?\\):\\([0-9]+\\)\\(?::in\s*`\\(.*?\\)'\\)?\\]?"
68
   prepend))
69
70
(defun rails-test:error-regexp-alist ()
71
  (list
72
   (list 'rails-test-trace
73
         (rails-test:line-regexp) 1 2 nil 1)
74
   (list 'rails-test-error
75
         (rails-test:line-regexp nil "\\(?:\\]:\\|\n$\\)") 1 2 nil 2))) ; ending of "]:" or next line is blank
76
77
(defun rails-test:count-errors-failures ()
78
  "Return the sum of errors and failures for a completed run."
79
  (let ((failures 0)
80
        (errors 0))
81
    (with-current-buffer (get-buffer rails-script:buffer-name)
82
      (save-excursion
83
        (goto-char (point-min))
84
        (while (re-search-forward rails-test:result-regexp (point-max) t)
85
          (setq failures (+ failures (string-to-number (match-string 2))))
86
          (setq errors (+ errors (string-to-number (match-string 3)))))))
87
    (+ errors
88
       failures)))
89
90
(defun rails-test:print-summaries ()
91
  "Print summary lines."
92
  (let ((msg nil))
93
    (with-current-buffer (get-buffer rails-script:buffer-name)
94
      (save-excursion
95
        (goto-char (point-min))
96
        (while (re-search-forward rails-test:result-regexp (point-max) t)
97
          (setq msg (cons (match-string-no-properties 1) msg)))))
98
    (when msg
99
      (message "%s" (strings-join " || " (reverse msg))))))
100
101
(defun rails-test:print-progress (start end len)
102
  (let (content)
103
    (save-excursion
104
      (goto-char (point-min))
105
      (while (re-search-forward "^Started" end t)
106
	(line-move 1)
107
	(save-match-data
108
	  (let ((progress (string=~ rails-test:progress-regexp
109
				    (current-line-string) $m)))
110
	    (when progress
111
	      (setq content (concat content progress))
112
	      (setq rails-ui:num-errors 0
113
		    rails-ui:num-failures 0
114
		    rails-ui:num-ok 0)
115
	      (dolist (c (string-to-list content))
116
		(case c
117
		  (?\E (setq rails-ui:num-errors (+ 1 rails-ui:num-errors)))
118
		  (?\F (setq rails-ui:num-failures (+ 1 rails-ui:num-failures)))
119
		  (?\. (setq rails-ui:num-ok (+ 1 rails-ui:num-ok))))))))))
120
    (when (and content
121
	       (not rails-test:quiet))
122
      (message "Progress of %s: %s" rails-script:running-script-name content))))
123
124
(define-derived-mode rails-test:compilation-mode compilation-mode "RTest"
125
  "Major mode for RoR tests."
126
  (rails-script:setup-output-buffer)
127
  ; replace compilation font-lock-keywords
128
  (set (make-local-variable 'compilation-mode-font-lock-keywords) rails-test:font-lock-keywords)
129
  (set (make-local-variable 'compilation-error-regexp-alist-alist)
130
       (rails-test:error-regexp-alist))
131
  (set (make-local-variable 'compilation-error-regexp-alist)
132
       '(rails-test-error
133
         rails-test-trace))
134
  (add-hook 'after-change-functions 'rails-test:print-progress nil t)
135
  (add-hook 'rails-script:after-hook-internal 'rails-test:run-after-hooks t t)
136
  (add-hook 'rails-script:show-buffer-hook 'rails-test:reset-point-and-height t t))
137
138
(defcustom rails-test:run-after-hook '(rails-test:hide-rails-project-root
139
                                       rails-test:print-summaries)
140
  "Hooks ran after tests have run."
141
  :group 'rails
142
  :type 'hook)
143
144
(defcustom rails-test:run-after-fail-hook '(rails-script:popup-buffer)
145
  "Hooks ran after tests have run and any have failured or an error occured."
146
  :group 'rails
147
  :type 'hook)
148
149
(defcustom rails-test:run-after-pass-hook nil
150
  "Hooks ran after tests have run and any all passed."
151
  :group 'rails
152
  :type 'hook)
153
154
(defun rails-test:run-after-hooks ()
155
  (run-hooks 'rails-test:run-after-hook)
156
  (if (or (not (zerop rails-script:output-mode-ret-value))
157
          (> (rails-test:count-errors-failures) 0))
158
    (run-hooks 'rails-test:run-after-fail-hook)
159
    (run-hooks 'rails-test:run-after-pass-hook)))
160
161
(defun rails-test:hide-rails-project-root ()
162
  "Show files that are relative to the project root as relative filenames
163
As the buffer is read-only this is merely a change in appearance"
164
  (rails-project:with-root (root)
165
    (save-excursion
166
      (goto-char (point-min))
167
      (let ((file-regex (concat (regexp-quote root) "[^:]+")))
168
        (while (re-search-forward file-regex nil t)
169
          (let* ((orig-filename (match-string 0))
170
                 (rel-filename (file-relative-name orig-filename root)))
171
            (overlay-put (make-overlay (match-beginning 0) (match-end 0))
172
                         'display rel-filename)))))))
173
174
(defun rails-test:reset-point-and-height ()
175
  "Resets the point and resizes the window for the output buffer.
176
Used when it's determined that the output buffer needs to be shown."
177
  (let ((win (get-buffer-window (current-buffer))))
178
    (when (window-live-p win)
179
      (set-window-point win 0)
180
      (unless (buffer-visible-p (current-buffer))
181
        (compilation-set-window-height win)))))
182
183
(defun rails-test:list-of-tasks ()
184
  "Return a list contains test tasks."
185
  (append (list "all")
186
          (delete* nil
187
                   (mapcar
188
                    #'(lambda (task) (string=~ "^test\\:\\([^ ]+\\)" task $1))
189
                    (rails-rake:list-of-tasks))
190
                   :if 'null)))
191
192
(defun rails-test:run (task)
193
  "Run rake tests in RAILS_ROOT."
194
  (interactive (rails-completing-read "What test run"
195
                                      (rails-test:list-of-tasks)
196
                                      'rails-test:history t))
197
  (rails-ui:reset-error-count)
198
  (unless task
199
    (setq task "all")
200
    (add-to-list rails-test:history task))
201
  (let ((task-name
202
         (if (string= "all" task)
203
             rails-test:rake-test-all-task-name
204
           (concat "test:" task))))
205
    (rails-rake:task task-name 'rails-test:compilation-mode (concat "test " task))))
206
207
(defvar rails-test:previous-run-single-param nil
208
  "Hold params of previous run-single-file call.")
209
210
(defun rails-test:run-single-file (file &optional param)
211
  "Run test for single file FILE."
212
  (when (not (or file param))
213
    "Refuse to run ruby without an argument: it would never return")
214
  (rails-ui:reset-error-count)
215
  (let ((param (if param
216
                   (list file param)
217
                 (list file)))
218
	(test-name file))
219
    (if (string-match "\\([^/\\\\.]+\\)_test\.rb$" test-name)
220
	(setq test-name (concat "test " (match-string-no-properties 1 test-name))))
221
    (rails-script:run "ruby" (append (list "-Itest") param) 'rails-test:compilation-mode test-name)
222
    (setq rails-test:previous-run-single-param param)))
223
224
(defun rails-test:rerun-single ()
225
  "Rerun previous single file test."
226
  (interactive)
227
  (if rails-test:previous-run-single-param
228
    (apply 'rails-test:run-single-file rails-test:previous-run-single-param)
229
    (message "No previous single file test recorded.")))
230
231
(defun rails-test:run-current ()
232
  "Run a test for the current controller/model/mailer."
233
  (interactive)
234
  (let* ((model (rails-core:current-model))
235
         (controller (rails-core:current-controller))
236
         (mailer (rails-core:current-mailer))
237
         (func-test (rails-core:functional-test-file controller))
238
         (unit-test (rails-core:unit-test-file model))
239
         (mailer-test (rails-core:unit-test-file mailer)))
240
    (rails-test:run-single-file
241
     (cond
242
      ;; model
243
      ((and model unit-test) unit-test)
244
      ;; controller
245
      ((and controller (not (rails-core:mailer-p controller)) func-test)
246
       func-test)
247
      ;; mailer
248
      ((and mailer mailer-test) mailer-test)
249
      ;; otherwise...
250
      (t (if (string-match "test.*\\.rb" (buffer-file-name))
251
             (buffer-file-name)
252
           (error "Cannot determine which test file to run.")))))))
253
254
(defun rails-test:active-support-test-case-current-test ()
255
  (save-excursion
256
    (ruby-end-of-block)
257
    (and (search-backward-regexp "^[ \t]+test[ \t]+\\([\'\"]\\)\\(.*?\\)\\1[ \t]+do" nil t)
258
         (match-string-no-properties 2))))
259
260
(defun rails-test:run-current-method ()
261
  "Run a test for the current method."
262
  (interactive)
263
  (let ((file (substring (buffer-file-name) (length (rails-project:root))))
264
        (method (rails-core:current-method-name))
265
        (description (or (rails-shoulda:current-test) (rails-test:active-support-test-case-current-test))))
266
    (cond (description
267
           (rails-test:run-single-file file
268
                                       (format "--name=/%s/"
269
                                               (replace-regexp-in-string "^\\.\\|\\.$" ""
270
                                                                         (replace-regexp-in-string "[^a-z0-9,-]+" "."
271
                                                                                                   description)))))
272
          (method
273
           (rails-test:run-single-file file
274
                                       (format "--name=%s" method))))))
275
276
;; These functions were originally defined anonymously in ui. They are defined here so keys
277
;; can be added to them dryly
278
(defun rails-test:run-integration ()
279
  "Run Integration Tests."
280
  (interactive)
281
  (rails-test:run "integration"))
282
(defun rails-test:run-units ()
283
  "Run Unit Tests."
284
  (interactive)
285
  (rails-test:run "units"))
286
(defun rails-test:run-functionals ()
287
  "Run Functional Tests."
288
  (interactive)
289
  (rails-test:run "functionals"))
290
(defun rails-test:run-recent ()
291
  "Run Recent Tests."
292
  (interactive)
293
  (rails-test:run "recent"))
294
(defun rails-test:run-all ()
295
  "Run All Tests."
296
  (interactive)
297
  (rails-test:run "all"))
298
299
(provide 'rails-test)