Specjour with Custom Bundler and Database Setup

June 25th, 2010, by Aaron

We have a test suite here that now is rapidly approaching 2 hours using a single core. Let me just repeat that. A developer realistically would have to leave their machine testing overnight to see if the suite is working. That’s really not good enough.

Specjour has been a bit of a turn-key miracle worker with our RSpec suite, however lately we’ve started to require some custom database setup that we do in a seeds.rb file as well as some custom bundler install parameters as most of our devs don’t have MySQL installed. Both of our needs were being nicely stomped on by Specjour so I thought it was time to look elsewhere.

I took a trip down Hydra lane and while getting to the point of having a working, local, dual runner system was a piece of cake, getting something working remotely via SSH took me hours of pain. Debugging the remote SSH workers was a nightmare and I spent a couple of hours running through code before deciding it was probably better to update our existing solution rather than tooling up a brand new one.

Back to the Specjour code. Specjour includes a rails directory inside which is an init.rb which Rails will run at initialisation (it’s part of what Rails does) but the Specjour initialiser will always just run the default database setup task no matter what initialiser you’ve got setup. We had a specjour initialiser that runs if ENV['PREPARE_DB'] was populated, which it is by Specjour, the problem was that the Specjour initialiser ran in the Rails after_initialization hook and therefore stomped all over our database setup.

The first step was just to have our initialiser write to another ENV element and then to have the Specjour after_initialize handler respect this. This isn’t too hard to implement as the after_initialize handler is just a block that is attached and so inside of this block you just need to check that ENV element. In my case I created a new ENV['DB_PREPPED'] element when my database setup had completed and then when the after_initialize block runs it checks for ENV['DB_PREPPED'] and will do nothing if that’s been set to true.

Easy. I now had Specjour respecting our database setup task.

The next step was to try and test this outside of a Rails application, not only that but to test the operation of a block (anonymous function?). To do this I setup a stub on a mock Rails class and let it capture the after_initialize block and then I ran a number of specs against this block.

module Specjour
  module DbScrub
  end
end

DO_NOT_REQUIRE = true

describe "Rails Initialiser" do
  before :all do
    ENV['PREPARE_DB'] = "true"

    stub(Specjour::DbScrub).scrub

    class Rails
      class << self; attr_accessor :configuration; end
      class << self; attr_accessor :test_block; end
    end

    config = Object.new
    stub(config).after_initialize { |args|
      object = Object.new
      Rails.test_block = args
      object
    }
    Rails.configuration = config

    require 'rails/init'
  end

... tests ...

This code essentially mocks up Rails.configuration and then stubs the after_initialize method. This stub then places the block that after_initialize yields to into Rails.test_block. When I require 'rails/init' it sequentially processes the file (as with all Ruby) and the stub will capture the block. After this is a bunch of tests I run an whether the Specjour::DbScrub.scrub method is called or not, so it’s nothing special.

I felt like at this stage I had fairly well tested the main aspects of the database setup.

The next issue was with how bundler was being handled. We have a situation where we would like to install sometimes without some gems. Some of the gems we use and have written use applications we’d rather not maintain in development and get tested in our staging and production environments. We generally will run a bundle install in development without the production or metrics groups so I wanted to have the ability to pass through a custom bundler command. That’s pretty easy now with my gem. Inside .specjour/bundler.yml there is a command property. I think this is more complex than what’s required, but I can foresee us needing a number of custom rake tasks and shell scripts so this bundler.yml should have probably started life as a settings/commands/something_generic.yml

To test this part of my changes was pretty simple. I basically just stubbed the system calls to bundler to give certain return values and checked to make sure the correct program flow happened.

