Agile_disciple_small

Ruby Tidbits: Spork

Last week Tim Harper announced Spork, which is billed as a better RSpec DRb server. What does that mean, and why should I care?

RSpec of course is a Behavior Driven Development framework for Ruby. DRb is a distributed object system for Ruby, and it ships with the standard library.

As a Rails code base gets larger, you can start to see app initialization times increase. In production, this isn’t a big problem, since you aren’t frequently starting your app. However, when you are involved in a rapid development/test cycle this can become a nuisance when you start to find yourself waiting for your app to initialize to run your tests.

Spork aims to tackle this problem by only taking that initialization hit once. It starts up the application environment, and then listens over DRb. Then, RSpec can connect to that DRb server and run specs inside that process, skipping the initialization. Furthermore, before running the specs, Spork will fork itself, which is a relatively inexpensive call on POSIX1 systems. Then, when the specs are done running, this process can just go away, and we don’t have to worry about object pollution when running specs again later.

Let’s see just how easy it is to get going with Spork.

My environment for this tidbit includes:

  • Mac OS X 10.5.7
  • Rails 2.3.2
  • RSpec 1.2.6
  • The Spork I am installing is version 0.5.6

Install Spork from Ruby gems.

  sudo gem install spork

Create (use an existing) Rails project

  rails crappy_spoon

Edit config/environments/test.rb. And add the following lines to tell Rails we are using RSpec.

  config.gem "rspec", :lib => false
  config.gem "rspec-rails", :lib => false

Bootstrap the RSpec environment.

  script/generate rspec

I’m going to use scaffolding just to get some specs to try things out with. (Don’t forget to migrate)

  script/generate rspec_scaffold Fork
  rake db:migrate

I have a little benchmark script that will run a command an number of times and print out the results. Running rake to run these specs 5 times gives:

user system total real
0.000000 0.000000 5.350000 6.364407
0.000000 0.000000 5.300000 5.648598
0.000000 0.000000 5.320000 5.694012
0.000000 0.000000 5.300000 5.610330
0.000000 0.000000 5.350000 5.824451
>total: 0.000000 0.000000 26.620000 29.141798
>avg: 0.000000 0.000000 5.324000 5.828360

So on average on my machine I had to wait 5.8 seconds to run all my specs.

Let’s enable Spork and try it again.

  spork --bootstrap

When that’s done, it prints out a message for us.

Done. Edit /Users/redinger/workspaces/rubyrx/crappy_spoon/spec/spec_helper.rb now with your favorite text editor and follow the instructions.

So, let’s go do that. I moved everything into the prefork section for this example. Here’s my final spec_helper.rb:

  require 'rubygems'
  require 'spork'

  Spork.prefork do
    ENV["RAILS_ENV"] ||= 'test'
    require File.dirname(__FILE__) + "/../config/environment"
    require 'spec/autorun'
    require 'spec/rails'

    Spec::Runner.configure do |config|
      config.use_transactional_fixtures = true
      config.use_instantiated_fixtures  = false
      config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
    end
  end

  Spork.each_run do
  end

Then, edit spec.opts to enable drb:

  spec.opts --drb

Load up spork

 spork 

Rerunning the benchmark:

user system total real
0.000000 0.000000 2.220000 3.100511
0.000000 0.000000 2.210000 3.147859
0.000000 0.010000 2.230000 3.057866
0.000000 0.000000 2.190000 2.919749
0.000000 0.000000 2.190000 2.864102
>total: 0.000000 0.010000 11.040000 15.090087
>avg: 0.000000 0.002000 2.208000 3.018017

And we are now down to an average of 3 seconds. Obviously this is with a bare bones app, so we don’t see the real performance savings that we would with a real world app. But, just for the sake of argument, let’s say someone accidentally added a sleep 5 to the init process (simulating an additional 5 second app initialization time).

Rerunning without Spork running (it’s worth pointing out here if you run rspec without having a Spork process running, the specs will just revert to running the old way, printing out a message notifying you there is no server running.):

user system total real
0.000000 0.000000 6.150000 18.199735
0.000000 0.000000 5.040000 10.131084
0.000000 0.000000 5.040000 10.217521
0.000000 0.000000 5.060000 10.121561
0.000000 0.000000 5.020000 10.150404
>total: 0.000000 0.000000 26.310000 58.820305
>avg: 0.000000 0.000000 5.262000 11.764061

That causes the extra time to be seen for each running of the specs. Under spork, which takes an additional 5 seconds to start up the Spork server:

user system total real
0.000000 0.000000 2.090000 2.722946
0.000000 0.000000 2.080000 2.661724
0.000000 0.000000 2.110000 2.731659
0.000000 0.000000 2.080000 2.665148
0.000000 0.010000 2.100000 2.647866
>total: 0.000000 0.010000 10.460000 13.429343
>avg: 0.000000 0.002000 2.092000 2.685869

No extra time. (Technically, less time, but we’ll count that as margin of error.)

One final thing to point out, if you just run spork -d it will run diagnostic mode, which will list which files are being preloaded, and don’t get reloaded each time the specs are run.

Hopefully if you are using RSpec in your Rails project you can see how using spork can immediately benefit you.

1 Right, I said POSIX, so Windows users will need to pursue a different solution for now.