Rocksteady
Often you need to test bits of code you write with bits of code other people write, across varying revisions. This is tedious, requiring a lot of custom work and infrastructure, so you probably don’t do it enough. I know I don’t, and it’s a bad habit.
Rocksteady is meant to ease the process of switching out different versions of inter-dependent code, providing you with a simple scenario metaphor to write build and test code within. It’s test framework agnostic (it just uses exit codes), and extremely flexible.
Below you can read an example of testing different versions of Rails against different versions of a plugin; more in-depth examples can be found on the wiki (github.com/bruce/rocksteady/wikis/examples).
Please let me know how you’re using Rocksteady; it’s nice to have feedback.
Bruce Williams (codefluency.com) github.com/bruce
Installation
Get it from RubyForge (once released):
sudo gem install rocksteady
Dependencies
-
rake
sudo gem install rake
-
ruport
sudo gem install ruport
-
mojombo-grit >= 0.8.0
sudo gem install mojombo-grit -s http://gems.github.com
-
(git in your PATH)
Defining Scenarios
Create a new directory, and toss a Rakefile
in it. Make it look like this:
require 'rubygems'
require 'rocksteady'
Now, point it at the git repos that are holding the code you’d like to test. You could clone
the repos somewhere in this directory, if you’d like. For this example, we’ll be testing a Rails plugin we’ve written against various versions of Rails, so it like look something like this (assuming we cloned bare .git
repos into the current directory)
repos 'rails.git', 'our_plugin.git'
Okay, now let just create a single scenario. In general, a scenario will be some set of configuration you’d like to test. For this example we won’t be doing anything fancy (just a simple drop in vendor/plugins
) so it might look like this:
scenario "Installed in vendor/plugins" do
generate_rails_app
install_plugin
verify_loads_environment
end
You see 3 things need to happen in the scenario; we’ve broken these out for clarity and just define them underneath:
First, let’s generate a fresh Rails app for testing with:
def generate_rails_app
ruby "#{rails_path}/railties/bin/rails rails_app"
end
A few notes on what you see above:
-
ruby
is simply arake
convenience method that calls out to a newruby
interpreter -
rails_path
is the absolute path to a fresh clone of therails.git
repo we defined at the beginning of the file, checked-out to the reference point we’re currently checking (more on how that’s set later).*_path
convenience methods are generated for all the repos you’ve defined; you’ll see the other one ininstall_plugin
. -
We’re calling out to the
rails
executable in the checked-out source directly so we can generate an accurate skeleton app for that version. Therails_app
argument is just the name of the directory where it will be generated.
It’s worth noting that when scenarios are being run, the current working directory is changed for you automatically; right now you’re actually sitting in build/<timestamp>/scenarios/installed_in_vendor_plugins
, where rails_app
will be generated as a subdirectory.
Let’s install the plugin now:
def install_plugin
cp_r our_plugin_path, 'rails_app/vendor/plugins'
end
-
cp_r
is anotherrake
convenience method that does a recursive copy. -
our_plugin_path
is the*_path
method generated automatically for our plugin repo.
So, that was simple; for this quick example we just copy the plugin directly in.
Now let’s do something that just verifies the Rails app code will load successfully across the standard environments, just by printing the Rails version from script/runner
:
def verify_loads_environment
Dir.chdir 'rails_app' do
%w(test development production).each do |env|
ENV['RAILS_ENV'] = env
ruby "script/runner 'p Rails::VERSION'"
end
end
end
The great thing about the convenience methods that rake
provides (eg, ruby
, sh
, cp_r
, etc) is that the raise an exception if the system call they make returns a bad exit code; the scenario automatically catches them and assigns a failure to the scenario. If you’re not using these convenience methods and need to assign a failure, you can raise an exception manually to get the same result.
Running against arbitrary references
Here are some examples on how we could run the scenario, with plain English explanations:
Run all scenarios, with all repos set to master
:
$ rake rocksteady
Run just the first scenario (you can use rake -T
to see them all), with all repos set to master
:
$ rake rocksteady:scenario:1
The same, but setting rails
to v2.0.2
:
$ rake rocksteady:scenario:1 REFS=rails:v2.0.2
and now with our_plugin
set to the experimental
branch:
$ rake rocksteady:scenario:1 REFS=rails:v2.0.2,our_plugin:experimental
You can also use full references:
$ rake rocksteady:scenario:1 REFS=rails:refs/tags/v2.0.2,our_plugin:refs/heads/experimental
Output
Currently the output is a simple text-based table. You’ll need to refer to the output in the terminal to find where your failures occur. More granular, labelled scenario-level logging is on the roadmap.
License
Copyright (c) 2008 Bruce R. Williams
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.