"undefined method" with any_instance.stub in RSpec 2.11 #168

Closed
henrik opened this Issue Jul 31, 2012 · 32 comments

Projects

None yet

6 participants

@henrik
henrik commented Jul 31, 2012

After upgrading from RSpec 2.10 to 2.11, this line fails every time:

ThinkingSphinx::Search.any_instance.stub(:error)

The error:

undefined method `dup' for class `ThinkingSphinx::Search'
/Users/henrik/.rvm/gems/ruby-1.9.3-p0-patched@foobar/gems/rspec-mocks-2.11.1/li‌​b/rspec/mocks/any_instance.rb:64:in `alias_method'

Someone else got a similar error but with undefined method '__rspec_original_dup':

http://stackoverflow.com/questions/11679987/rspec-any-instance-stub-causing-error-undefined-method-rspec-original-dup

Sorry not to provide more info. Just reporting for now, but might dig into it further later.

@dchelimsky
Member

This is because that class removes the dup method: https://github.com/freelancing-god/thinking-sphinx/blob/master/lib/thinking_sphinx/search.rb#L11-23. I'm sure there is good reason for that, but dup is a core Ruby method that Ruby's contract says is part of every object. In my view, Search is violating that contract and RSpec can't be responsible for handling cases like this. I'm open to other views and/or solutions, but that's my current thinking. I'm going to close this but feel free to keep the conversation going here.

@dchelimsky dchelimsky closed this Aug 1, 2012
@dchelimsky dchelimsky reopened this Aug 1, 2012
@dchelimsky
Member

Actually, this is something rspec is doing to safeguard scenarios in which other code calls dup (so the dup doesn't carry rspec modifications along with it). This is reasonably fixable within rspec IMO since it's not a core method that rspec is using to make mocks work.

@alindeman
Contributor

What are your thoughts, a guard on respond_to?(:dup) ?

@dchelimsky dchelimsky closed this in b828604 Aug 1, 2012
@dchelimsky
Member

@alindeman I used method_defined? because this happens on the class, not its instances.

@alindeman
Contributor

Ah ha, that makes sense.

@henrik
henrik commented Aug 1, 2012

@dchelimsky Awesome, thank you!

@moll
moll commented Aug 23, 2012

Umm, I'm guessing now that 2.11.2 is out, this should be fixed, yet I'm still getting the __rspec_original_dup error on regular controllers:

 Failure/Error: Unable to find matching line from backtrace
 NameError:
   undefined method `__rspec_original_dup' for class `Accounts::RegistrationsController'
 # /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb:73:in `alias_method'
 # /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb:73:in `restore_dup'
 # /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb:72:in `class_eval'

Looks like the problem happens when the stubbed/mocked method is called in the controller action. Calling it from within the example doesn't raise this.

@henrik
henrik commented Aug 23, 2012

@moll This is what we did right after @dchelimsky committed the above fix, and it solved our problems. No idea if it is supposed to just work with 2.11.2:

  gem 'rspec-rails'
  # We need edge rspec-mocks because of this bug:
  # https://github.com/rspec/rspec-mocks/issues/168
  # When rspec >2.11.1 is released, we can probably
  # remove the following line.
  gem 'rspec-mocks', github: 'rspec/rspec-mocks'

And in our Gemfile.lock, these are the versions we have:

    rspec-rails (2.11.0)

GIT
  remote: git://github.com/rspec/rspec-mocks.git
  revision: b828604d7cc16217994caaef3c2700c03b708172
  specs:
    rspec-mocks (2.11.1)
@dchelimsky
Member

@moll I can't duplicate the issue you're seeing and can't understand how it's happening. https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/any_instance.rb#L71 asks if __rspec_original_dup is defined before it's used in alias_method.

Would you be willing/able to post a one-action Rails app on github that I can clone so I can see (and debug) the error?

@moll
moll commented Aug 24, 2012

Gotcha, @dchelimsky. I'll dig in and get back to you with further intel!
And thanks, @henrik, for the suggestion, though this committed fix is actually in 2.11.2, so it must be something else. I'll dig code!

@moll
moll commented Sep 7, 2012

@dchelimsky, I haven't tried to create a one-off Rails apps for this, but I've honed it down in the debugger:

