追記: 2014/10/6 PullReqがマージされたので現時点でのmasterでは直ってます。
power_assertのブロック内でfactory_girlが評価されると以下のようなエラーが出て死ぬようです。
NoMethodError:
undefined method `<<' for nil:NilClass
軽く調査してみた所、FactoryGirl内部でメソッドの呼び出しをトラッキングしている部分と競合しているようです。
insntace_evalのせいで細かい評価コンテキストが追い切れなかったので、詳細は不明ですがFactoryGirl::Decorator::InvocationTracker
というのが問題なのは確かなようです。
FactoryGirlは内部でattributeをassignしていく時に、decoratorを被せてメソッドの呼び出しをmethod_missingで格納していっています。
問題は、このdecoratorがBasicObjectから継承されている点です。
power_assertはTracePointから評価されたスタック相対位置を取るために、tp.binding.eval('caller_locations')
って感じでcaller_locations
を呼び出しているんですが、このevalした先がBasicObjectだとcaller_locationsが無くてmethod_missingが変に呼ばれて死ぬ、という感じになってるんじゃないかなと思います。
なんとか直せないかと思ったんですが、BasicObjectで問答無用でmethod_missingされると、評価されてる所がBasicObject上なのかどうかを調べること事態が結構めんどくさい。
ここのTracePointイベントを上手く捨てられればいいんだけど、ちょっとすぐには思い付かない。
とりあえずの回避策は、FactoryGirl側にモンキーパッチを当てること。
module PowerAssertForFactoryGirl
module DummyStack
def self.length
2147483647
end
end
def caller_locations
DummyStack
end
end
FactoryGirl::Decorator::InvocationTracker.include(PowerAssertForFactoryGirl)
power_assert内でcaller_locations
のlength
を取ってるので、やたらでかいlength
を返すダミーを返せばとりあえず問題のイベントは捨てられます。
ただ、power_assertの実装が変わると死ぬので、できればちゃんと直したい。
もしくは、InvocationTrackerにKernelモジュールをincludeさせる方法もあります。モンキーであることは変わりませんが。
caller_locations
が使えるようになるので、これでも動作するんですが、定義されるメソッドがかなり多いのでFactoryGirlの動作に影響を及ぼすかもしれません。
まあ、そんなKernelモジュールのメソッドと衝突しそうなattribute名を定義することはそんなに無いと思いますが。
追記:
BasicObjectベースのオブジェクトが渡された時、もしくはBasicObjectが直接渡された場合にpower_assertを適切に動かす方法はちゃんと検証できたので、pull requestを投げました。
単にevalで呼んでるメソッドをKernel.caller_locations
に変えれば良いだけだった。
後、inspectメソッドも無いのでその場合にinspectできないよって出すように処理を追加した。
Fix for BasicObject by joker1007 · Pull Request #7 · k-tsj/power_assert