LoginSignup
7
7

More than 5 years have passed since last update.

power_assertのブロック内でfactory_girlが呼び出されると死ぬ

Last updated at Posted at 2014-09-30

追記: 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_locationslengthを取ってるので、やたらでかい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

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7