(rdb:1) l=
[69, 78] in /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb
   69        
   70        def restore_dup
   71          require 'ruby-debug'; debugger
   72          if self.method_defined?(:__rspec_original_dup)
   73            self.class_eval do
=> 74              alias_method  :dup, :__rspec_original_dup
   75              remove_method :__rspec_original_dup
   76              remove_method :__rspec_dup
   77            end
   78          end

(rdb:1) method_defined?(:__rspec_original_dup)
true
(rdb:1) alias_method  :dup, :__rspec_original_dup
NameError Exception: undefined method `__rspec_original_dup' for class `Connexion::Google'
(rdb:1) __rspec_original_dup
NameError Exception: undefined local variable or method `__rspec_original_dup' for #<Class:0x10f0efa00>

(rdb:1) where
--> #0 Module.restore_dup 
       at line /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb:74
    #1 RSpec::Mocks::AnyInstance.restore_dup 
       at line /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb:73
    #2 RSpec::Mocks::AnyInstance.rspec_reset 
       at line /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/any_instance.rb:46
    #3 Array.reset_all 
       at line /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/space.rb:17
    #4 RSpec::Mocks::Space.reset_all 
       at line /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks/space.rb:16
    #5 RSpec::Mocks.teardown 
       at line /Library/Ruby/Gems/1.8/gems/rspec-mocks-2.11.2/lib/rspec/mocks.rb:23
    #6 RSpec::Core::MockFrameworkAdapter.teardown_mocks_for_rspec 
       at line /Library/Ruby/Gems/1.8/gems/rspec-core-2.11.1/lib/rspec/core/mocking/with_rspec.rb:18
    #7 RSpec::Core::Example.run_after_each 
       at line /Library/Ruby/Gems/1.8/gems/rspec-core-2.11.1/lib/rspec/core/example.rb:308

Umm, so, how does this method_defined? vs class_eval work? And does it matter that the class in question atm is meant for a single-table-inheritance model (with definition of class Connexion::Google < Connexion)?
I'm also on the stock Ruby 1.8.7 of Mountain Lion here.

@moll
moll commented Sep 7, 2012

@dchelimsky:
@alindeman I used method_defined? because this happens on the class, not its instances.

After a bit of reading it seems to me that method_defined? is for instances, not for class methods. As in:

[1] pry(main)> String.method_defined? :upcase
=> true
[2] pry(main)> String.upcase
NoMethodError: undefined method `upcase' for String:Class
[3] pry(main)> String.new.upcase
=> ""

Or did you mean something else by that?

@dchelimsky
Member

@moll even in your example, method_defined? is asked of the class, but references an instance method. That's what I meant.

@moll
moll commented Sep 18, 2012

Umm, could it be that class_eval and alias_method don't play along? The debugger output I pasted above replicates that the if method_defined? check passes, yet alias_method fails.

@dchelimsky
Member

@moll it could be any number of things, but what you're experiencing is specific to your environment, so we can't help you debug it unless you can provide code that reproduces the error. Once we can see it happen locally we can debug things with some accuracy. Otherwise this is just a guessing game.

@alindeman
Contributor

@moll, I'd be happy to look into this if you can get it reproduced in a succinct bit of code that you can share with us.

@SergeyJey

@molls error can be reproduced with this bit of code. It works differently on different 1.8.7 ruby versions:

class A
  class << self
    def modify_dup
      if self.method_defined?(:dup) and !self.method_defined?(:__rspec_original_dup)
        self.class_eval do
          def __rspec_dup; '__rspec_dup'; end

          alias_method  :__rspec_original_dup, :dup
          alias_method  :dup, :__rspec_dup
        end
      end
    end

    def restore_dup
      if self.method_defined?(:__rspec_original_dup)
        self.class_eval do
          alias_method  :dup, :__rspec_original_dup
          remove_method :__rspec_original_dup
          remove_method :__rspec_dup
        end
      end
    end
  end
end

# 1.8.7-p174
A.modify_dup
A.instance_methods.grep /dup/ #=> ["dup", "__rspec_original_dup", "__rspec_dup"]
A.restore_dup
A.instance_methods.grep /dup/ #=> ["dup"]
A.method_defined?(:__rspec_original_dup) #=> true        <- ???
A.restore_dup #=> NameError: undefined method `__rspec_original_dup'

