Raking and Testing with EventMachine

Posted on by in Development, Process

I have been getting more and more interested in high-performance Ruby apps, and in EventMachine in particular. First of all, super props to Aman Gupta for EM, and to some other Ruby devs out there who have been writing libraries and drivers on top if it, such as Ilya Grigorik, and Carbon Five’s own Mike Perham.

However one area that has not gotten a lot of attention within the EventMachine world is that of testing and tools support. It would be ideal for evented codebases if all tests and all rakes were automatically run inside an EventMachine reactor. I realize that many of the EM-enabled libraries out there, like mysql2, work whether they are in a reactor or not, so this may seem unnecessary. But this means that your tests are exercising a different code path than your production app, which is a bad idea.

So how can we get our tools running within EventMachine?

Monkey-patching to the rescue

I am using rake, rspec and cucumber in an EM-enabled project, and I monkey-patched each of these gems within my project to run inside a reactor. The strategy for each gem is very similar: I look for the first method called within the gem in question that is executed after the project files have been loaded, and I override it, wrapping it in a reactor.

Let’s take a look at how this works for Rake:

Rake

lib/tasks/em.rake

module Rake
  class Application

    alias_method :top_level_alias, :top_level

    def top_level
      EM.synchrony do
        top_level_alias
        EM.stop
      end
    end

  end
end

In this case, I override Rake::Application.top_level. I first alias it to top_level_alias, and then I wrap a call to that aliased method with a call to EM.synchrony.[1] And of course I stop the reactor in the end of my block.

For RSpec, the code is a little more complex, but the idea is the same.

RSpec

spec/spec_helper.rb

module RSpec
  module Core
    class ExampleGroup

      class << self
        alias_method :run_alias, :run

        def run(reporter)
          if EM.reactor_running?
            run_alias reporter
          else
            out = nil
            EM.synchrony do
              out = run_alias reporter
              EM.stop
            end
            out
          end
        end
      end

    end
  end
end

I would like to call a method that wraps the entire test suite, but the best I can do is a method wrapping a single example group. This means that I start and stop a reactor for each spec file in my project. This is not ideal, but it works just fine.

Also notice that I check whether or not the reactor is running. Because of the recursive way RSpec works, we are often already in a reactor loop when we wind up calling this method.

Cucumber is straightforward, just like Rake.

Cucumber

features/support/em.rb

module Cucumber
  module Ast
    class TreeWalker
      alias_method :visit_features_alias, :visit_features

      def visit_features(features)
        EM.synchrony do
          visit_features_alias features
          EM.stop
        end
      end
    end
  end
end

Conclusion

My hope is that each of these gems (and the other similar ones out there) will add the ability to run themselves inside an EM reactor. I see this as a configuration option, much the way ‘-drb’ is used by many gems to enable Spork. It is my plan to fork these gems and implement it myself, so the gem owners out there should expect a pull request some time soon.

Footnotes

[1] If you don’t already know, EM.synchrony is part of Ilya’s excellent em-synchrony gem. It elegantly starts a reactor within a Ruby Fiber (which also means you need to be using Ruby 1.9).