Continuous Integration w/ Rails

Posted by ryan
at 6:14 PM on Wednesday, May 24, 2006



Rails does many things well, but one of the best is providing a framework for easy and complete testing. However, continuous integration, theoretically a by-product of easy testability, doesn’t seem to be a major player in the Rails landscape yet. One good reason is that there’s not yet a continuous integration application like CruiseControl or AntHill out for ruby. Sure, there have been sightings of such tools like CIA (seems to have been deprecated) and Damage Control, and there is the handy Autotest which kind of gives you a continuous integration feel – but what about real continuous integration?

Well, we do have an option in the form of the continuous_builder plugin. Is it a slick web-app that lets you configure your build process within the comforts of a GUI? No. But, it does automatically run all your tests on every subversion commit and send a nasty-gram calling out the person who broke it, and that’s pretty much what continuous integration is right?

On a side note, I would love to see an open-source CruiseControl for Rails developed and suspect one will be out shortly as the community grows even further

So how does one get the continuous builder plugin up and running? Read on…

Install continuous_builder Plugin

Installing the continuous_builder plugin is a cinch since it’s part of the rails source tree and is a recognized plugin repository.


cd app
script/plugin install continuous_builder

You can also manually install it if the plugin install doesn’t work for you – though I don’t know why it wouldn’t.


cd app
cd vendor/plugins
svn export http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder

Configure Environment & Mailer

The builder plugin runs as a rake task triggered by subversion’s post-commit trigger. However, it needs a few things before it can just work.

The first thing to note about the plugin is that it’s a bit of a dichotomy. When it runs the rake task to perform the tests (all unit, functional and integration tests) it runs under the test environment. That part is easy and expected. However, what you should also know is that the builder runs inside the development environment by default (which is normal for all other rails commands but slightly unintuitive in this particular situation). This means that, if left as is, the builder will pull the action mailer config from the config/environments/development.rb configuration (or config/environment.rb if there’s one configuration for all environments). So to summarize, the builder configuration wil pull from the development environment config and the rake testing task operates in the test environment.

This could be an issue if your development config doesn’t send real emails (your test config probably wouldn’t either since you’d usually be using ActionMailer’s mock delivery to unit test email functionality). What I would suggest doing to keep your builder configuration seperate from your other environments is to create a new build environment config file. This involves

  1. Creating an config/environments/build.rb config file with a working ActionMailer config
  2. Updating config/database.yml to point to a build database (this can be an empty db as all it will do is create an initial connection (though I wouldn’t suggest pointing to the test or production db)

Now we can safely tell the builder to use the “build” environment to get its mailer configuration, and we can leave the other environments untouched.

Test Configuration

Once you have your build configuration setup (or even if you haven’t chosen to do that), you’re going to want to test the builder. This can be done on your local machine, though it requires some little hacks to make the mail function trigger.

The mail functionality will trigger when the build or tests fail and there’s code to update from the repository. To simulate this I created a unit test that always failed, and reverted one small portion of the code to a previous version (using svn update -r _previous revision#_ ). Once you’ve got this simulated failure setup, run the following:


# Omit "RAILS_ENV=build" if you did not set up a seperate environment
# Include the BIN_PATH if your rake executable is not located
# at /usr/local/bin (and include the trailing "/"!)
RAILS_ENV=build BIN_PATH=/usr/bin/ rake -t test_latest_revision \
NAME=app_name RECIPIENTS="dev-list@yourcompany.com" \
SENDER="build@yourcompany.com" 

If all is setup correctly, you will see the standard test output and should see the build status output to the log/last_build.log file. If anything is wrong it should be recognizeable as a build or test error. Fix it and try again :) (and don’t forget to re-setup the fake failure situation described before)

Setup Environment on Build Server

Now that you know your plugin is working, you have to duplicate that setup on the build server – which has to be the same server that subversion is running. Here’s the process I went through:

  1. SSH to the subversion machine as the user that subversion runs as
  2. Make a directory where the builder can check out your source to (I’ve used /tmp/rails/app)
  3. Checkout the code to that directory from the command line – just to make sure you can
  4. Edit the hooks/post-commit file located in your subversion repository directory to look like this (for me this was ~svn/repo/hooks/post-commit)

#!/bin/sh

# Set vars (if necessary)
BUILD_DIR=/tmp/rails/app
BIN_PATH=/usr/bin

# Execute builder (use the same command that worked on your local
machine in the previous test scenario)
cd $BUILD_DIR && \

