EM::Ventually
Background
Your tests will eventually pass. You're not sure when, but you know it'll be quick. This is where EM::Ventually can help you!
Take this trivial example. (It's in Minitest, but you can use whatever test suite you'd like*) Without em-ventually, testing your EM code requires a lot of boilerplate such as:
# Without em-ventually
def test_em
# Start EM loop.
EM.run do
# The only two lines you actually care about.
val = nil
EM.add_timer(0.5) { val = 'test' }
# Test that work happens in the future.
EM.add_periodic_timer(0.1) do
if val == 'test'
pass # Increment assertion count.
EM.stop # Manually stop EM loop.
end
end
# Guard against infinite loops.
EM.add_timer(1) { raise "Potential infinite loop averted!" }
end
end
So, you want to test that val is 'test', and as well, you want EM to start on the beginning of your test and shutdown when it's done. The other thing you want is the ability to not let this test run forever, but to shutdown EM and consider it a failure if it doesn't complete.
EM::Ventually is here to help!
EM::Ventually
will take care to start and stop EventMachine, and make sure your tests run serially (unless you don't want them to). It will also prevent your tests from running forever.
Taking the example above you can enqueue a test.
def test_em
val = 'not test'
EM.add_timer(0.5) { val = 'test' }
eventually('test') { val }
end
This will poll the block passed to eventually every 0.1 seconds to see if the condition is equal to the argument you passed in to eventually, in this case 'test'. After one second, if it's still not equal, it will fail. If you do not pass in a value for testing, it will evaluate what you return against ruby's notion of truth (not nil and not false). An exmaple:
def test_em
count = 0
EM.add_periodic_timer(0.01) { count += 0.5 }
eventually { count >= 3 }
end
As well, you can change both the polling interval as well as the maximum execution time by passing in :every
and :total
. Here is an example:
def test_em
val = nil
EM.add_timer(0.5) { val = 'test' }
eventually('test', :every => 0.2, :total => 10) { val } # check every 0.2 seconds
# for a total of 10 seconds.
end
You can also parallelize tests if you don't want them to run serially.
def test_em
val1, val2 = nil, nil
EM.add_timer(0.5) { val1 = 'test1' }
EM.add_timer(0.5) { val2 = 'test2' }
parallel do
eventually('test1') { val1 }
eventually('test2') { val2 }
end
end
Simply returning from your block doesn't always cover your test. Say for instance, you need to do a call against some sort of async client that will return your value in a callback. You can pass values back for equality by doing the following.
class AsyncClient
def status(&blk)
EM.add_timer(0.5) do
blk.call(:ok)
end
end
end
def test_em
client = AsyncClient.new
eventually(:ok) { |with| client.status {|status| with[status]} }
# The secret sauce is in the arity of the block passed to `eventually`
end
Usage
Right now, Test::Unit
, RSpec
and MiniTest
are supported. Just do include EM::Ventually
in your test and you'll get all this for free. If you want to use this in all your tests, you can require 'em-ventually/{minitest,unittest,rspec}'
your appropriate test suite.
There are a couple of global options you can mess with too. EM::Ventually.every_default=
and EM::Ventually.total_default=
can both set global :every
and :total
times for your tests.
If you don't pass a value to eventually, it will test that your value is true (in the ruby sense). Optionally, you can call .test
to pass a custom tester.
def test_em
count = 0
EM.add_periodic_timer(0.01) { count += 0.5 }
eventually { count }.test{ |v| v >= 3 }
end
of course, you're gonna be writing so many of these we've aliased it to make your tests stylish and classy.
def test_em
count = 0
EM.add_periodic_timer(0.01) { count += 0.5 }
ly { count }.test{ |v| v >= 3 }
end
If you want to manually manage stopping and starting EM within a test, you can call manually_stop_em!
within your test. An example:
def test_em
manually_stop_em!
EM.add_timer(0.5) { assert "Hey!"; EM.stop }
end