# 1.8.7-p371
A.modify_dup
A.instance_methods.grep /dup/ #=> ["dup", "__rspec_original_dup", "__rspec_dup"]
A.restore_dup
A.instance_methods.grep /dup/ #=> ["dup"]
A.method_defined?(:__rspec_original_dup) #=> false       <- OK
A.restore_dup #=> nil

So, if restore_dup will be called more then once on 1.8.7-p174 you will get alias_method error because self.method_defined?(:__rspec_original_dup) will return true even if there is no __rspec_original_dup anymore.

@moll
moll commented Oct 22, 2012

Yay. Go @SergeyJey for getting to the bottom of this faster than me!
I'll give RSpec 2.11 a new try soon — maybe Mountain Lion's first update upgraded Ruby. It's at ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0] now.

@alindeman
Contributor

Nice @SergeyJey. @moll, did it end up working?

@moll
moll commented Oct 28, 2012

@alindeman Tried it now. Unfortunately same issue with __rspec_original_dup with ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0] on my OS X Mountain Lion.

@SergeyJey's test code prints the same error:

dup.rb:17:in `alias_method': undefined method `__rspec_original_dup' for class `A' (NameError)
@moll
moll commented Oct 28, 2012

When using undef_method instead of remove_method, @SergeyJey's test code works as intended immediately even on my machine.

@SergeyJey

Huh, ran test code with ruby-1.8.7-p358 and ruby-1.8.7-p302 on Mountain Lion. Everything seems to be working properly.

@moll
moll commented Oct 28, 2012

Well, when I run those lines manually in Pry, I still get:

[2] pry(main)> A.modify_dup
=> A
[4] pry(main)> A.method_defined?(:__rspec_original_dup)
=> true
[5] pry(main)> A.restore_dup
=> A
[6] pry(main)> A.method_defined?(:__rspec_original_dup)
=> true

Now, if I in addition do this (call #method on the instance), it fixes itself:

[10] pry(main)> A.new.__rspec_original_dup
=> #<A:0x107446440>
[11] pry(main)> A.method_defined?(:__rspec_original_dup)
=> true
[9] pry(main)> A.new.method :__rspec_original_dup
NameError: undefined method `__rspec_original_dup' for class `A'
from (pry):28:in `method'
[10] pry(main)> A.method_defined?(:__rspec_original_dup)
=> false

But other than that, it's totally reproducible each time. I couldn't find much related to remove_method in the Ruby bug tracker either.

@moll
moll commented Oct 28, 2012

@SergeyJey Did you test with the stock Ruby or have you compiled those versions yourself?

@SergeyJey

@moll I experimented with RVM ruby versions. RVM ruby-1.8.7-p358 working properly. Tried now with stock ruby-1.8.7-p358 version - test code prints error.

@dchelimsky
Member

Huh, ran test code with ruby-1.8.7-p358 and ruby-1.8.7-p302 on Mountain Lion. Everything seems to be working properly.

@SergeyJey - In your example above the problem only occurred with 1.8.7-p174, right?

@moll
moll commented Oct 31, 2012

@dchelimsky Naa, it seems we're both having this issue on stock OS X Mountain Lion Ruby that's ruby-1.8.7-p358.

@dchelimsky
Member

@moll OK - I was able to see the error using stock ruby-1.8.7-p358 on Mountain Lion. @alindeman, were planning to look into this? If not I can take a crack at it but likely not before the coming weekend.

@alindeman
Contributor

I can definitely give it a shot but it might be later this week/weekend as well.

@moll
moll commented Dec 21, 2012

Happy X-holidays! I'm guessing neither of you have had the opportunity to take a crack shot yet. :)

@alindeman
Contributor

Correct. It's still on the list, but I did not estimate correctly :)

@prusswan
prusswan commented Mar 8, 2013

Not sure if this is of any help, but I managed to trigger the same error while trying to stub a system call with:

Object.any_instance.should_receive(:'`').and_return(0)

Probably not the best approach, but the error:

An error occurred in an around(:each) hook
  NameError: method `__rspec_original_dup' not defined in Configuration
  occurred at /home/prusswan/.rvm/gems/ruby-1.8.7-p371/gems/rspec-mocks-2.13.0/lib/rspec/mocks/any_instance.rb:74:in `remove_method'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment