TheCaseOfTheInactiveMailerTest

August 05, 2008
I couldn't get actionmailer-2.1.0/test/mail_server_test.rb to run. I was just picking on it for research related to my DiyMocks article, and couldn't get it to work.

The first problem turned out to be a bug in the 0.9.0 version of mocha, which was simple enough to workaround, then the author sent me a patch.

But the test ran into a new problem after the mocha patch:

undefined method 'class_inheritable_accessor' for ActionMailer:Base:Class (NoMethodError)


I went and got git for Windows (many thx to those keeping us Windoze loozers on the fringe of the action) and yanked the source down. On master, the test passed. Changed to the release branch (“git checkout origin/2-1-stable”) and the test also passed. Hmmm. Perhaps we had a deployment problem? Or maybe the git branch has been patched but not released?

I ran a WinMerge against the source folder and the deployed gem folder, and while I found some minor differences, nothing important showed up.

As Raymond Chen says, “Theorize if you want, but if the problem is right there in front of you, why not go for the facts?”

First off, if class_inheritable_accessor couldn't be found, where did it live? A Google Code search[1] turned up several hits. Clicking through the first hit led me to Rails diff, that showed class_inheritable_accessor was defined in /tags/rel_1-0-0/activesupport/lib/active_support/class_inheritable_attributes.rb, at least back in 1.0.0. Looking in the gem deployment folders for 2.1.0, showed no such file.

Browsing through more Google Code results showed activesupport-1.3.0 had this method defined in lib/active_support/core_ext/class/inheritable_attributes.rb. Lo and behold, there it was on my hard drive in gems/activesupport-2.1.0/lib/active_support/core_ext/class/inheritable_attributes.rb.

Now that I knew where it lived, I needed to find the disconnect between it and mail_server_test.rb. The stack trace was no help, because the error occurred only when the class under test couldn't find what it needed, not when the file wasn't loaded -- it's a non-event.

My next thought was to simply add a require statement in mail_server_test.rb to force the inclusion of inheritable_attributes, but it was obvious from the structure of the test files and their dependency on abstract_unit that the designers intended for abstract_unit to be a hub of requires, not mail_server_test.rb. And besides, on the source 2.1.0 branch, the test worked without that direct require; something else was amiss.

At this point, I could sidebar on Things Java Has Right That Ruby Doesn't. In Java I couldn't get away (barring use of reflection) with a reference to a method that's not been imported, and I would have to import that class directly in my test class (presuming here, that probably in the Java version of all this, we wouldn't be sharing package names). Viva la static typing, no? Fear not, Ruby will get its chance.[2]

Trouble is, because Ruby doesn't have the strict importing rules Java does, who knows where the missing link in the requires dependency tree is?

Well, come to think of it, the code in the pulled source knows.

I opened up inheritable_attributes.rb and added this line at the very top of the file,[3]

begin raise Exception.new; rescue Exception => e; puts e.backtrace; end

then ran the test from the pulled source. The stack trace showed me the path from mail_server_test.rb to inheritable_attributes.rb, so I switched back over to my local gems folder and started walking the path looking for anything out of the ordinary. In actionmailer-2.1.0/lib/action_mailer.rb, I found what I was looking for:

unless defined?(ActionController)
begin
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
require 'action_controller'
rescue LoadError
require 'rubygems'
gem 'actionpack', '>= 1.12.5'
end
end

Ahh, someone perhaps was a little too clever in working out how to require action_controller in both the source environment and deployment environment. Unfortunately, they overlooked one line, probably a simple mistake not caught by verifying the unit tests in a deployed environment:

--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -28,6 +28,7 @@ unless defined?(ActionController)
rescue LoadError
require 'rubygems'
gem 'actionpack', '>= 1.12.5'
+ require 'action_controller'
end
end

Loading a gem does not result in getting all files in that gem required. At least, not by default.

Now the test in question runs in its deployment folder, and I can get on with my anal retentive life.



Notes:

[1] Why not grep the local gems folder or somesuch instead of Google Code search? I dunno - no particular reason. Probably because Window's find command is not under my fingertips (and even if it was, it doesn't recurse?!?) and I was too lazy to use the grep-ish tools in my editors.

[2] Personally (probably due to laziness rather than wisdom) this is trade-off thing. I love the static analysis I can get in Java; I love the cool stack trace trick I used in Ruby to help me find the problem.

[3] Sure enough, I publish this, and then think - wait wait wait - I don't have to throw an exception to get a trace ... right? There's another way on the tip of my brain - but ... too tired to go research and remind myself. Must sleep now.

tags: ComputersAndTechnology