Hi! I was just talking to @dchelimsky over Twitter about a weird corner case we''ve run into on 1.3.1 (I''ve also been able to reproduce it in 1.3.2) So we''re using this gem called ClassyStruct that''s a higher performing OpenStruct: https://github.com/amikula/classy_struct Basically it acts the same as OpenStruct but defines methods on the object''s class instead of the object itself on the fly. When it receives a call that hits method_missing it calls attr_accessor on the method name then passes the call on to the object. Our problem comes from having one spec that stubs out a call to the object: foo.stub! :bar => ''test'' and later in another spec file trying to set the same method with a value then having our code use that value: # spec foo.bar = ''other test'' # code puts "#{foo.bar} baz" So our expectation is that foo.bar will return ''other test''. Instead it hits the old stub on foo which calls method_missing which is picked up again by ClassyStruct causing it to fire off attr_accessor again then passing the method through causing the stub to call method_missing and on and on finally giving us a "stack level too deep" error. The crux of the problem is that ClassyStruct is adding a method to the class after Rspec has added the same method to the instance. As I said it''s a very weird corner case because we''re calling attr_accessor on a class that already has objects floating around. The easiest way to fix this is to use stub! in both places. Regardless we were surprised that the proxy sticks around after a test run. What is the reason for keeping it around? Thanks for your time! Doug McInnes
On Apr 22, 2011, at 4:58 PM, Doug McInnes wrote:> Hi! > > I was just talking to @dchelimsky over Twitter about a weird corner case we''ve run into on 1.3.1 (I''ve also been able to reproduce it in 1.3.2) > > So we''re using this gem called ClassyStruct that''s a higher performing OpenStruct: > https://github.com/amikula/classy_struct > > Basically it acts the same as OpenStruct but defines methods on the object''s class instead of the object itself on the fly. > When it receives a call that hits method_missing it calls attr_accessor on the method name then passes the call on to the object. > > Our problem comes from having one spec that stubs out a call to the object: > foo.stub! :bar => ''test'' > > and later in another spec file trying to set the same method with a value then having our code use that value: > # spec > foo.bar = ''other test'' > # code > puts "#{foo.bar} baz" > > So our expectation is that foo.bar will return ''other test''. Instead it hits the old stub on foo which calls method_missing which is picked up again by ClassyStruct causing it to fire off attr_accessor again then passing the method through causing the stub to call method_missing and on and on finally giving us a "stack level too deep" error. > > The crux of the problem is that ClassyStruct is adding a method to the class after Rspec has added the same method to the instance. > > As I said it''s a very weird corner case because we''re calling attr_accessor on a class that already has objects floating around. The easiest way to fix this is to use stub! in both places. > > Regardless we were surprised that the proxy sticks around after a test run. What is the reason for keeping it around?There''s no intent to keep it around, so there is a bug at play here, but let''s see if we can narrow it down. Can you post (gist or pastie) an example that I can just run as/is to see the behavior you''re seeing?
Sure! Here''s the Gist: https://gist.github.com/940868 It''s works in Rspec 2.5.1, but not in Rspec 1.3.2 Doug On Apr 23, 2011, at 3:00 PM, David Chelimsky wrote:> On Apr 22, 2011, at 4:58 PM, Doug McInnes wrote: > >> Hi! >> >> I was just talking to @dchelimsky over Twitter about a weird corner case we''ve run into on 1.3.1 (I''ve also been able to reproduce it in 1.3.2) >> >> So we''re using this gem called ClassyStruct that''s a higher performing OpenStruct: >> https://github.com/amikula/classy_struct >> >> Basically it acts the same as OpenStruct but defines methods on the object''s class instead of the object itself on the fly. >> When it receives a call that hits method_missing it calls attr_accessor on the method name then passes the call on to the object. >> >> Our problem comes from having one spec that stubs out a call to the object: >> foo.stub! :bar => ''test'' >> >> and later in another spec file trying to set the same method with a value then having our code use that value: >> # spec >> foo.bar = ''other test'' >> # code >> puts "#{foo.bar} baz" >> >> So our expectation is that foo.bar will return ''other test''. Instead it hits the old stub on foo which calls method_missing which is picked up again by ClassyStruct causing it to fire off attr_accessor again then passing the method through causing the stub to call method_missing and on and on finally giving us a "stack level too deep" error. >> >> The crux of the problem is that ClassyStruct is adding a method to the class after Rspec has added the same method to the instance. >> >> As I said it''s a very weird corner case because we''re calling attr_accessor on a class that already has objects floating around. The easiest way to fix this is to use stub! in both places. >> >> Regardless we were surprised that the proxy sticks around after a test run. What is the reason for keeping it around? > > There''s no intent to keep it around, so there is a bug at play here, but let''s see if we can narrow it down. > > Can you post (gist or pastie) an example that I can just run as/is to see the behavior you''re seeing?
On Apr 25, 2011, at 10:51 AM, Doug McInnes wrote:> Sure! > Here''s the Gist: > > https://gist.github.com/940868 > > It''s works in Rspec 2.5.1, but not in Rspec 1.3.2 > > Doug > > > On Apr 23, 2011, at 3:00 PM, David Chelimsky wrote: > >> On Apr 22, 2011, at 4:58 PM, Doug McInnes wrote: >> >>> Hi! >>> >>> I was just talking to @dchelimsky over Twitter about a weird corner case we''ve run into on 1.3.1 (I''ve also been able to reproduce it in 1.3.2) >>> >>> So we''re using this gem called ClassyStruct that''s a higher performing OpenStruct: >>> https://github.com/amikula/classy_struct >>> >>> Basically it acts the same as OpenStruct but defines methods on the object''s class instead of the object itself on the fly. >>> When it receives a call that hits method_missing it calls attr_accessor on the method name then passes the call on to the object. >>> >>> Our problem comes from having one spec that stubs out a call to the object: >>> foo.stub! :bar => ''test'' >>> >>> and later in another spec file trying to set the same method with a value then having our code use that value: >>> # spec >>> foo.bar = ''other test'' >>> # code >>> puts "#{foo.bar} baz" >>> >>> So our expectation is that foo.bar will return ''other test''. Instead it hits the old stub on foo which calls method_missing which is picked up again by ClassyStruct causing it to fire off attr_accessor again then passing the method through causing the stub to call method_missing and on and on finally giving us a "stack level too deep" error. >>> >>> The crux of the problem is that ClassyStruct is adding a method to the class after Rspec has added the same method to the instance. >>> >>> As I said it''s a very weird corner case because we''re calling attr_accessor on a class that already has objects floating around. The easiest way to fix this is to use stub! in both places. >>> >>> Regardless we were surprised that the proxy sticks around after a test run. What is the reason for keeping it around? >> >> There''s no intent to keep it around, so there is a bug at play here, but let''s see if we can narrow it down. >> >> Can you post (gist or pastie) an example that I can just run as/is to see the behavior you''re seeing? >Any ideas on this issue? Here''s the code: class TestClass end # our use case is with a stub on a constant TEST = TestClass.new describe TestClass do it "works before the stub" do TestClass.send(:attr_accessor, :foo) TEST.foo = :baz TEST.foo.should == :baz TestClass.send(:remove_method, ''foo'') TestClass.send(:remove_method, ''foo='') end it "has a stubbed method" do TEST.stub! :foo => :bar TEST.foo.should == :bar end it "fails after the stub" do TestClass.send(:attr_accessor, :foo) TEST.foo = :baz TEST.foo.should == :baz end end And the console output: ~/tmp $ ruby -v ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0] ~/tmp $ spec -v rspec 1.3.2 ~/tmp $ spec test.rb ..F 1) NoMethodError in ''TestClass fails after the stub'' undefined method `foo'' for #<TestClass:0x00000101180158> test.rb:28:in `block (2 levels) in <top (required)>'' Finished in 0.046678 seconds 3 examples, 1 failure ~/tmp $ rspec -v 2.5.1 ~/tmp $ rspec test.rb ... Finished in 0.00154 seconds 3 examples, 0 failures As I said this is a weird corner case :) Thanks, Doug