This post originated from an RSS feed registered with Ruby Buzz
by Eric Hodel.
Original Post: Speeding up Test Runs with fork
Feed Title: Segment7
Feed URL: http://blog.segment7.net/articles.rss
Feed Description: Posts about and around Ruby, MetaRuby, ruby2c, ZenTest and work at The Robot Co-op.
Loading Rails takes a significant portion of your test run time, especially when you want to run only one test file or one test method. On my Powerbook loading Rails takes between four and six seconds. If you're frequently running unit tests this constant overhead can quickly become annoying.
When using autotest I may have to wait as much as ten seconds (five seconds between scans for changes, four seconds to load rails, one second to run the test) before I know if my changes fixed a problem or not. Ten seconds is past the threshold where I can keep paying attention which makes my mind wander. (A wandering mind is no good for productive work.) Also, those extra four seconds of loading Rails per test start to add up. I may load rails hundreds of times in a day just to run a tiny test.
There's one already existing way to reduce or eliminate that constant overhead of loading Rails. In development mode Rails reloads files to keep things running without restarting Rails on every change. I prefer to have an environment that is guaranteed to be clean when the tests start and reloading files removes this option.
Since I want Rails loaded without any application code I chose to create a process that would load rails then open up a server socket and wait for connections. When a connection comes in the process will fork to make a copy of the environment that can then load the application and run the tests.
A regular test run for just one file runs like this:
$ time ruby test/controllers/route_controller_test.rb Loaded suite test/controllers/route_controller_test
Started
......................................................
Finished in 13.192465 seconds.
54 tests, 268 assertions, 0 failures, 0 errors
real 0m17.884s
user 0m8.147s
sys 0m1.424s
The difference between the real time and the Test::Unit run time accounts for Rails and app loading overhead, about five seconds.
I've tentatively named the parent process spawner 'ruby_fork' and the client 'ruby_fork_client', so you start up the parent process:
$ RAILS_ENV='test' ruby_fork -r rubygems -e 'require_gem "rails"'
/Users/drbrain/Links/ZT/bin/ruby_fork Running as PID 3570 on 9084
ruby_fork understands -r, -I and -e just like regular ruby so I can just load Rails and none of the rest of my application.
Then I run ruby_fork_client which takes its arguments and passes them across to the child process and then reads from the socket and prints to STDOUT.
$ time ruby_fork_client -r test/controllers/route_controller_test.rb
Loaded suite /Users/drbrain/Links/ZT/bin/ruby_fork
Started
......................................................
Finished in 12.442556 seconds.
54 tests, 268 assertions, 0 failures, 0 errors
real 0m13.947s
user 0m0.077s
sys 0m0.022s
Now that extra time spent loading Rails is gone and I'm left with application loading and Test::Unit overhead which is miniscule in comparison.
ruby_fork is not Rails specific. The server and client can do anything they like, so this has applications beyond testing Rails (for example, handling incoming mail) or even Rails itself.
I'd like to release ruby_fork and ruby_fork_client as part of ZenTest but I'll be holding it until 3.3.0. Currently ZenTest is almost ready for release and ruby_fork and ruby_fork_client needs to act more like a regular invocation of ruby.