Testing daemons with Cucumber

Sometimes when writing daemons and doing outside-in testing, you want to fire up the daemon in the background, interact with it, and test the interactions match the behaviour you’re expecting.

The biggest challenge is starting and stopping the daemon reliably - you don’t want daemons hanging around between tests, consuming extra resources, and mucking up state.

Your test might look something like this:

Feature: Some daemon
  As an operator
  I want to run my daemon
  And have it do things

  Scenario: Command line tool
    When I start my daemon with "kelpie start"
    Then a daemon called "kelpie" should be running

  Scenario: Seeing where my daemon is getting its data from
    When I start my daemon with "kelpie start"
    Then I should see "Using data from /.*mysql" on the terminal

I’ve found this problem can be handled quite nicely with with IO.popen and an at_exit callback.

When /^I start my daemon with "([^"]*)"$/ do |cmd|
  @root = Pathname.new(File.dirname(__FILE__)).parent.parent.expand_path
  command = "#{@root.join('bin')}/#{cmd}"

  @pipe = IO.popen(command, "r")
  sleep 2 # so the daemon has a chance to boot

  # clean up the daemon when the tests finish
  at_exit do
    Process.kill("KILL", @pipe.pid)
  end
end

Then /^a daemon called "([^"]*)" should be running$/ do |daemon|
  `ps -eo cmd |grep ^#{daemon}`.size.should > 0
end

The other way to do this is with the usual backtick method, and poke at $?.

command = "kelpie"
output = `#{command}`
pid = $?.pid

at_exit do
  Process.kill("KILL", pid)
end

The issue here is blocking - if the daemon is doing its job, that command won’t return at all, and you certainly won’t see any output from the command.

IO.popen’s main advantage here is that it spawns a subprocess to execute the command, which won’t block Ruby.

So how do you get at the output of the daemon? Easy - we’ve bound the IO.open instance to @pipe, so we can just interact with that.

Then /^I should see "([^"]*)" on the terminal$/ do |string|
  output = @pipe.read(250)
  output.should =~ /#{string}/
end

The above code is fairly naive and you’ll have to tweak just how much data you read, otherwise Ruby will block on reading that pipe.

The last gotcha is that Ruby buffers output to STDOUT by default, so if the daemon you’re testing is also written in Ruby, you may not see anything on that pipe even though the daemon has executed its puts and print statements.

You can disable buffered output by including this statement somewhere in your daemon (I like putting it just after requires):

$stdout.sync = true