describe ".bundle_install" do
    let :manager do
      stub.instance_of(Specjour::Manager).project_path { "/tmp" }

      stub(Dir).chdir(anything) { |args|
        args.last.call # This yields to the block for Dir.chdir()
      }

      manager = Specjour::Manager.new
      stub(manager).project_path { "blah" }
      mock(manager).system('bundle lock')

      manager
    end

    it "should perform a bundle lock" do
      stub(manager).system('bundle check > /dev/null') { true }

      manager.bundle_install
    end

    it "should check if there are gems required" do
      mock(manager).system('bundle check > /dev/null') { true }

      manager.bundle_install
    end

    context "when gems are required" do
      before :each do
        # Not a before :all as it needs to hook into the let hook above

        stub(manager).system('bundle check > /dev/null') { false }
      end

      context "and there is a bundler YAML file" do
        before :each do
          config_file = ".specjour/bundler.yml"

          mock(File).exists?(config_file) { true }
          mock(File).read(config_file) { "" }
          mock(YAML).load(anything) {
            { 'command' => "do it" }
          }
        end

        it "should get the bundle command from the YAML file" do
          mock(manager).system('do it > /dev/null')
          manager.bundle_install
        end
      end

      context "and there is no bundler YAML file" do
        before :each do
          mock(File).exists?(".specjour/bundler.yml") { false }
        end

        it "should perform a bundle install" do
          mock(manager).system('bundle install > /dev/null')
          manager.bundle_install
        end
      end
    end
  end

You can see that I stubbed our the Dir.chdir block to just yield directly to the call, otherwise it’ll throw an exception. Then I stubbed and mocked out the Kernel.system calls as necessary. Kernel methods are generally included into Ruby objects so you don’t stub Kernel, you stub the object that has the Kernel methods. Most of the testing is pretty basic, but I’d be keen to hear if I’m doing anything incorrectly!

This was my first major venture into adding functionality to a public project and it was good fun. I think it made me do a little better work than I might normally, it’s a great motivation to potentially have peers look at how you do things.

After bundling it all up and testing it here with over a dozen developers and even more machines I’m pretty happy with how it functions. I’ve made a pull request back to the original gem creator and hopefully he’ll like what I’ve done. In the meantime if you want to check it out then my Specjour is available on Git Hub.

We are a Perth web design and web development company and this is our blog. We specialize in building web applications with the Ruby on Rails framework. Jump to the Ruby on Rails category or contact us.


Parallel RSpec Performance Testing

June 23rd, 2010, by Aaron

We’re currently deciding on hardware to build a bit of a testing cluster on which we’ll run whatever the current best remote testing package we can find. At the moment, for us, that’s ended up being Specjour. We ended up pitting one of our i7 iMacs against a $400 Acer box we bought. We installed Ubuntu’s REE 1.8.7 on the Acer machine and RVM and REE 1.8.7 on the iMac.

i7 iMac – Quad Core Hyperthreading

real 11m14.131s
user 0m0.776s
sys 0m0.348s

Acer – E5200 Dual Core E5200

real 35m56.658s
user 0m0.870s
sys 0m2.500s

It seems like the little Acer box offers slightly better value for money in this case. I’d like to see how we’d do with some virtualisation sitting on top this, but I think the included memory will be quite limiting in this regard. I wonder whether the processor resources are actually being fully utilised.

We are a Perth web design and web development company and this is our blog. We specialize in building web applications with the Ruby on Rails framework. Jump to the Ruby on Rails category or contact us.


Named Scopes with Joins and Default Block Arguments

May 4th, 2010, by Aaron

Today I was fiddling with some code to get particular types of payments that are due on particular days and I ran across a couple of things I don’t want to solve again.

Firstly, the problem of being able to have default arguments to a block in ruby. It’s solved nicely in Ruby 1.9 but we’re using 1.8.x on our boxes at the moment. The work around is incredibly simple though and goes something like this :


lambda { |*args|
date = (args[0] || Date.today)
.. remaining code ..
}

That’s all there is to it really. You could go the whole hog with hashed attributes and so on but I think it starts to get a bit smelly if your anonymous functions are taking more than one argument.

The other issue I had was whether a named scope can include a join, and it can.


named_scope :credit_card, { :joins => :subscription, :conditions => "subscriptions.method = 'credit_card'" }

