Testing your gem against multiple rubies and rails versions with RVM

June 5, 2011 Pivotal Labs

I recently wanted to make it easier for contributors to ActiveHash to test their changes against multiple versions of Rails, with multiple versions of Ruby. My stories looked like this:

As a contributor
I want to be able to run `bundle install`, then quickly run the suite spec suite against the latest released version of rails
So that I can develop quickly using a familiar workflow

As a gem maintainer
I want to be able to run the spec suite against 3 different versions of ruby, each with 5 different versions of rails
So that I can release the gem with confidence that I'm not going to break people's apps

In this post I’ll explain how I did that with a (relatively) simple shell script.

The final script looks like this:

#!/bin/sh

set -e

if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
  source "$HOME/.rvm/scripts/rvm"
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
  source "/usr/local/rvm/scripts/rvm"
else
  printf "ERROR: An RVM installation was not found.n"
fi

function run {
  gem list --local bundler | grep bundler || gem install bundler --no-ri --no-rdoc

  echo 'Running bundle exec rspec spec against activesupport / activerecord 2.3.2...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=2.3.2 bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against activesupport / activerecord 2.3.5...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=2.3.5 bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against activesupport / activerecord 2.3.11...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=2.3.11 bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against the latest released version of activesupport / activerecord...'
  ACTIVE_HASH_ACTIVERECORD_VERSION="" bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against edge activesupport / activerecord...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=edge bundle update activerecord activesupport
  bundle exec rspec spec
}

rvm use ruby-1.8.7@active_hash --create
run

rvm use ree-1.8.7@active_hash --create
run

rvm use ruby-1.9.2@active_hash --create
run

echo 'Success!'

Here’s a breakdown of what’s happening:

First the script tells the shell to exit immediately if any of the commands in the script fail:

set -e

This is helpful for continuous integration servers especially, so they fail fast. Next, the script loads RVM, looking first in the home directory, then for a system install:

if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
  source "$HOME/.rvm/scripts/rvm"
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
  source "/usr/local/rvm/scripts/rvm"
else
  printf "ERROR: An RVM installation was not found.n"
fi

The next section defines a function which, when invoked, runs the spec suite against 5 different versions of activerecord/activesupport:

function run {
  gem list --local bundler | grep bundler || gem install bundler --no-ri --no-rdoc

  echo 'Running bundle exec rspec spec against activesupport / activerecord 2.3.2...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=2.3.2 bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against activesupport / activerecord 2.3.5...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=2.3.5 bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against activesupport / activerecord 2.3.11...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=2.3.11 bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against the latest released version of activesupport / activerecord...'
  ACTIVE_HASH_ACTIVERECORD_VERSION="" bundle update activerecord
  bundle exec rspec spec

  echo 'Running bundle exec rspec spec against edge activesupport / activerecord...'
  ACTIVE_HASH_ACTIVERECORD_VERSION=edge bundle update activerecord activesupport
  bundle exec rspec spec
}

(I know, it’s not particularly DRY – pulling out the duplication into other functions is an exercise for the reader…)

This function sets an environment variable to describe the intended gem versions, then updates bundler and runs the suite. To make this work, I added the following to my Gemfile:

source :gemcutter
gemspec

group :development do
  activerecord_version = ENV['ACTIVE_HASH_ACTIVERECORD_VERSION']

  if activerecord_version == "edge"
    git "git://github.com/rails/rails.git" do
      gem "activerecord"
      gem "activesupport"
    end
  elsif activerecord_version && activerecord_version.strip != ""
    gem "activerecord", activerecord_version
  else
    gem "activerecord"
  end
  # other gems...
end

The Gemfile checks the ACTIVE_HASH_ACTIVERECORD_VERSION environment variable, and sets the correct gem version/git repo accordingly. If it gets a blank ACTIVE_HASH_ACTIVERECORD_VERSION it does not set the version in the Gemfile, so when the bash script calls bundle update activerecord it conveniently fetches the latest released version.

If you execute bundle update, it won’t have a ACTIVE_HASH_ACTIVERECORD_VERSION so it will fetch the latest, so the normal workflow still works as expected.

Finally, it runs the function in each of 3 rubies:

rvm use ruby-1.8.7@active_hash --create
run

rvm use ree-1.8.7@active_hash --create
run

rvm use ruby-1.9.2@active_hash --create
run

echo 'Success!'

RVM allows you to run a command with all of your installed rubies, but in this case I wanted to target 3 particular versions, and ensure that they work. RVM will fail if you don’t have the latest version of each ruby installed, and prompt you to install it. And since set -e was called at the top of the file, there’s no need to check the return code of any of the functions because the script will fail if any individual command fails.

If you are a gem maintainer, this is an easy way to ensure that your gem is highly compatible with standard Ruby / Rails setups.

About the Author

Biography

More Content by Pivotal Labs
Previous
New in Pivotal Tracker: Stay signed in across browsers, all-HTTPS, 3rd party tools RSS feed
New in Pivotal Tracker: Stay signed in across browsers, all-HTTPS, 3rd party tools RSS feed

As we blogged about last week, all pages in Tracker that require you to sign in are now served exclusively ...

Next
More details on Pivotal Tracker going (almost) all SSL
More details on Pivotal Tracker going (almost) all SSL

As Dan blogged last Tuesday, the next release to Pivotal Tracker is going to ensure that all project pages ...