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 require
s):
$stdout.sync = true