# Make sure we have the latest and greatest db structure
$BIN_PATH/rake migrate VERSION=0 && \
$BIN_PATH/rake migrate && \

# Run continuous build task!
RAILS_ENV=build BIN_PATH=$BIN_PATH $BIN_PATH/rake -t test_latest_revision \
NAME="App" \
RECIPIENTS="dev-list@yourcompany.com" \
SENDER="build@yourcompany.com" 

Once the post-commit file is in place, you need to do make sure of a few things:

  1. That your post-commit file is owned by the user that the subversion process runs as
  2. That your post-commit file is executable by that user
  3. That the subversion user can checkout code to the build directory (easy as setting that user to be the owner of the build dir)

An easy way to test this setup is to run the post-commit file from the command line. Since it’s executable you should be able to just run ./post-commit. If you’ve recreated the fake failure scenario on the build server as you did on your local machine you should get the nasty-gram email sent to the dev-list@yourcompany.com email address. If not you should at least see the process output.

Test in Production

Now the only thing left to do is to test the builder on an actual commit. This is pretty easy, all you have to do is commit a test that fails. You should see an email pop up in your inbox saying that you wreaked havoc on the build, bringing the scorn of the developers upon you.

Damn – that was a lot. Hopefully you’re now living in continuous build nirvana…

Resources:

http://dev.rubyonrails.org/browser/plugins/continuous_builder

CIA Stuff:

http://wiki.rubyonrails.org/rails/pages/How+To+Use+CIA+For+Continuous+Integration http://article.gmane.org/gmane.comp.lang.ruby.rails.core/790 http://blog.innerewut.de/articles/2005/09/18/setting-up-cia-with-rails-and-subversion

tags: rubyonrails, continuous integration, agile

Comments

