├── .gitignore ├── README.md └── kaocha-runner.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | .rvmrc 3 | /TAGS 4 | elpa 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kaocha-runner.el 2 | 3 | An emacs package for running Kaocha tests via CIDER. 4 | 5 | **Breaking:** Commands in this package are now prefixed with `kaocha-runner-` 6 | instead of just `kaocha-`. Update your keybindings accordingly. 7 | 8 | ## Installation 9 | 10 | I highly recommend installing kaocha-runner through elpa. 11 | 12 | It is available on [melpa](http://melpa.milkbox.net/): 13 | 14 | M-x package-install kaocha-runner 15 | 16 | or 17 | 18 | (use-package kaocha-runner 19 | :after (cider-mode)) 20 | 21 | Note that you must include kaocha in your dev dependencies for this to run in 22 | the repl. The kaocha docs suggests creating a separate `:kaocha` profile, but 23 | skip that if you want to run it from the repl. 24 | 25 | ## Usage 26 | 27 | Kaocha runner exposes the following commands: 28 | 29 | - `kaocha-runner-run-test-at-point` 30 | 31 | Runs the test at point in the current namespace. 32 | 33 | - `kaocha-runner-run-tests` 34 | 35 | Runs tests in the current namespace. With a prefix argument, it runs test id provided by the user. 36 | 37 | - `kaocha-runner-run-all-tests` 38 | 39 | Runs all tests in the project. 40 | 41 | - `kaocha-runner-show-warnings` 42 | 43 | If you get any warnings from Kaocha, the report will just say *X warnings*. 44 | You can display them with this command. Given a prefix argument, it 45 | displays the warnings in a separate window. 46 | 47 | - `kaocha-runner-hide-windows` 48 | 49 | When displaying results, kaocha-runner pops up a window. This command hides 50 | it again. You can also switch to the window and kill it normally, if you 51 | prefer that. 52 | 53 | ## Keybindings 54 | 55 | Pick your own. Here are mine: 56 | 57 | ```cl 58 | (define-key clojure-mode-map (kbd "C-c k t") 'kaocha-runner-run-test-at-point) 59 | (define-key clojure-mode-map (kbd "C-c k r") 'kaocha-runner-run-tests) 60 | (define-key clojure-mode-map (kbd "C-c k a") 'kaocha-runner-run-all-tests) 61 | (define-key clojure-mode-map (kbd "C-c k w") 'kaocha-runner-show-warnings) 62 | (define-key clojure-mode-map (kbd "C-c k h") 'kaocha-runner-hide-windows) 63 | ``` 64 | 65 | With `use-package`, something like: 66 | 67 | ```cl 68 | (use-package kaocha-runner 69 | :after (cider-mode) 70 | :bind (:map clojure-mode-map 71 | ("C-c k t" . kaocha-runner-run-test-at-point) 72 | ("C-c k r" . kaocha-runner-run-tests) 73 | ("C-c k a" . kaocha-runner-run-all-tests) 74 | ("C-c k w" . kaocha-runner-show-warnings) 75 | ("C-c k h" . kaocha-runner-hide-windows))) 76 | ``` 77 | 78 | ## Configuration 79 | 80 | The way kaocha-runner invokes kaocha is guided by two custom vars: 81 | 82 | - `kaocha-runner-repl-invocation-template` 83 | 84 | which defaults to `(do (require 'kaocha.repl) %s)`. The `%s` is replaced 85 | with either `(kaocha.repl/run ...)` or `(kaocha.repl/run-all ...)` 86 | 87 | - `kaocha-runner-extra-configuration` 88 | 89 | which defaults to `{:kaocha/fail-fast? true}`. 90 | 91 | So by default, kaocha-runner does this when running tests: 92 | 93 | ```clj 94 | (do (require 'kaocha.repl) (kaocha.repl/run {:kaocha/fail-fast? true})) 95 | ``` 96 | 97 | You can change this by changing the custom vars mentioned above. 98 | 99 | Note that kaocha-runner *does not evaluate your code in any way*. You'll have to 100 | evaluate the code first, with a `(reset)` or just `C-c C-k` in the buffer. 101 | 102 | To remedy this, you can change the `kaocha-runner-repl-invocation-template` to 103 | include a reset of your choice. 104 | 105 | Also, if you want to shave ~150ms from each test run, you can remove the require 106 | from the template. In that case, you'll have to require it yourself. 107 | 108 | ### Visual customisation 109 | 110 | If the default red/green/yellow doesn't work for you, the faces are customisable: 111 | 112 | - `kaocha-runner-success-face` 113 | 114 | Face used to highlight success messages. 115 | 116 | - `kaocha-runner-error-face` 117 | 118 | Face used to highlight error messages. 119 | 120 | - `kaocha-runner-warning-face` 121 | 122 | Face used to highlight warning messages. 123 | 124 | You can also customise the tests, failure, and output window heights by setting 125 | the following custom vars to the desired value: 126 | 127 | - `kaocha-runner-ongoing-tests-win-min-height` 128 | 129 | The minimum height in lines of the output window when tests are taking long 130 | to run. This is to show the ongoing progress from kaocha. 131 | 132 | - `kaocha-runner-failure-win-min-height` 133 | 134 | The minimum height in lines of the output window when there are failing tests. 135 | 136 | - `kaocha-runner-output-win-max-height` 137 | 138 | The maximum height in lines of the output window. 139 | 140 | ## License 141 | 142 | Copyright (C) 2019 Magnar Sveen 143 | 144 | Author: Magnar Sveen 145 | 146 | This program is free software; you can redistribute it and/or modify 147 | it under the terms of the GNU General Public License as published by 148 | the Free Software Foundation, either version 3 of the License, or 149 | (at your option) any later version. 150 | 151 | This program is distributed in the hope that it will be useful, 152 | but WITHOUT ANY WARRANTY; without even the implied warranty of 153 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 154 | GNU General Public License for more details. 155 | 156 | You should have received a copy of the GNU General Public License 157 | along with this program. If not, see . 158 | -------------------------------------------------------------------------------- /kaocha-runner.el: -------------------------------------------------------------------------------- 1 | ;;; kaocha-runner.el --- A package for running Kaocha tests via CIDER. -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2019 Magnar Sveen 4 | 5 | ;; Author: Magnar Sveen 6 | ;; Version: 0.3.2 7 | ;; Package-Requires: ((emacs "26") (s "1.4.0") (cider "0.21.0") (parseedn "0.1.0")) 8 | ;; URL: https://github.com/magnars/kaocha-runner.el 9 | 10 | ;; This program is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation, either version 3 of the License, or 13 | ;; (at your option) any later version. 14 | 15 | ;; This program is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | ;;; Commentary: 24 | 25 | ;; A minor-mode for running Kaocha tests with CIDER 26 | 27 | ;;; Code: 28 | 29 | (require 'cider) 30 | (require 'parseedn) 31 | (require 's) 32 | 33 | (defgroup kaocha-runner nil 34 | "Run Kaocha tests via CIDER." 35 | :group 'tools) 36 | 37 | (defcustom kaocha-runner-repl-invocation-template 38 | "(do (require 'kaocha.repl) %s)" 39 | "The invocation sent to the REPL to run kaocha tests, with the actual run replaced by %s." 40 | :group 'kaocha-runner 41 | :type 'string) 42 | 43 | (defcustom kaocha-runner-extra-configuration 44 | "{:kaocha/fail-fast? true}" 45 | "Extra configuration options passed to kaocha, a string containing an edn map." 46 | :group 'kaocha-runner 47 | :type 'string) 48 | 49 | (defcustom kaocha-runner-long-running-seconds 50 | 3 51 | "After a test run has taken this many seconds, pop up the output window to see what is going on." 52 | :group 'kaocha-runner 53 | :type 'integer 54 | :package-version '(kaocha-runner . "0.3.0")) 55 | 56 | (defcustom kaocha-runner-too-long-running-seconds 57 | 180 ;; 3 minutes 58 | "After a test run has taken this many seconds, stop listening for events. This is to reduce the damage of a failure mode where CIDER keeps calling us back indefinitely." 59 | :group 'kaocha-runner 60 | :type 'integer 61 | :package-version '(kaocha-runner . "0.3.1")) 62 | 63 | (defcustom kaocha-runner-ongoing-tests-win-min-height 64 | 12 65 | "The minimum height in lines of the output window when tests are taking long to run. 66 | This is to show the ongoing progress from kaocha." 67 | :group 'kaocha-runner 68 | :type 'integer 69 | :package-version '(kaocha-runner . "0.2.0")) 70 | 71 | (defcustom kaocha-runner-failure-win-min-height 72 | 4 73 | "The minimum height in lines of the output window when there are failing tests." 74 | :group 'kaocha-runner 75 | :type 'integer 76 | :package-version '(kaocha-runner . "0.2.0")) 77 | 78 | (defcustom kaocha-runner-output-win-max-height 79 | 16 80 | "The maximum height in lines of the output window." 81 | :group 'kaocha-runner 82 | :type 'integer 83 | :package-version '(kaocha-runner . "0.2.0")) 84 | 85 | (defface kaocha-runner-success-face 86 | '((t (:foreground "green"))) 87 | "Face used to highlight success messages." 88 | :group 'kaocha-runner) 89 | 90 | (defface kaocha-runner-error-face 91 | '((t (:foreground "red"))) 92 | "Face used to highlight error messages." 93 | :group 'kaocha-runner) 94 | 95 | (defface kaocha-runner-warning-face 96 | '((t (:foreground "yellow"))) 97 | "Face used to highlight warning messages." 98 | :group 'kaocha-runner) 99 | 100 | (defun kaocha-runner--eval-clojure-code (code callback) 101 | "Send CODE to be evaled and run to CIDER, calling CALLBACK with updates." 102 | (cider-nrepl-request:eval 103 | code 104 | callback 105 | nil nil nil nil 106 | (cider-current-repl 'clj 'ensure))) 107 | 108 | (defvar kaocha-runner--out-buffer "*kaocha-output*") 109 | (defvar kaocha-runner--err-buffer "*kaocha-error*") 110 | 111 | (defun kaocha-runner--clear-buffer (buffer) 112 | "Ensure that BUFFER exists and is empty." 113 | (get-buffer-create buffer) 114 | (with-current-buffer buffer 115 | (delete-region (point-min) (point-max)))) 116 | 117 | (defun kaocha-runner--colorize () 118 | "Turn ANSI codes in the current buffer into Emacs colors." 119 | (save-excursion 120 | (goto-char (point-min)) 121 | (insert "") 122 | (ansi-color-apply-on-region (point-min) (point-max)))) 123 | 124 | (defun kaocha-runner--insert (buffer s) 125 | "Insert S into BUFFER, then turn ANSI codes into color." 126 | (with-current-buffer buffer 127 | (insert s) 128 | (kaocha-runner--colorize))) 129 | 130 | (defmacro kaocha-runner--with-window (buffer original-buffer &rest body) 131 | "Open a dedicated window showing BUFFER, perform BODY, then switch back to ORIGINAL-BUFFER." 132 | (declare (debug (form body)) 133 | (indent 2)) 134 | `(let ((window (get-buffer-window ,buffer))) 135 | (if window 136 | (select-window window) 137 | (let ((window (split-window-vertically -4))) 138 | (select-window window) 139 | (switch-to-buffer ,buffer) 140 | (set-window-dedicated-p window t))) 141 | ,@body 142 | (switch-to-buffer-other-window ,original-buffer))) 143 | 144 | (defun kaocha-runner--fit-window-snuggly (min-height max-height) 145 | "Resize current window to fit its contents, within MIN-HEIGHT and MAX-HEIGHT." 146 | (window-resize nil (- (max min-height 147 | (min max-height 148 | (- (line-number-at-pos (point-max)) 149 | (line-number-at-pos (point))))) 150 | (window-height)))) 151 | 152 | (defun kaocha-runner--recenter-top () 153 | "Change the scroll position so that the cursor is at the top of the window." 154 | (recenter (min (max 0 scroll-margin) 155 | (truncate (/ (window-body-height) 4.0))))) 156 | 157 | (defun kaocha-runner--num-warnings () 158 | "Count the number of warnings in the error buffer." 159 | (s-count-matches "WARNING:" 160 | (with-current-buffer kaocha-runner--err-buffer 161 | (buffer-substring-no-properties (point-min) (point-max))))) 162 | 163 | (defun kaocha-runner--show-report (value testable-sym) 164 | "Show a message detailing the test run restult in VALUE, prefixed by TESTABLE-SYM" 165 | (unless (s-equals? value "0") ;; no result 166 | (when-let* ((result (parseedn-read-str 167 | (s-with value 168 | (s-chop-prefix "#:kaocha.result" ) 169 | (s-replace ":kaocha.result/" ":"))))) 170 | (let* ((tests (gethash :count result)) 171 | (pass (gethash :pass result)) 172 | (fail (gethash :fail result)) 173 | (err (gethash :error result)) 174 | (warnings (kaocha-runner--num-warnings)) 175 | (happy? (and (= 0 fail) (= 0 err))) 176 | (report (format "%s%s" 177 | (if testable-sym 178 | (concat "[" testable-sym "] ") 179 | "") 180 | (propertize (format "%s tests, %s assertions%s, %s failures." 181 | tests 182 | (+ pass fail err) 183 | (if (< 0 err) 184 | (format ", %s errors" err) 185 | "") 186 | fail) 187 | 'face (if happy? 188 | 'kaocha-runner-success-face 189 | 'kaocha-runner-error-face))))) 190 | (when (< 0 warnings) 191 | (let ((warnings-str (format "(%s warnings)" warnings))) 192 | (setq report (concat report (s-repeat (max 3 (- (frame-width) (length report) (length warnings-str))) " ") 193 | (propertize warnings-str 'face 'kaocha-runner-warning-face))))) 194 | (message "%s" report))))) 195 | 196 | (defvar kaocha-runner--fail-re "\\(FAIL\\|ERROR\\)") 197 | 198 | (defun kaocha-runner--show-details-window (original-buffer min-height) 199 | "Show details from the test run with a MIN-HEIGHT, but switch back to ORIGINAL-BUFFER afterwards." 200 | (kaocha-runner--with-window kaocha-runner--out-buffer original-buffer 201 | (visual-line-mode 1) 202 | (goto-char (point-min)) 203 | (let ((case-fold-search nil)) 204 | (re-search-forward kaocha-runner--fail-re nil t)) 205 | (end-of-line) 206 | (kaocha-runner--fit-window-snuggly min-height kaocha-runner-output-win-max-height) 207 | (kaocha-runner--recenter-top))) 208 | 209 | (defun kaocha-runner--testable-sym (ns test-name cljs?) 210 | (concat "'" 211 | (if cljs? "cljs:" "") 212 | ns 213 | (when test-name (concat "/" test-name)))) 214 | 215 | (defun kaocha-runner--hide-window (buffer-name) 216 | (when-let (buffer (get-buffer buffer-name)) 217 | (when-let (window (get-buffer-window buffer)) 218 | (delete-window window)))) 219 | 220 | (defvar kaocha-runner--current-run-index 0) 221 | 222 | (defun kaocha-runner--run-tests (testable-sym &optional run-all? background? original-buffer) 223 | "Run kaocha tests. 224 | 225 | If RUN-ALL? is t, all tests are run, otherwise attempt a run with the provided 226 | TESTABLEY-SYM. In practice TESTABLEY-SYM can be a test id, an ns or an ns/test-fn. 227 | 228 | If BACKGROUND? is t, we don't message when the tests start running. 229 | 230 | Given an ORIGINAL-BUFFER, use that instead of (current-buffer) when switching back." 231 | (interactive) 232 | (kaocha-runner--clear-buffer kaocha-runner--out-buffer) 233 | (kaocha-runner--clear-buffer kaocha-runner--err-buffer) 234 | (kaocha-runner--eval-clojure-code 235 | (format kaocha-runner-repl-invocation-template 236 | (if run-all? 237 | (format "(kaocha.repl/run-all %s)" kaocha-runner-extra-configuration) 238 | (format 239 | "(kaocha.repl/run %s %s)" 240 | testable-sym 241 | kaocha-runner-extra-configuration))) 242 | (let ((my-run-index (setq kaocha-runner--current-run-index 243 | (1+ kaocha-runner--current-run-index))) 244 | (original-buffer (or original-buffer (current-buffer))) 245 | (done? nil) 246 | (any-errors? nil) 247 | (shown-details? nil) 248 | (the-value nil) 249 | (start-time (float-time))) 250 | (unless background? 251 | (if run-all? 252 | (message "Running all tests ...") 253 | (message "[%s] Running tests ..." testable-sym))) 254 | (lambda (response) 255 | (nrepl-dbind-response response (value out err status) 256 | (when (not done?) 257 | (when (< kaocha-runner-too-long-running-seconds 258 | (- (float-time) start-time)) 259 | (message "Kaocha run timed out after %s seconds." kaocha-runner-too-long-running-seconds) 260 | (setq done? t)) 261 | (when (= kaocha-runner--current-run-index 262 | my-run-index) 263 | (when out 264 | (kaocha-runner--insert kaocha-runner--out-buffer out) 265 | (when (let ((case-fold-search nil)) 266 | (string-match-p kaocha-runner--fail-re out)) 267 | (setq any-errors? t)) 268 | (when (and (< kaocha-runner-long-running-seconds 269 | (- (float-time) start-time)) 270 | (not shown-details?)) 271 | (setq shown-details? t) 272 | (kaocha-runner--show-details-window original-buffer kaocha-runner-ongoing-tests-win-min-height))) 273 | (when err 274 | (kaocha-runner--insert kaocha-runner--err-buffer err)) 275 | (when value 276 | (setq the-value value)) 277 | (when (and status (member "done" status)) 278 | (setq done? t)) 279 | (when done? 280 | (if the-value 281 | (kaocha-runner--show-report the-value (unless run-all? testable-sym)) 282 | (unless (get-buffer-window kaocha-runner--err-buffer 'visible) 283 | (message "Kaocha run failed. See error window for details.") 284 | (switch-to-buffer-other-window kaocha-runner--err-buffer)))) 285 | (when done? 286 | (if any-errors? 287 | (kaocha-runner--show-details-window original-buffer kaocha-runner-failure-win-min-height) 288 | (kaocha-runner--hide-window kaocha-runner--out-buffer)))))))))) 289 | 290 | ;;;###autoload 291 | (defun kaocha-runner-hide-windows () 292 | "Hide all windows that kaocha has opened." 293 | (interactive) 294 | (kaocha-runner--hide-window kaocha-runner--out-buffer) 295 | (kaocha-runner--hide-window kaocha-runner--err-buffer)) 296 | 297 | ;;;###autoload 298 | (defun kaocha-runner-run-tests (&optional test-id?) 299 | "Run tests in the current namespace. 300 | If prefix argument TEST-ID? is present ask user for a test-id to run." 301 | (interactive "P") 302 | (kaocha-runner-hide-windows) 303 | (let ((test-id (when test-id? (read-from-minibuffer "test id: ")))) 304 | (kaocha-runner--run-tests 305 | (if test-id 306 | test-id 307 | (kaocha-runner--testable-sym (cider-current-ns) nil (eq major-mode 'clojurescript-mode)))))) 308 | 309 | ;;;###autoload 310 | (defun kaocha-runner-run-test-at-point () 311 | "Run the test at point in the current namespace." 312 | (interactive) 313 | (kaocha-runner-hide-windows) 314 | (kaocha-runner--run-tests 315 | (kaocha-runner--testable-sym (cider-current-ns) (cadr (clojure-find-def)) (eq major-mode 'clojurescript-mode)))) 316 | 317 | ;;;###autoload 318 | (defun kaocha-runner-run-all-tests () 319 | "Run all tests." 320 | (interactive) 321 | (kaocha-runner-hide-windows) 322 | (kaocha-runner--run-tests nil t)) 323 | 324 | ;;;###autoload 325 | (defun kaocha-runner-show-warnings (&optional switch-to-buffer?) 326 | "Display warnings from the last kaocha test run. 327 | Prefix argument SWITCH-TO-BUFFER? opens a separate window." 328 | (interactive "P") 329 | (if switch-to-buffer? 330 | (switch-to-buffer-other-window kaocha-runner--err-buffer) 331 | (message "%s" 332 | (s-trim 333 | (with-current-buffer kaocha-runner--err-buffer 334 | (buffer-substring (point-min) (point-max))))))) 335 | 336 | (provide 'kaocha-runner) 337 | ;;; kaocha-runner.el ends here 338 | --------------------------------------------------------------------------------