Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly unstubs methods stubbed with #any_instance #182

Closed
wants to merge 5 commits into from

Conversation

alindeman
Copy link
Contributor

  • Instances stubbed with #any_instance would not be usable after the
    test finished because MethodDouble would stash the implementation of
    the method already overridden by AnyInstance::Recorder. When the
    test finished, that implementation would be restored on the object's
    singleton class, and any future calls to it would blow up with a stack
    overflow.
  • This fix only stashes methods if they are defined on the object's
    singleton class to begin with; AnyInstance::Recorder defines a
    method on the object's class so that method will not be stashed.
  • If there is no method on the object's singleton class, RSpec can
    safely define one there without stashing the original implementation.
    At the end of the test, the method is simply removed entirely from the
    singleton class. Any original implementation defined in the object's
    ancestor chain will show through again.
  • This issue cannot be fixed on MRI 1.8.6 because it does not support
    Method#owner. However, #any_instance itself is not supported on
    1.8.6 for the same reason. The fix should not negatively affect 1.8.6,
    though, because the fallback behavior is to stash the method in all
    cases (which was the original behavior).
  • This commit also refactors the stashing behavior out into its own
    object. While not explicitly necessary, it helped me reason about the
    fix much easier than when all the responsibility was in MethodDouble
    (which also has other responsibilities).
  • Fixes Instances stubbed with any_instance blow up when used after spec is finished #167

* Instances stubbed with `#any_instance` would not be usable after the
  test finished because `MethodDouble` would stash the implementation of
  the method already overridden by `AnyInstance::Recorder`. When the
  test finished, that implementation would be restored on the object's
  singleton class, and any future calls to it would blow up with a stack
  overflow.
* This fix only stashes methods if they are defined on the object's
  singleton class to begin with; `AnyInstance::Recorder` defines a
  method on the object's class so that method will not be stashed.
* If there is no method on the object's singleton class, RSpec can
  safely define one there without stashing the original implementation.
  At the end of the test, the method is simply removed entirely from the
  singleton class. Any original implementation defined in the object's
  ancestor chain will show through again.
* This issue cannot be fixed on MRI 1.8.6 because it does not support
  `Method#owner`. However, `#any_instance` itself is not supported on
  1.8.6 for the same reason. The fix should not negatively affect 1.8.6,
  though, because the fallback behavior is to stash the method in all
  cases (which was the original behavior).

* This commit also refactors the stashing behavior out into its own
  object. While not explicitly necessary, it helped me reason about the
  fix much easier than when all the responsibility was in `MethodDouble`
  (which also has other responsibilities).

* Fixes rspec#167

object_singleton_class.class_eval <<-EOF, __FILE__, __LINE__ + 1
remove_method :#{@method_name}
EOF
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to eval a string here for this? I would think you could just do object_singleton_class.send(:remove_method, @method_name).

In general, I try to avoid eval'ing strings unless it's really needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, doh, that makes a lot more sense. I think the original code was class_evaled so I just copied it over without considering simplifications.

@myronmarston
Copy link
Member

Awesome work, Andy! I have a couple thoughts on slightly simpler implementations of a few things (see my comments above) but otherwise this looks great.

@@ -44,16 +46,6 @@ class << @object; self; end
end

# @private
def obfuscate(method_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still one call to obfuscate in this class - must not be tested.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch. I'll definitely fix this and also try to cover it with a spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good news everyone! I think that object_responds_to? (which uses obfuscate) is unused as of b599a42.

There's no reference to it in any of the rspec repos currently (except its definition):

[rspec-dev] grep -R object_responds_to\? repos
repos/rspec-mocks/lib/rspec/mocks/method_double.rb:      def object_responds_to?(method_name)

I'm going to bias towards removing it unless someone lets me know that I'm missing something.

@dchelimsky
Copy link
Contributor

Besides obfuscate, I think this looks good overall and I agree w/ @myronmarston's suggestions.

@alindeman
Copy link
Contributor Author

Pushed up some new commits that address the code review comments. If I get a 👍 from @myronmarston or @dchelimsky, I'll squash-merge.

@alindeman
Copy link
Contributor Author

Not sure what's up with Travis. There was no output for the failed 1.8.7 build http://travis-ci.org/#!/rspec/rspec-mocks/jobs/2382370 and it runs green locally.

@klass.method_defined?(@method) || @klass.private_method_defined?(@method)
end

if ::Method.method_defined?(:owner)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit pedantic, but shouldn't this be ::UnboundMethod.method_defined?(:owner)? @klass.instance_method returns an UnboundMethod.

I think that owner is either present or absent on both ::Method and ::UnboundMethod but it still makes sense to check against the class that the object below is actually an instance of.

@myronmarston
Copy link
Member

👍

* Class#instance_method returns an UnboundMethod
@alindeman
Copy link
Contributor Author

Will change Method to UnboundMethod and merge. Thanks for the reviews.

@alindeman alindeman closed this in a727464 Sep 9, 2012
alindeman added a commit that referenced this pull request Sep 9, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Instances stubbed with any_instance blow up when used after spec is finished
4 participants