As we saw on the previous article, we can run our specs using rspec-mode in emacs. But when working with code behind a wall, such as a VM environment, there's a catch. We write code on the host machine (in a shared directory), but the specs need to be run inside the virtual machine. This means that, for one, we need to modify the running command to previously access the target environment and on the other hand, there's a high probability that our directory structure differs from one side to the other.
In my case, on my host machine a spec might be located at /home/fedex/code/work/vm1/app5/spec/models/my_model.rb
, while on the virtual machine on /home/vagrant/work/vm1/app5/spec/models/my_model.rb
. A small but very real difference.
For this, I don't use rspec-mode at all. There are too many configurations involved for my taste. So I created three elisp commands that accomplish what I need and use the vmexec
script I showed on a previous article.
The first command will run all the specs on app1
inside vm1
. I know I shouldn't be hardcoding the virtual machines and apps here, but it was a quick and dirty fix I needed. The idea is to add some configuration to this in order to preset the specifics in a per session basis (and I still haven't gotten to that brainstorming session yet, comments and suggestions are encouraged).
The second and third commands rely on a function that retrieves the real file name of the spec that's inside the VM (that I'll explain in a moment). They run the command be rspec <file_name>
. But the third differs from the second in that it also appends the current line after a colon.
;;;###autoload
(defun fdx/vm/rspec-verify-all ()
(interactive)
(compile
(concat "vmexec vm1 app1 'be rspec spec' --no-rerun")))
;;;###autoload
(defun fdx/vm/rspec-verify ()
(interactive)
(compile
(concat "vmexec vm1 app1 'be rspec " (fdx/vm/current-file-spec) "' --no-rerun")))
;;;###autoload
(defun fdx/vm/rspec-verify-single ()
(interactive)
(compile
(concat "vmexec vm1 app1 'be rspec " (fdx/vm/current-file-spec) ":" (number-to-string (line-number-at-pos)) "' --no-rerun")))
All three of this commands use the compile
function, that executes the command we pass as a string argument and drops the output into the *compile*
buffer. It does so synchronously, which means that you'll see the output in real time, but also that the editor will hang until the process ends.
As I said, the last two commands rely on a function that returns the real file name in the server. The way I decided to figure this out is by splitting the current file path by the regex /spec/
. This assumes that there's only one folder named spec
in that entire path, which is the case in our code base. This is probably an assumption that will blow up at some point, but there's a very low probability that I'll ever have a spec
folder some place other than inside a project, so I went with this extremely simple approach.
This is the function. It concatenates the spec/
string to the first element (string) of the remainder elements (list) of the split function. The spec/
string needs to be prepended because split actually removes it.
;;;###autoload
(defun fdx/vm/current-file-spec ()
(concat "spec/" (first (cdr (split-string (buffer-file-name) "\\/spec\\/")))))
I imagine this function might be written in simpler terms, but I couldn't find a way to do it. If you know a way, please leave a comment, I'll be very grateful.
I hope this helps you in some way. I know that it's very specific to my particular needs, but the example might become handy to you at some point. Until then
Saluti