├── .gitignore ├── .travis.yml ├── Cask ├── README.md ├── UNLICENSE.txt ├── bpr.el ├── img ├── error-run.gif └── success-run.gif └── test-bpr.el /.gitignore: -------------------------------------------------------------------------------- 1 | .cask/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | env: 3 | - EVM_EMACS=emacs-25.3-travis 4 | before_install: 5 | - sudo mkdir /usr/local/evm 6 | - sudo chown $(id -u):$(id -g) /usr/local/evm 7 | - curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash 8 | - export PATH="$HOME/.evm/bin:$PATH" 9 | - evm config path /tmp 10 | - evm install $EVM_EMACS --use --skip 11 | - git clone --depth=1 https://github.com/cask/cask.git "$HOME/.cask" 12 | - export PATH="$HOME/.cask/bin:$PATH" 13 | - cask 14 | script: 15 | - cask exec buttercup -L . 16 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa-stable) 3 | 4 | (development 5 | (depends-on "buttercup")) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](http://melpa.org/packages/bpr-badge.svg)](http://melpa.org/#/bpr) [![Build Status](https://travis-ci.org/ilya-babanov/emacs-bpr.svg)](https://travis-ci.org/ilya-babanov/emacs-bpr) 2 | ## Emacs-BPR (Background Process Runner) 3 | This package provides logic for async process execution. 4 | 5 | It's similar to `async-shell-command`, but: 6 | - `bpr` spawns processes asynchronously without displaying output buffers. 7 | - `bpr` shows progress messages for running processes in echo area. 8 | - `bpr` can display buffer with process output in case of errors. 9 | - `bpr` can use `projectile` for assigning process directory. 10 | - `bpr` can format process output (understands ansi escape codes). 11 | - you can add handlers for `completion/success/error` events 12 | - you can set different options for different processes. 13 | 14 | `bpr` is very handy for running tests/builds, but you can run any processes with it. 15 | 16 | ## Example 17 | Given this configuration: 18 | ```elisp 19 | (require 'bpr) 20 | 21 | ;; Set global config for bpr. 22 | ;; Variables below are applied to all processes. 23 | (setq bpr-colorize-output t) 24 | (setq bpr-close-after-success t) 25 | 26 | ;; define function for running desired process 27 | (defun run-tests () 28 | "Spawns 'grunt test' process" 29 | (interactive) 30 | ;; Set dynamic config for process. 31 | ;; Variables below are applied only to particular process 32 | (let* ((bpr-scroll-direction -1)) 33 | (bpr-spawn "grunt test --color"))) 34 | 35 | ;; set key-binding 36 | (define-key global-map "\C-ct" 'run-tests) 37 | ``` 38 | You get this behavior for success: 39 | ![grunt test success](./img/success-run.gif) 40 | And this for error: 41 | ![grunt test error](./img/error-run.gif) 42 | 43 | What's happening: 44 | - User enters predefined key-binding, which invokes function `run-tests`. 45 | - `bpr-spawn` starts async process `grunt test --color` and writes progress messages in echo area. 46 | - If process ends successfully - success message is being shown. 47 | - If process ends with error - error message is being shown and window with output buffer is being opened. 48 | 49 | ## Installation 50 | ### MELPA 51 | `M-x package-install bpr` 52 | 53 | ### Manually 54 | ```elisp 55 | ;; If you have cloned this repo into `~/some-path/emacs-bpr/` 56 | (add-to-list 'load-path "~/some-path/emacs-bpr/") 57 | (require 'bpr) 58 | ``` 59 | 60 | ## Configuration 61 | If you want to set options globally for all processes: 62 | ```elisp 63 | (require 'bpr) 64 | 65 | ;; use ansi-color-apply-on-region function on output buffer 66 | (setq bpr-colorize-output t) 67 | 68 | ;; use comint-mode for processes output buffers instead of shell-mode 69 | (setq bpr-process-mode #'comint-mode) 70 | 71 | ;; call `do-something` whenever bpr-spawn's process is compelted 72 | (setq bpr-on-completion #'do-something) 73 | ``` 74 | 75 | If you want to set options to particular process, set them dynamically right before `bpr-spawn`: 76 | ```elisp 77 | (let* (;; don't erase process output buffer before starting this process again. 78 | (bpr-erase-process-buffer nil) 79 | ;; don't show progress messages (only success/error messages will be displayed) 80 | (bpr-show-progress nil) 81 | ;; call `do-something` when process below is successfully completed 82 | (bpr-on-success #'do-something)) 83 | (bpr-spawn "ping -c 4 www.wikipedia.org")) 84 | ``` 85 | 86 | Default directory for processes is `default-directory` of current buffer, but with `projectile` installed, `bpr` would use `projectile-project-root` function. To disable projectile support, set `bpr-use-projectile` to nil. If you want to set custom logic for project root detection, just reimplement `bpr-try-get-project-root` function. 87 | 88 | Default major mode for process's output buffer is `shell-mode`. Note, that this buffer is only showed in case of error, but you can manually open it at any time by `bpr-open-last-buffer`. Template for buffers names: `*process-name (process-directory)*` 89 | 90 | ### Commands 91 | ###### `bpr-spawn (cmd)` 92 | Executes string CMD asynchronously in background. 93 | 94 | ###### `bpr-open-last-buffer ()` 95 | Opens the buffer of the last spawned process. 96 | 97 | ### Options 98 | ###### `bpr-close-after-success nil` 99 | Indicates whether the process output window is closed on success. 100 | 101 | ###### `bpr-open-after-error t` 102 | Indicates whether the process output window is shown on error. 103 | 104 | ###### `bpr-window-creator #'split-window-vertically` 105 | Function for creating window for process. 106 | 107 | ###### `bpr-process-mode #'shell-mode` 108 | Mode for process's buffer. 109 | 110 | ###### `bpr-process-directory nil` 111 | Directory for process. 112 | If not nil, it will be assigned to default-direcotry. 113 | If nil, standard default-direcotry will be used, 114 | or projectile-project-root, if it's available and bpr-use-projectile isn't nil. 115 | 116 | ###### `bpr-use-projectile t` 117 | Whether to use projectile-project-root (if available) for process's directory. 118 | 119 | ###### `bpr-erase-process-buffer t` 120 | Indicates whether the process buffer is erased at the start of the new process. 121 | 122 | ###### `bpr-scroll-direction 1` 123 | Scroll text in error window, -1 for scroll up, 1 - scroll down. 124 | 125 | ###### `bpr-show-progress t` 126 | Whether to show progress messages for process. 127 | 128 | ###### `bpr-poll-timout 0.2` 129 | Progress update interval. 130 | 131 | ###### `bpr-colorize-output nil` 132 | Whether to colorize process output buffer. For this operation `ansi-color-apply-on-region' is used. 133 | 134 | ###### `bpr-on-success '(lambda (process))` 135 | Function which is called in case of success. If function is interactive, it's called interactively; if not, it's called in a normal way with one argument - process. 136 | 137 | ###### `bpr-on-error '(lambda (process))` 138 | Function which is called in case of error. If function is interactive, it's called interactively; if not, it's called in a normal way with one argument - process. 139 | 140 | ###### `bpr-on-completion '(lambda (process))` 141 | Function, which is always called when process is completed. If function is interactive, it's called interactively; if not, it's called in a normal way with one argument - process. 142 | 143 | ### Examples for different use cases 144 | ##### Running tests 145 | ```elisp 146 | (defun my-test-runner () 147 | "Spawns test process" 148 | (interactive) 149 | (let* ((bpr-scroll-direction -1) ;; scroll to the top of the output window (which is being shown in case of error) 150 | (bpr-close-after-success t)) ;; close error window after process ended successfully (if it's not already closed) 151 | (bpr-spawn "rake tests"))) 152 | ``` 153 | ##### Running builds 154 | ```elisp 155 | (defun my-build-runner () 156 | "Spawns build process" 157 | (interactive) 158 | (let* ((bpr-process-directory "~/chromium/") ;; spawn process in this directory (instead of default-directory or projectile-project-root) 159 | (bpr-poll-timout 60.0)) ;; show progress messages once in 60 seconds 160 | (bpr-spawn "make long-build"))) 161 | ``` 162 | -------------------------------------------------------------------------------- /UNLICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /bpr.el: -------------------------------------------------------------------------------- 1 | ;;; bpr.el --- Background Process Runner 2 | 3 | ;; Author: Ilya Babanov 4 | ;; URL: https://github.com/ilya-babanov/emacs-bpr 5 | ;; Version: 1.5 6 | ;; Package-Requires: ((emacs "24")) 7 | ;; Keywords: background, async, process, management 8 | ;; License: Unlicense 9 | 10 | ;;; Commentary: 11 | ;; This package provides functionality for running processes in background. 12 | ;; For detailed instructions see https://github.com/ilya-babanov/emacs-bpr. 13 | 14 | ;;; Code: 15 | (require 'shell) 16 | 17 | (defgroup bpr nil 18 | "Background Process Runner" 19 | :group 'processes 20 | :group 'extensions) 21 | 22 | (defcustom bpr-close-after-success nil 23 | "Indicates whether the process output window is closed on success." 24 | :type 'boolean) 25 | 26 | (defcustom bpr-open-after-error t 27 | "Indicates whether the process output window is shown on error." 28 | :group 'bpr 29 | :type 'boolean) 30 | 31 | (defcustom bpr-window-creator #'split-window-vertically 32 | "Function for creating window for process." 33 | :group 'bpr 34 | :type 'function) 35 | 36 | (defcustom bpr-process-mode #'shell-mode 37 | "Mode for process's buffer." 38 | :group 'bpr 39 | :type 'function) 40 | 41 | (defcustom bpr-process-directory nil 42 | "Directory for process. 43 | If not nil, it will be assigned to default-direcotry. 44 | If nil, standard default-direcotry will be used, 45 | or projectile-project-root, if it's available and bpr-use-projectile isn't nil." 46 | :group 'bpr 47 | :type 'string) 48 | 49 | (defcustom bpr-use-projectile t 50 | "Whether to use projectile-project-root (if available) for process's directory." 51 | :group 'bpr 52 | :type 'boolean) 53 | 54 | (defcustom bpr-erase-process-buffer t 55 | "Indicates whether the process buffer is erased at the start of the new process." 56 | :group 'bpr 57 | :type 'boolean) 58 | 59 | (defcustom bpr-scroll-direction 1 60 | "Scroll text in error window, -1 for scroll up, 1 - scroll down." 61 | :group 'bpr 62 | :type 'number) 63 | 64 | (defcustom bpr-show-progress t 65 | "Whether to show progress messages for process." 66 | :group 'bpr 67 | :type 'boolean) 68 | 69 | (defcustom bpr-poll-timout 0.2 70 | "Progress update interval." 71 | :group 'bpr 72 | :type 'number) 73 | 74 | (defcustom bpr-colorize-output nil 75 | "Whether to colorize process output buffer. 76 | For this operation `ansi-color-apply-on-region' is used." 77 | :group 'bpr 78 | :type 'boolean) 79 | 80 | (defcustom bpr-on-start '(lambda (process)) 81 | "Function, which is called when the process starts. 82 | If function is interactive, it's called interactively; 83 | if not, it's called in normal way with one argument - process." 84 | :group 'bpr 85 | :type 'function) 86 | 87 | (defcustom bpr-on-success '(lambda (process)) 88 | "Function, which is called in case of success. 89 | If function is interactive, it's called interactively; 90 | if not, it's called in normal way with one argument - process." 91 | :group 'bpr 92 | :type 'function) 93 | 94 | (defcustom bpr-on-error '(lambda (process)) 95 | "Function, which is called in case of error 96 | If function is interactive, it's called interactively; 97 | if not, it's called in normal way with one argument - process." 98 | :group 'bpr 99 | :type 'function) 100 | 101 | (defcustom bpr-on-completion '(lambda (process)) 102 | "Function, which is always called when process is completed 103 | If function is interactive, it's called interactively; 104 | if not, it's called in normal way with one argument - process." 105 | :group 'bpr 106 | :type 'function) 107 | 108 | (defvar bpr-last-buffer nil 109 | "Buffer for the last spawned process.") 110 | 111 | ;;;###autoload 112 | (defun bpr-spawn (cmd) 113 | "Executes string CMD asynchronously in background." 114 | (interactive 115 | (list (read-shell-command "Command: "))) 116 | (let* ((proc-name (bpr-create-process-name cmd)) 117 | (process (get-process proc-name))) 118 | (if process 119 | (progn 120 | (message "Process already exist: %s" process) 121 | (bpr-try-refresh-process-window process)) 122 | (bpr-run-process cmd)))) 123 | 124 | ;;;###autoload 125 | (defun bpr-open-last-buffer () 126 | "Opens the buffer of the last spawned process." 127 | (interactive) 128 | (if (buffer-live-p bpr-last-buffer) 129 | (set-window-buffer (funcall bpr-window-creator) bpr-last-buffer) 130 | (message "Can't find last used buffer"))) 131 | 132 | (defun bpr-run-process (cmd) 133 | (message "Running process '%s'" cmd) 134 | (let* ((default-directory (bpr-get-current-directory)) 135 | (proc-name (bpr-create-process-name cmd)) 136 | (buff-name (concat "*" proc-name "*")) 137 | (buffer (get-buffer-create buff-name)) 138 | (process (start-process-shell-command proc-name buffer cmd))) 139 | (setq bpr-last-buffer buffer) 140 | (set-process-plist process (bpr-create-process-plist)) 141 | (set-process-sentinel process 'bpr-handle-result) 142 | (bpr-handle-progress process) 143 | (bpr-config-process-buffer buffer) 144 | (bpr-funcall (process-get process 'on-start) process))) 145 | 146 | (defun bpr-get-current-directory () 147 | (if bpr-process-directory 148 | bpr-process-directory 149 | (bpr-try-get-project-root))) 150 | 151 | (defun bpr-try-get-project-root () 152 | (if (and bpr-use-projectile (fboundp 'projectile-project-root)) 153 | (let ((projectile-require-project-root nil)) 154 | (projectile-project-root)) 155 | default-directory)) 156 | 157 | (defun bpr-create-process-name (cmd) 158 | (concat cmd " (" (abbreviate-file-name (bpr-get-current-directory)) ")")) 159 | 160 | (defun bpr-create-process-plist () 161 | (list 'poll-timeout bpr-poll-timout 162 | 'close-after-success bpr-close-after-success 163 | 'open-after-error bpr-open-after-error 164 | 'show-progress bpr-show-progress 165 | 'window-creator bpr-window-creator 166 | 'colorize-output bpr-colorize-output 167 | 'scroll-direction bpr-scroll-direction 168 | 'on-start bpr-on-start 169 | 'on-success bpr-on-success 170 | 'on-error bpr-on-error 171 | 'on-completion bpr-on-completion 172 | 'start-time (float-time))) 173 | 174 | (defun bpr-config-process-buffer (buffer) 175 | (when buffer 176 | (with-current-buffer buffer 177 | (when (and bpr-erase-process-buffer (not buffer-read-only)) 178 | (let ((inhibit-read-only t)) 179 | (erase-buffer))) 180 | (funcall bpr-process-mode)))) 181 | 182 | (defun bpr-handle-progress (process) 183 | (when (process-live-p process) 184 | (let* ((show-progress (process-get process 'show-progress))) 185 | (when show-progress (bpr-show-progress-message process)) 186 | (bpr-delay-progress-handler process)))) 187 | 188 | (defun bpr-delay-progress-handler (process) 189 | (let* ((poll-timeout (process-get process 'poll-timeout))) 190 | (run-at-time poll-timeout nil 'bpr-handle-progress process))) 191 | 192 | (defun bpr-handle-result (process &optional event) 193 | (bpr-colorize-process-buffer process) 194 | (unless (process-live-p process) 195 | (bpr-funcall (process-get process 'on-completion) process) 196 | (let* ((exit-code (process-exit-status process))) 197 | (if (= exit-code 0) 198 | (bpr-handle-success process) 199 | (bpr-handle-error process exit-code))))) 200 | 201 | (defun bpr-handle-success (process) 202 | (bpr-funcall (process-get process 'on-success) process) 203 | (bpr-show-success-message process) 204 | (let* ((buffer-window (bpr-get-process-window process)) 205 | (close-after-success (process-get process 'close-after-success))) 206 | (when (and buffer-window close-after-success) 207 | (delete-window buffer-window)))) 208 | 209 | (defun bpr-handle-error (process exit-code) 210 | (bpr-funcall (process-get process 'on-error) process) 211 | (bpr-show-error-message process exit-code) 212 | (let* ((buffer (process-buffer process)) 213 | (buffer-window (get-buffer-window buffer)) 214 | (open-after-error (process-get process 'open-after-error))) 215 | (when (and open-after-error (not buffer-window)) 216 | (setq buffer-window (funcall (process-get process 'window-creator))) 217 | (set-window-buffer buffer-window buffer)) 218 | (bpr-try-refresh-process-window process))) 219 | 220 | (defun bpr-show-progress-message (process) 221 | (let* ((status (process-status process)) 222 | (time-diff (bpr-get-process-time-diff process))) 223 | (message "Status: %s Time: %.1f Process: %s" status time-diff process))) 224 | 225 | (defun bpr-show-success-message (process) 226 | (message "Status: %s Time: %.3f Process: %s" 227 | (propertize "Success" 'face '(:foreground "green")) 228 | (bpr-get-process-time-diff process) 229 | process)) 230 | 231 | (defun bpr-show-error-message (process exit-code) 232 | (message "Status: %s Code: %s Time: %.3f Process: %s" 233 | (propertize "Error" 'face '(:foreground "red")) 234 | exit-code 235 | (bpr-get-process-time-diff process) 236 | process)) 237 | 238 | (defun bpr-get-process-time-diff (process) 239 | (let* ((start-time (process-get process 'start-time))) 240 | (- (float-time) start-time))) 241 | 242 | (defun bpr-get-process-window (process) 243 | (let* ((buffer (process-buffer process))) 244 | (get-buffer-window buffer))) 245 | 246 | (defun bpr-try-refresh-process-window (process) 247 | (let* ((window (bpr-get-process-window process)) 248 | (scroll-direciton (process-get process 'scroll-direction))) 249 | (when window (bpr-refresh-process-window window scroll-direciton)))) 250 | 251 | (defun bpr-refresh-process-window (window direction) 252 | (with-selected-window window 253 | (ignore-errors 254 | (scroll-down-command (bpr-get-remaining-lines-count direction))))) 255 | 256 | (defun bpr-colorize-process-buffer (process) 257 | (when (and (process-get process 'colorize-output) 258 | (fboundp 'ansi-color-apply-on-region)) 259 | (with-current-buffer (process-buffer process) 260 | (unless buffer-read-only 261 | (ansi-color-apply-on-region (point-min) (point-max)))))) 262 | 263 | (defun bpr-get-remaining-lines-count (direction) 264 | (count-lines (point) (buffer-end direction))) 265 | 266 | (defun bpr-funcall (fn &rest args) 267 | (if (commandp fn t) 268 | (call-interactively fn) 269 | (apply fn args))) 270 | 271 | (provide 'bpr) 272 | ;;; bpr.el ends here 273 | -------------------------------------------------------------------------------- /img/error-run.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iliabv/emacs-bpr/7f3c787ed80ac0e83447192ac5450dfa7110ade1/img/error-run.gif -------------------------------------------------------------------------------- /img/success-run.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iliabv/emacs-bpr/7f3c787ed80ac0e83447192ac5450dfa7110ade1/img/success-run.gif -------------------------------------------------------------------------------- /test-bpr.el: -------------------------------------------------------------------------------- 1 | ;;; test-bpr.el --- Tests for Background Process Runner 2 | 3 | ;;; Commentary: 4 | ;; WIP 5 | ;; It isn't good tests: 6 | ;; All used emacs functions are mocked, and each test checks that 7 | ;; correct emacs function(s) have been called. 8 | ;; If some internal package logic changes (without breaking the public api behavior) 9 | ;; there is a chance that tests will fail. 10 | 11 | ;; How to run: 12 | ;; cask exec buttercup -L . 13 | 14 | ;;; Code: 15 | (require 'bpr) 16 | (require 'buttercup) 17 | 18 | (defmacro with-fake-buffer (buffer-or-name &rest body) 19 | `(save-current-buffer 20 | (set-buffer ,buffer-or-name) 21 | (fset 'old-get-buffer-create 'get-buffer-create) 22 | (fset 'get-buffer-create (lambda (name) fake-buffer)) 23 | ,@body 24 | (fset 'get-buffer-create 'old-get-buffer-create))) 25 | 26 | (describe "bpr-backage" 27 | (let* (default-directory fake-buffer fake-process fake-plist) 28 | 29 | (before-each 30 | (setq default-directory "/test/") 31 | (setq fake-buffer (get-buffer-create "test-buffer")) 32 | (setq fake-process "I am fake process") 33 | 34 | (fset 'message (lambda (str &rest args) nil)) 35 | (fset 'delete-window (lambda (window) nil)) 36 | (fset 'get-buffer-window (lambda (buffer) nil)) 37 | (fset 'erase-buffer (lambda () nil)) 38 | (fset 'shell-mode (lambda () nil)) 39 | (fset 'set-window-buffer (lambda (window buffer) nil)) 40 | (fset 'split-window-vertically (lambda () nil)) 41 | (fset 'process-live-p (lambda (process) nil)) 42 | (fset 'get-process (lambda (name) nil)) 43 | (fset 'process-buffer (lambda (process) fake-buffer)) 44 | (fset 'process-get (lambda (process prop) (plist-get fake-plist prop))) 45 | (fset 'process-exit-status (lambda (process) nil)) 46 | (fset 'set-process-plist (lambda (process plist) (setq fake-plist plist))) 47 | (fset 'set-process-sentinel (lambda (process func) nil)) 48 | (fset 'start-process-shell-command (lambda (name buffer command) fake-process)) 49 | 50 | (spy-on 'delete-window :and-call-through) 51 | (spy-on 'erase-buffer :and-call-through) 52 | (spy-on 'split-window-vertically :and-call-through) 53 | (spy-on 'set-window-buffer :and-call-through) 54 | (spy-on 'get-buffer-create :and-call-through) 55 | (spy-on 'start-process-shell-command :and-call-through)) 56 | 57 | (describe "defaults" 58 | (it "should have correct values" 59 | (expect bpr-close-after-success :to-be nil) 60 | (expect bpr-open-after-error :to-be t) 61 | (expect bpr-window-creator :to-be #'split-window-vertically) 62 | (expect bpr-process-mode :to-be #'shell-mode) 63 | (expect bpr-process-directory :to-be nil) 64 | (expect bpr-use-projectile :to-be t) 65 | (expect bpr-erase-process-buffer :to-be t) 66 | (expect bpr-scroll-direction :to-be 1) 67 | (expect bpr-show-progress :to-be t) 68 | (expect bpr-poll-timout :to-equal 0.2) 69 | (expect bpr-open-after-error :to-be t))) 70 | 71 | (describe "bpr-spawn" 72 | (it "should set correct name for process buffer" 73 | (bpr-spawn "ls") 74 | (expect 'get-buffer-create 75 | :to-have-been-called-with 76 | (concat "*ls (" default-directory ")*"))) 77 | 78 | ;; All directory checks are made by checking process buffer name... 79 | ;; This is because I haven't found direct way to do it. 80 | (it "should set correct directory for process with default options" 81 | (bpr-spawn "ls") 82 | (expect 'get-buffer-create 83 | :to-have-been-called-with 84 | "*ls (/test/)*")) 85 | 86 | (it "should set correct directory for process using projectile" 87 | (fset 'projectile-project-root (lambda () "/projects/root/")) 88 | (bpr-spawn "ls") 89 | (expect 'get-buffer-create 90 | :to-have-been-called-with 91 | "*ls (/projects/root/)*") 92 | (fmakunbound 'projectile-project-root)) 93 | 94 | 95 | (it "should not use projectile when bpr-use-projectile is nil" 96 | (fset 'projectile-project-root (lambda () "/projects/root/")) 97 | (let* ((default-directory ".") 98 | (bpr-use-projectile nil)) 99 | (bpr-spawn "ls") 100 | (expect 'get-buffer-create 101 | :to-have-been-called-with 102 | "*ls (.)*")) 103 | (fmakunbound 'projectile-project-root)) 104 | 105 | (it "should use bpr-process-directory if it's not nil" 106 | (fset 'projectile-project-root (lambda () "/projects/root/")) 107 | (let* ((default-directory ".") 108 | (bpr-process-directory "should/use/this")) 109 | (bpr-spawn "ls") 110 | (expect 'get-buffer-create 111 | :to-have-been-called-with 112 | "*ls (should/use/this)*")) 113 | (fmakunbound 'projectile-project-root)) 114 | 115 | (it "should spawn process with correct name, buffer and command" 116 | (with-fake-buffer fake-buffer 117 | (let* ((default-directory "dev/null")) 118 | (bpr-spawn "ls") 119 | (expect 'start-process-shell-command 120 | :to-have-been-called-with 121 | "ls (dev/null)" fake-buffer "ls")))) 122 | 123 | (it "shoud not start process if it already exists" 124 | (fset 'get-process (lambda (name) (when (equal name "ls (/test/)") fake-process))) 125 | (bpr-spawn "ls") 126 | (expect 'start-process-shell-command :not :to-have-been-called)) 127 | 128 | (it "should erase process buffer" 129 | (bpr-spawn "ls -la") 130 | (expect 'erase-buffer :to-have-been-called)) 131 | 132 | (it "should not erase process buffer if it's read only" 133 | (with-fake-buffer fake-buffer 134 | (let* ((buffer-read-only t)) 135 | (bpr-spawn "ls -la") 136 | (expect 'erase-buffer :not :to-have-been-called)))) 137 | 138 | (it "should not erase process buffer if bpr-erase-process-buffer is nil" 139 | (let* ((bpr-erase-process-buffer nil)) 140 | (bpr-spawn "ls -la") 141 | (expect 'erase-buffer :not :to-have-been-called))) 142 | 143 | (it "should call bpr-process-mode on process buffer" 144 | (fset 'test-mode-func (lambda ())) 145 | (let* ((bpr-process-mode 'test-mode-func)) 146 | (spy-on 'test-mode-func) 147 | (bpr-spawn "ls -la") 148 | (expect 'test-mode-func :to-have-been-called))) 149 | 150 | (it "should display process buffer in case of error" 151 | (let* (test-sentiel-handler) 152 | (fset 'set-process-sentinel (lambda (process handler) 153 | (when (eq process fake-process) 154 | (setq test-sentiel-handler handler)))) 155 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 156 | (bpr-spawn "make build") 157 | (funcall test-sentiel-handler fake-process) 158 | (expect 'split-window-vertically :to-have-been-called) 159 | (expect 'set-window-buffer :to-have-been-called))) 160 | 161 | (it "should not display process buffer in case of error if bpr-open-after-error is nil" 162 | (let* ((test-sentiel-handler nil) 163 | (bpr-open-after-error nil)) 164 | (fset 'set-process-sentinel (lambda (process handler) 165 | (when (eq process fake-process) 166 | (setq test-sentiel-handler handler)))) 167 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 168 | (bpr-spawn "make build") 169 | (funcall test-sentiel-handler fake-process) 170 | (expect 'split-window-vertically :not :to-have-been-called) 171 | (expect 'set-window-buffer :not :to-have-been-called))) 172 | 173 | (it "should not close error buffer after success with default options" 174 | (let* ((test-sentiel-handler nil)) 175 | (fset 'set-process-sentinel (lambda (process handler) 176 | (when (eq process fake-process) 177 | (setq test-sentiel-handler handler)))) 178 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 179 | (bpr-spawn "make build") 180 | (funcall test-sentiel-handler fake-process) 181 | (expect 'split-window-vertically :to-have-been-called) 182 | (expect 'set-window-buffer :to-have-been-called) 183 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 0))) 184 | (fset 'get-buffer-window (lambda (buffer) (when (eq buffer fake-buffer) "I am window"))) 185 | (bpr-spawn "make build") 186 | (funcall test-sentiel-handler fake-process) 187 | (expect 'delete-window :not :to-have-been-called))) 188 | 189 | (it "should close error buffer after success when bpr-close-after-success is t" 190 | (let* ((bpr-close-after-success t) 191 | (test-sentiel-handler nil)) 192 | (fset 'set-process-sentinel (lambda (process handler) 193 | (when (eq process fake-process) 194 | (setq test-sentiel-handler handler)))) 195 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 196 | (bpr-spawn "make build") 197 | (funcall test-sentiel-handler fake-process) 198 | (expect 'split-window-vertically :to-have-been-called) 199 | (expect 'set-window-buffer :to-have-been-called) 200 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 0))) 201 | (fset 'get-buffer-window (lambda (buffer) (when (eq buffer fake-buffer) "I am window"))) 202 | (bpr-spawn "make build") 203 | (funcall test-sentiel-handler fake-process) 204 | (expect 'delete-window :to-have-been-called))) 205 | 206 | (it "should colorize process buffer" 207 | (let* ((bpr-colorize-output t) 208 | (test-sentiel-handler nil)) 209 | (fset 'set-process-sentinel (lambda (process handler) 210 | (when (eq process fake-process) 211 | (setq test-sentiel-handler handler)))) 212 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 213 | (fset 'ansi-color-apply-on-region (lambda (begin end) nil)) 214 | (spy-on 'ansi-color-apply-on-region :and-call-through) 215 | (bpr-spawn "make build") 216 | (funcall test-sentiel-handler fake-process) 217 | (expect 'ansi-color-apply-on-region :to-have-been-called))) 218 | 219 | (it "should not colorize process buffer if it's read only" 220 | (let* ((bpr-colorize-output t) 221 | (test-sentiel-handler nil)) 222 | (fset 'set-process-sentinel (lambda (process handler) 223 | (when (eq process fake-process) 224 | (setq test-sentiel-handler handler)))) 225 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 226 | (fset 'ansi-color-apply-on-region (lambda (begin end) nil)) 227 | (spy-on 'ansi-color-apply-on-region :and-call-through) 228 | (bpr-spawn "make build") 229 | (with-current-buffer fake-buffer 230 | (let* ((buffer-read-only t)) 231 | (funcall test-sentiel-handler fake-process))) 232 | (expect 'ansi-color-apply-on-region :not :to-have-been-called))) 233 | 234 | (it "should call bpr-on-completion function in case of error and success" 235 | (let* ((completion-flag nil) 236 | (bpr-on-completion (lambda (p) (setq completion-flag t))) 237 | (test-sentiel-handler nil)) 238 | (fset 'set-process-sentinel (lambda (process handler) 239 | (when (eq process fake-process) 240 | (setq test-sentiel-handler handler)))) 241 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 0))) 242 | (bpr-spawn "make build") 243 | (funcall test-sentiel-handler fake-process) 244 | (expect completion-flag :to-be t) 245 | 246 | (setq completion-flag nil) 247 | (setq test-sentiel-handler nil) 248 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 249 | (bpr-spawn "make build") 250 | (funcall test-sentiel-handler fake-process) 251 | (expect completion-flag :to-be t))) 252 | 253 | (it "should call bpr-on-success function in case of success and not call in case of error" 254 | (let* ((success-flag nil) 255 | (bpr-on-success (lambda (p) (setq success-flag t))) 256 | (test-sentiel-handler nil)) 257 | (fset 'set-process-sentinel (lambda (process handler) 258 | (when (eq process fake-process) 259 | (setq test-sentiel-handler handler)))) 260 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 0))) 261 | (bpr-spawn "make build") 262 | (funcall test-sentiel-handler fake-process) 263 | (expect success-flag :to-be t) 264 | 265 | (setq success-flag nil) 266 | (setq test-sentiel-handler nil) 267 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 268 | (bpr-spawn "make build") 269 | (funcall test-sentiel-handler fake-process) 270 | (expect success-flag :to-be nil))) 271 | 272 | (it "should not call bpr-on-error function in case of success and call in case of error" 273 | (let* ((error-flag nil) 274 | (bpr-on-error (lambda (p) (setq error-flag t))) 275 | (test-sentiel-handler nil)) 276 | (fset 'set-process-sentinel (lambda (process handler) 277 | (when (eq process fake-process) 278 | (setq test-sentiel-handler handler)))) 279 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 0))) 280 | (bpr-spawn "make build") 281 | (funcall test-sentiel-handler fake-process) 282 | (expect error-flag :to-be nil) 283 | 284 | (setq test-sentiel-handler nil) 285 | (fset 'process-exit-status (lambda (process) (when (eq process fake-process) 3))) 286 | (bpr-spawn "make build") 287 | (funcall test-sentiel-handler fake-process) 288 | (expect error-flag :to-be t))))) 289 | 290 | 291 | (describe "bpr-open-last-buffer" 292 | (it "should open last used buffer" 293 | (with-fake-buffer fake-buffer 294 | (bpr-spawn "ls") 295 | (bpr-open-last-buffer) 296 | (expect 'split-window-vertically :to-have-been-called) 297 | (expect 'set-window-buffer :to-have-been-called-with nil fake-buffer))) 298 | 299 | (it "should not open last used buffer if it isn't exist" 300 | (let* ((bpr-last-buffer nil)) 301 | (bpr-open-last-buffer) 302 | (expect 'split-window-vertically :not :to-have-been-called) 303 | (expect 'set-window-buffer :not :to-have-been-called))) 304 | 305 | (it "should not open last used buffer if it have been killed" 306 | (with-fake-buffer fake-buffer 307 | (bpr-spawn "ls") 308 | (kill-buffer fake-buffer) 309 | (bpr-open-last-buffer) 310 | (expect 'split-window-vertically :not :to-have-been-called) 311 | (expect 'set-window-buffer :not :to-have-been-called))))) 312 | 313 | ;;; test-bpr.el ends here 314 | --------------------------------------------------------------------------------