Leave a response

  1. Anatol PomozovJuly 07, 2006 @ 03:15 AM
    Hi. you could also try to use Cerberus tool. It does the same as continuous_builder plugin - except that is separate distributed as tool in gem, so you no need to configure for each tool for each project. Ceberus easier in use than other tool called by you.
  2. Ryan DaigleJuly 07, 2006 @ 03:15 AM
    Hey Todd, I guess I'm a little confused as to why overriding @BIN_PATH@ would affect @RAILS_ENV@ at all - it's just the filesystem location to the @rake@ executable? I went back and looked at our running build install and I think the code is right - it does in fact execute the continuous build using the build environment properties - _but_ it actually runs the tests within the test environment... A small but important distinction. Is this the source of our confusion? Either way, I definitely see the need for specifying a different environment completely to run all tests in. I _believe_ that's what your patch addresses? Thanks for the feedback!
  3. ToddJuly 07, 2006 @ 03:15 AM
    Hey, just re-read your documentation above. You *do* use a build environment. Sorry. However, I don't think that code works because you override both RAILS_EVN and BIN_PATH in the code. Correct me if I wrong (or you can be bothered). Cheers once again.
  4. ToddJuly 07, 2006 @ 03:15 AM
    Ryan, Thanks for the code. I have been using this for the development of my new site. I've just got a small patch for you. I hoping you could put through because I want to propset you plugin rather than have it checked-in as I have. Problem: you hard-coded the environment for "test". However, I have created a "build" environment. For me, build is importantly different from test. Test is part of development and is local. So my database.yml points to localhost for test (see database.yml below). Build, however, is my (remote) host (that I ssh into) and also contains my SVN repository. Furthermore, my host doesn't give me localhost access to my (mysql) database (again have a look at database.yml below). Note: I know that I could change my routing via the host file. Yet, I want easy dev/test environment setup and changing the host file makes it difficult for others to have a replica dev environment (which I need). For anyone interested in these issues, keep an eye out in my blog (which will be up soon) at norockets.8wireunlimimted.com I have patched two files: Index: /vendor/plugins/continuous_builder/tasks/test_build.rake =================================================================== --- /vendor/plugins/continuous_builder/tasks/test_build.rake (revision 6) +++ /vendor/plugins/continuous_builder/tasks/test_build.rake (working copy) @@ -10,6 +10,7 @@ notice_options = { :application_name => ENV['NAME'], + :test_env => ENV['RAILS_ENV'] || 'test', :recipients => ENV['RECIPIENTS'], :sender => ENV['SENDER'] || "'Continuous Builder' <cb@example.com>" } Index: /vendor/plugins/continuous_builder/lib/continuous_builder.rb =================================================================== --- /vendor/plugins/continuous_builder/lib/continuous_builder.rb (revision 6) +++ /vendor/plugins/continuous_builder/lib/continuous_builder.rb (working copy) @@ -29,7 +29,7 @@ private def make - @output = `cd #{@options[:application_root]} && #{@options[:bin_path]}rake #{@options[:task_name]} RAILS_ENV=test` + @output = `cd #{@options[:application_root]} && #{@options[:bin_path]}rake #{@options[:task_name]} RAILS_ENV=#{@options[:test_env]}` make_successful? end For reference: ***database.yml*** login: &login adapter: mysql host: mysql.domain.com port: 3306 password: local: &local adapter: mysql host: localhost username: root password: ... test: database: db_tests <<: *local build: database: db_build username: user_build <<: *login
  5. ToddJuly 07, 2006 @ 03:15 AM
    If it is any help, I have also installed it as a cron job on my host using capistrano. I also have pasted in the TASK and the file: ***extract config/deploy.rb**** # ============================================================================= # CONTINUOUS BUILDS IN SVN # ============================================================================= set :svn_continuous_build, "#{home}/svn/#{application}/hooks/post-commit" set :recipients, "dev-list@#{domain}" set :sender, "no-reply-builder@#{domain}" desc "Setup the cron job for backups to remote file storage" task :setup_crontab, :roles => :host do # see: http://wiki.dreamhost.com/index.php/Crontab # make updates to the cron jobs in crontab crontab = render("./lib/server/crontab", :current_release => current_release, :user => user, :svn_continuous_build => svn_continuous_build) put crontab, "#{home}/.crontab", :mode => 0644 begin run "crontab -r" ensure run "crontab #{home}/.crontab" run "crontab -l" end end ***/lib/server/crontab.rhtml**** 0 * * * * /bin/sh <%=svn_continuous_build%>
  6. Pedro BeloJuly 07, 2006 @ 03:15 AM
    pretty cool post, thanks
  7. StephanSeptember 07, 2006 @ 10:23 AM
    Doesn't one need to update the migrate directory from the repository? Add: cd $BUILD_DIRECTORY/rails_project_root || exit -1 cd db # We need to update the migration files svn update cd .. # and then # Make sure we have the latest and greatest db structure $BIN_PATH/rake migrate VERSION=0 && \ $BIN_PATH/rake migrate && \
  8. StephanSeptember 07, 2006 @ 10:25 AM
    Your blogging software didn't like to leave the lines as they appear in the shell script. After "We need to update the migration files" svn update was supposed to appear on a separate line.
  9. Fyodor GolosJanuary 04, 2007 @ 05:49 AM
    Stephan, shouldn't "rake migrate" commands include RAILS_ENV="test", since continuous_builder uses test environment for running tests?