Experiments in metaprogramming

I’m having a go at metaprogramming in ruby. It started out simply enough. I wanted a module which had some custom assert methods in. Here’s the test:

  def test_matches
    assert_stderr_matches(/foo/) do
      warn "foo"
    end
  end

And here are the custom assert methods.

  module AssertStderr
    def assert_stderr_matches(regex, message=nil)
      old_stderr = $stderr
      $stderr = StringIO.new
      yield
      assert_match(regex, $stderr.string, message)
    ensure
      $stderr = old_stderr
    end

    def assert_stderr_empty(message=nil)
      # More-or-less identical.
    end
  end

Two methods, the bulk of which is similar. Yuck. Refactoring time.

  module AssertStderr
    def assert_stderr_matches(regex, message=nil, &block)
      assert_match(regex, capturing_stderr(block), message)
    end

    def assert_stderr_empty(message=nil, &block)
      assert_equal("", capturing_stderr(block))
    end

    def capturing_stderr(block)
      old_stderr = $stderr
      $stderr = StringIO.new
      block.call
      return $stderr.string
    ensure
      $stderr = old_stderr
    end
  end

That’s better, but it set me wondering how far I could go down this route. What about creating an assert_stderr builder? This is what I’ve come up with so far.

  def self.assert_builder(what, &check_block)
    define_method("assert_stderr_#{what}") do |*args|
      begin
        old_stderr = $stderr
        $stderr = StringIO.new
        yield
        check_block.call($stderr.string, *args)
      ensure
        $stderr = old_stderr
      end
    end
  end

  assert_builder :matches do |stderr,regex,message|
    assert_match(regex, stderr, message)
  end

  assert_builder :empty do |stderr,message|
    assert_equal("", stderr, message)
  end

It looks reasonable, but it doesn’t work. It’s meant to create a method which takes a block and executes it. However, careful debugging (read: p) shows that the yield is actually calling the check_block parameter instead of the block that’s provided when the assert_stderr_matches method is called. At this point, I have no idea why this is happening. I’m very confused.

It doesn’t help that you can’t really google for “ruby blocks that take blocks” and get a sensible answer.

Maybe I just need to nose around the Rails source long enough. There are examples of most forms of Ruby magic hanging around in there…

This entry was posted in Uncategorized and tagged . Bookmark the permalink.

Comments are closed.