I ran across a really obscure and subtle bug in Ruby 1.9, which was triggered by RSpec including a module for the purpose of altering RSpec::Mock objects before they’re serialized to YAML.
I was attempting to update a work project from REE 1.8.7 to MRI 1.9.3. Some of the tests were failing with a SystemStackOverflow: stack level too deep error while trying to save an ActiveRecord (AR) model. The stack traces were completely useless, they could only lead me to the save! call in the test.
Using the debugger proved exceedingly difficult, because as of AR 3.x, callbacks were re-written to use heavy metaprogramming, and it doesn’t take long before the debugger has no idea which line of code its on anymore.
I tried a number of things, including disabling callbacks, observers, and serialized attributes. I increased the maximum stack size, ran the tests in different orders, ensure the databases were cleaned before and after each test, and a number of other things. The tests always failed with 1.9, but passed with 1.8.
When none of those things worked for the primary model, I started to do the same for models referenced as belongs_to relations of the primary model. Sure enough, one of them was indeed the culprit. I began the process again, but this time on the secondary model. I was able to quickly narrow it down to one of the secondary models’s serialized attributes. A simple ActiveSupport::HashWithIndifferentAccess (HWIA) was to blame. Whenever I tried to serialize it, kaboom! I was able to further narrow it down to the first key-value pair, ("async", nil). Reproducing this same HWIA in the console did not yield a crash, however.
Using pry, I compared the output of ls -v for the nil value in the console, versus the nil value in the RSpec test. What I noticed, was that the to_yaml methods were attributed to different sources. In the console, to_yaml came from Object, while in the RSpec test, to_yaml came from RSpec::Mocks::Serialization::YAML.
Searching Google, I quickly found a workaround, use the older YAML parser, syck, instead of the new default of psych:
YAML::ENGINE.yamler = "syck"
But syck has its own issues, and besides, there’s no reason why this shouldn’t work, the HWIA wasn’t cyclical at all. I kept looking, then eventually I found it.
https://github.com/rspec/rspec-core/commit/622a4b7ac41e0ab6a4786070cca3ff866b72b57c
More googling led me to this:
End result? Upgrade RSpec to a version >= 2.6.
Then I searched some more, to see if the core ruby folks know about this? Yep, they do:
It’s slated to be fixed in 1.9.4, if such a beast is ever released.
Thanks to my co-workers at LMP, and the folks on the URUG mailing list for helping me out along the way.
Just before the weirdness
This blank line is the weirdness, it’s really disconcerting when typing a long command, or when using readline to look through previous commands.
The weirdness continues…
But a CTRL-l will help ease our pain.