Hair tearing
Monday, November 17th, 2008I’ve been struggling with a bug all day, and I’m stuck, so it’s time to blog.
There’s a wonderful set of matchers in Rspec on Rails that lets you do something like this:
describe "some html thing" do code = '<div><a href="wherever">link</a>, <a href="somewhere">another</a></div>' code.should have_tag('div') do with_tag('a', 'link') with_tag('a', 'another') end end
Basically, since HTML is nested, it’s really nice to be able to nest your matchers.
Unfortunately, has_tag seems to be the only matcher that works like this. I am writing some code for Forkolator that generates god files, and I find myself wanting to do something like this:
it "should have a proper mongrel startup command" do
code.should match /"mongrel_rails start[^"]*"/ do
with_match /-c #\{RAILS_ROOT\}/
with_match /-p #\{PORT\}/
with_match /-d/
end
endAlas, the match method cannot do this. In the interest of writing clean specs and in learning as much as possible about advanced Ruby features, I decided to write it myself.
Unfortunately, looking at the code for has_tag didn’t help much. It’s written as a wrapper of the AssertSelect class, and it seemed like yak shaving to dive in to that.
So I started from scratch. You can my code and my spec so far.
Here’s where it gets weird. The examples all pass. Or they appear to, anyway. I get all nice green dots, 0 failures. If you look closely though, you’ll see that only 5 examples are running, even though I count 8 shoulds in my spec.
Weird, huh? Turns out, the matchers that actually use the additional block (the whole point of this crazy project) don’t ever return to their examples. Rspec seems to just move on along to the next one. Now why would that be?
I broke out my ruby debugger, and started stepping through the tests to see where the problem might be, and lo and behold right when MatchNGo::matches? is about to return on line 37, The debugger throws up an error:
/var/forkolator/apps/forkolator/vendor/plugins/rspec/bin/spec:4Tracing(1):/var/forkolator/apps/forkolator/vendor/plugins/rspec/lib/spec/expectations/extensions/object.rb:31 ExpectationMatcherHandler.handle_matcher(self, matcher, &block)
INTERNAL ERROR!!! (eval):1:in `[]‘: can’t convert String into Integer
/usr/lib/ruby/gems/1.8/gems/ruby-debug-base-0.10.2/lib/ruby-debug-base.rb:31:in `eval’
I’m really not sure what to make of this. Is it a bug in the debugger? Could it be the reason my code is all screwy? Is it a bug in rspec? What’s going on?
Update: I’ve given up for now and am just writing my tests like this:
1 2 3 4 5 6 7 8 | it "should have a nice startup command" do
m = /w.start = "([^"]*)"/.match(@god)
cmd = m[0]
cmd.should match(/mongrel_rails start/)
cmd.should match(/-c \#\{RAILS_ROOT\}/)
cmd.should match(/-p \#\{port\}/)
cmd.should match(/-d/)
end |
It’s not pretty, but it works. Maybe as I get better at ruby I’ll be able to go back and get it working the right way.