-->
2018-07-11
In this one I explore Emacs’s self-documentation and customization features to make my IDE just a bit better.
Spacemacs is a great Rails IDE. With a spec file open , t b
opens the compilation buffer and runs the tests. The problem is the output is not very space efficient. It runs rspec with the -b
flag, which includes the full error backtrace. A simple failure in a single spec can easily have ~50 lines of traceback, most of it useless.
I’m going to document my exploration of the Emacs ruby test runner code, and my attempt to make the output more appropriate for Spacemacs.
First things first - lets find an entry point. One of my favorite features of Spacemacs is command discoverability and self-documentation. I already know that , t b
1 runs all the tests in the buffer…But what code does it execute? Lets try that again, slower.
Type , t
and pause. Now we see that if we added b
we’d execute ruby-test-run
, so thats our entry point.
If you have a function name, the next step is to use Emacs’s built in help functionality. With Spacemacs you can get to it with SPC h d f
2. Now type in the function you want help with and hit enter.
Here we see the file the function is defined in, the function’s signature, and some documentation.
Get your cursor over ruby-test-mode.el
and hit enter. You’ll instantly be taken to the definition of the function :)
;;;###autoload
(defun ruby-test-run ()
"Run the current buffer's file as specification or unit test."
(interactive)
(let ((filename (ruby-test-find-file)))
(if filename
(ruby-test-with-ruby-directory filename
(ruby-test-run-command (ruby-test-command filename)))
(message ruby-test-not-found-message))))
This code handles a bit of plumbing but since we’re laser focused on just the command being run we can hone in on (ruby-test-run-command (ruby-test-command filename))
. ruby-test-command
looks like promising, so lets check it out. Move your cursor over it and type gd
to jump to its definition.
(defun ruby-test-command (filename &optional line-number)
"Return the command to run a unit test or a specification depending on the FILENAME and LINE-NUMBER."
(cond ((ruby-test-spec-p filename)
(ruby-test-spec-command filename line-number))
((ruby-test-p filename)
(ruby-test-test-command filename line-number))
(t (message "File is not a known ruby test file"))))
This looks like a conditional that determines whether it should dispatch to RSpec or Test::Unit. We’re only interested in RSpec in the moment so lets jump into ruby-test-spec-command
.
(defun ruby-test-spec-command (filename &optional line-number)
"Return command to run spec in FILENAME at LINE-NUMBER."
(let ((command
(cond ((file-exists-p ".zeus.sock") "zeus rspec")
((file-exists-p "bin/rspec") "bin/rspec")
(t "bundle exec rspec")))
(options ruby-test-rspec-options)
(filename (if line-number
(format "%s:%s" filename line-number)
filename)))
(format "%s %s %s" command (mapconcat 'identity options " ") filename)))
We’re close now. I can feel it. Lets look at the definition of ruby-test-rspec-options
:
(defcustom ruby-test-rspec-options
'("-b")
"Pass extra command line options to RSpec when running specs."
:initialize 'custom-initialize-default
:type '(list)
:group 'ruby-test)
Boom! This passes -b
into the command, filling our test buffer with dozens of lines of trash. But what’s defcustom
? As an Emacs newbie I’d never seen that before so I googled it and landed on its docs. Turns out Emacs has a GUI config system called the “customization interface.” defcustom
is a function for adding config parameters along with their default values and setter functions.
At this point you could use the customization GUI to modify the value for ruby-test-rspec-options
. My preference is to stick with simple file based config.
All thats left now is to override the value for this variable in your .spacemacs
. (SPC f e d
takes you right to it). Jump to your dotspacemacs/user-config
section and add the following:
;; disable rspec backtrace (-b) option
(setq ruby-test-rspec-options '())
Now reload your config (SPC f e R
) and we’re done!
setq
, custom-set-variables
, customize-set-variable
. This is a bit of a rabbit hole. In short - if the variable in question uses a setter function, setq
won’t cut it. You’d know because defcustom
would have a :set
argument.M-x debug-on-entry
, then enter the function name you want to trace.