Sunday, March 6

One-button testing

I wrote about some inefficiencies in my procedure of running tests a while ago. I really dislike the repetitiveness of commands to switch context, as I use the vim editor for editing code and a terminal for running the tests. Had it been code, I would have refactored it long ago. Now I decided that it's time to optimize this part a little bit.

My very first idea which I had come up with long ago was to write a small script called loop, which would loop a command given as an argument infinitely, waiting for a keypress between runs. The script was an extremely simple one-liner:

while true; do $@ ; read; done
However, it only helped me with losing the Up,Enter habit a little bit, as Enter would be sufficient.

The second go at the problem was on the right track. I decided to write a small Python script to behave much like the loop script, but it would register a global shortcut handler so that I could do an iteration without having to switch to the terminal.

The idea of handling global shortcuts was OK, but the implementation gave me some pains. I tried to look around for some examples of registering global shortcuts with GNOME, but found nothing really useful. I then remembered that a multitude of KDE apps register global shortcuts, and decided to try the KDE Python bindings. In the end I wasted several hours scouring the web for information and watching my app segfault because of odd reasons. It took me a long while to get the details mostly right, and because of reentrancy problems I managed to wedge my keyboard completely so that I had to login remotely and kill the Python process to get control back. I did not quite like having to load KDE libraries either, which took a whole second to import on script startup. In the end I dumped this solution for a more simplistic approach.

After playing with the KDE shortcuts for a while, I finally understood that I don't really need a global shortcut, as 99% of the time I need to run the tests while I am working in Vim. This allowed for a much simpler system. The loop script has remained, but has evolved significantly from the one-liner. Notably, it now checks the return status of the executed command and prints a red or a green horizontal bar with a timestamp. It is very nice to have coloured feedback on whether the tests have passed. There is also an option to run an alternate hardcoded command.

I implemented interprocess communication in a slightly hacky way, by having the loop script invoke another shell script for user input (this way I sidestepped smart signal handling in a shell script). The client, in our case vim, can then send a signal to the primitive sub-script with a simple killall command. Using the process name as a unique identifier is not very clean, but good enough for me.

So, there are three scripts in total:

  • loop (to be used from the command line)
  • dumbass.sh (the stupid sub-process, only used internally)
  • dumbass-kick.sh, which abstracts the killall command in case I want to implement it in a cleaner way. It accepts '1' or '2' as an argument. If you pass '2', loop runs the alternate command.

To be able to run the tests from vim by a single keypress, I added these lines to my .vimrc to bind the given command to F12, and the hardcoded alternate command to Shift+F12:

nmap <silent> <F12> :wall<CR>:silent !dumbass-kick.sh 1<CR>
imap <silent> <F12> <C-o>:wall<CR><C-o>:silent !dumbass-kick.sh 1<CR>
nmap <silent> <S-F12> :wall<CR>:silent !dumbass-kick.sh 2<CR>
imap <silent> <S-F12> <C-o>:wall<CR><C-o>:silent !
These commands also save all active files before running the tests. I added that because occasionally I forget or mistype the write command in vim and then waste some time trying to understand why the tests are misbehaving. All in all, this provides true one-button testing, you don't even have to exit from insert mode.

If you really want a global mapping, you can map a key to invoke dumbass-kick.sh in your window manager. That's a more lightweight solution than importing KDE libraries just to use their shortcut mechanism.

Even though I mostly use these scripts for running unit tests, they could be useful whenever you need to repeat the same command lots of times. For example, I have been toying around with Lilypond (music typesetting software) a bit, and I used loop on lilypond to generate DVI output on a keypress so that I could see my changes immediately without stopping to type and switching windows. The script could be useful with make when working with compiled languages.