You can hash it all out if you want to, though if it’s all about readability I find the above to be more suitable. However this will also work :


... :conditions { :payment_plans => { :payment_method => "credit_card" } }

The coolest thing about all of this though (and I feel I’m very late to the party here) is that I now get to do things like :


Payment.credit_card.due_on(Date.today)
# or
Payment.credit_card.due_on
# or
Payment.due_on(Date.today + 1.month)

In the above I had just used the default argument to the due_on named scope to be today’s date.

I mean, I’d seen a bunch of tutorials on named scopes and how they worked but hadn’t found a use case in my work. I think it’s finally twigged for me though and will be making use of them a bit more in the future.

We are a Perth web design and web development company and this is our blog. We specialize in building web applications with the Ruby on Rails framework. Jump to the Ruby on Rails category or contact us.


Running RSpec in Distributed and Parallel using Specjour

April 23rd, 2010, by Aaron

Just to cut to the chase, Specjour will easily allow you to run your tests in parallel on your local machine, or in a distributed way on your network. It cut our test suite from over 11 minutes to under 2.5 minutes. We went from one Macbook Pro to a Macbook Pro plus an iMac. Specjour is gooood, but continuing …

We were running into issues with our testing suite running past 15 minutes to run. It caused a number of issues, firstly it disuaded people from running the suite for small changes they thought would be okay, secondly people tended to slow down or not work at all when the suite was running. Given we were building tests into an existing code base and coverage was running generally under 30% this was already really worrying.

We’ve been concentrating primarily on functional testing with RSpec so I went looking for solutions for trying to parallelise RSpec specifically. I tried parallel-tests but I couldn’t get it to work. Then I heard about Specjour on a Hashrocket video so I decided to give that a try. It worked pretty well out of the box, I just had to plug up a wayward plugin, update the rsync config file for the project and change one of the files in the gem to call a different rake task.

Specjour essentially uses Bonjour to find workers on the network and dispatch tests to them and it uses rsync to synchronise the project you’re testing to the remote server. Once the sync has happened then runs the rake task to setup the databases, loads the environment and starts accepting the tests that are dispatched to it.

The instructions will work for the majority of people, I only had some hiccups due to our project. Essentially I gem installed specjour which gave me a specjour executable which I ran to create a dRb server that communicates via Bonjour. Then in my project I added specjour to my Gemfile (all managed via Bundler!) and ran rake specjour.

For me, I ended up having a few hundred failing tests, all to do with the Ruby version of a stack overflow (stack level too deep). It turned out it was caused by a plugin calling alias method chain twice so that the original method ended up being aliased to the replacement method and so there was an infinite loop. Once I applied a fix then we were only down to a few failing tests to do with seed data.

I changed the db_scrub.rb file to perform a different rake task for us as we do require some seed data and then we were on our way.

Previously on my Macbook Pro the suite ran in just over 11 minutes, after turning on a four core iMac and my Macbook Pro I had six workers ready to go and it took about 2.5 minutes to run our tests. I’d really like to crank up the other few iMacs and the 10 or so Macbooks and see what happens then.

I think the only changes we’ll require is to be able to specify which task is run to create the databases, other than that it works perfectly.

In closing I’d say I tried a number of distributed and parallel methods for running our tests and I like Specjour the best. Apparently it will run Cucumber tests as well, I’m hoping to move on to that soon enough.

We are a Perth web design and web development company and this is our blog. We specialize in building web applications with the Ruby on Rails framework. Jump to the Ruby on Rails category or contact us.


Setting Up an iMac Pairing Station for Rails Development

April 22nd, 2010, by Aaron

I used this post today as a guide on how to get another iMac up and running so I thought it was probably a good point to chuck this up here, if only for our own reference.

We are starting with a 27″ iMac and a base Snow Leopard install.

The first step was to install Xcode from the Snow Leopard disc, you’ll find it in the Optional Installs part of the disc.

Ruby and RubyGems comes with Snow Leopard by default so we’ll use them. You’ll likely need to update the RubyGems system :

    sudo gem update --system

