Fast Tests on JRuby (RSpec + Nailgun + autotest)

Michael Fairley

15 May 2012

JRuby is awesome. The JVM’s startup time is not:

$ rvm use 1.9.3
Using /Users/michaelfairley/.rvm/gems/ruby-1.9.3-p125
$ time ruby -e "puts 'Hello'"
Hello

real	0m0.077s
user	0m0.031s
sys	0m0.013s
$ rvm use jruby
Using /Users/michaelfairley/.rvm/gems/jruby-1.6.7
$ time ruby -e "puts 'Hello'"
Hello

real	0m1.955s
user	0m1.268s
sys	0m0.212s

This makes doing TDD in JRuby more painful than it ought to be.

Luckily, JRuby has built in support for Nailgun, which can prevent the need for starting up a new JVM each time we want to run Ruby, making things a brazilian times faster.

Using Nailgun is simple. First, start up the Nailgun server:

ruby --ng-server

And then pass --ng to ruby whenever you use it:

$ time ruby --ng -e "puts 'Hello'"
Hello

real	0m0.376s
user	0m0.001s
sys	0m0.004s

Much better.

Unfortunately, a teensy bit of monkey patching is required to get Nailgun playing nicely with autotest.

Dump this anywhere in your .autotest file:

# From https://github.com/rspec/rspec-core/blob/master/lib/autotest/rspec2.rb,
# but with "--ng" added.
class Autotest::Rspec2 < Autotest
  def make_test_cmd(files_to_test)
    files_to_test.empty? ? '' :
      "#{prefix}#{ruby}#{suffix} --ng -S '#{RSPEC_EXECUTABLE}' --tty \
      #{normalize(files_to_test).keys.flatten.map { |f| "'#{f}'"}.join(' ')}"
  end
end

And then run autotest with Nailgun:

ruby --ng -S autotest

Boom. Now you have a subsecond test cycle in JRuby.