Now we can install Rails :

  sudo gem install rails

This will install the latest stable version of Rails. We need a specific version for some of our applications (until we’ve tested it under the newer version) and you can do this by adding the -v switch :

  sudo gem install rails -v=2.3.4

After this we can switch on Apache and install passenger, the module that runs Rails on Apache. So turn on Web Sharing in the Sharing panel of your System Preferences.

  sudo gem install passenger
  sudo passenger-install-apache2-module

This will compile the Apache module and give you some text to paste into your apache conf file to load it. I keep this in a separate config file in /etc/apache2/other/passenger.conf. It should look something like :

  LoadModule passenger_module /Library/Ruby/Gems/1.8/gems/passenger-2.2.8/ext/apache2/mod_passenger.so
  PassengerRoot /Library/Ruby/Gems/1.8/gems/passenger-2.2.8
  PassengerRuby /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby

Next download the Passenger Preference Pane so you can control your Rails sites from the OS X Preferences area. You can get it from http://www.fngtps.com/passenger-preference-pane. You should now be able to run Rails websites on your local machine very easily.

We use Git for our source control and Git X is the best GUI for this on OS X at the moment. You can download Git for OS X from http://code.google.com/p/git-osx-installer/ and you can get GitX from http://gitx.frim.nl/.

I like to use a Pomodoro timer when pairing and the one I like the best for OS X is just called Pomodoro Desktop. It will keep people focussed and also can serve to keep the control swapping in a fairly regular way. You can find out about how Pomodoro works here.

We use Github for our repository and exchange is done via SSH, so you’ll need to generate an SSH key and put that into your Github account.

  ssh-keygen
  cat ~/.ssh/id_rsa.pub

Copy the output and put that into your public SSH keys in Git Hub.

When pairing with Git the ability to have both parties responsible for the work they’re doing is appreciated sometimes. Especially twelve months from now when you can’t remember why you did something. Not that you don’t comment your code, or always write beautifully understandable code. There is a nice gem called hitch. It comes out of Hashrocket and handles the ‘hitching’ and ‘unhitching’ of partners on a machine. We have a pairing station so generally we just want to change the pair that’s in operation, so it works well. You’ll need to be inside a git repository to do the initial setup. I cloned one of our projects and then you issue :

  hitch -m

Which will prompt you for your main email address, this is the email address that all devs in your team receive, or a group email of some sort.

Now to hitch two devs you’d do :

  hitch dev1 dev2

This will prompt you for names of the dev1 and dev2 users so that they’ll be displayed in your commit. It’ll also associate your commits to the email address devs+dev1+dev2@company.com if you setup devs@company.com as the email address when you issued the hitch -m command. If you setup gravatars for all your devs+user1+user2@company.com email addresses then you’ll get a nice picture in Github too.

I think that’s about it really. We obviously just install gems as required and make customisations on a per developer need. We generally use Textmate for editing so this means installing a theme (we use the Railscast theme) and the relevant bundles for Rspec, Haml and Sass.

We are a Perth web design and web development company and this is our blog. We specialize in building web applications with the Ruby on Rails framework. Jump to the Ruby on Rails category or contact us.


Follow Us

Stay in the Loop

  • Enter your email address to subscribe to our mailing list. You'll get updates about our products, specials and bonus offers, and general behind the scenes news from our team.

Twitter

Facebook Fans

Newsletters

Testimonial

The boys at The Frontier Group are amazing! For such a relaxed and personable organisation, they have phenomenal technical ability and a rampant professionalism. They have customisable solutions for all of my IT needs and they always deliver, on time and beyond expectation.

They fix problems other service providers can't and they helped me get a critical section of my web site up and running 10 minutes after I emailed the request!

Alex Hyndman, Nexus Car Share.

Featured Project

Case Study - Caudo Group - www.caudo.com.au

Website

www.caudo.com.au

Caudo Machinery

Caudo Group engaged our services to redesign their outdated website. We sent our photographer on-site to capture the essence of their business and turned it into a stunning web design.