-
-
Notifications
You must be signed in to change notification settings - Fork 357
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
Conversation
* 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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_eval
ed so I just copied it over without considering simplifications.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Besides |
* It appears that the method is unused as of b599a42 but was not removed as a part of that commit.
Pushed up some new commits that address the code review comments. If I get a 👍 from @myronmarston or @dchelimsky, I'll squash-merge. |
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) |
There was a problem hiding this comment.
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.
👍 |
* Class#instance_method returns an UnboundMethod
Will change |
#any_instance
would not be usable after thetest finished because
MethodDouble
would stash the implementation ofthe method already overridden by
AnyInstance::Recorder
. When thetest 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.
singleton class to begin with;
AnyInstance::Recorder
defines amethod on the object's class so that method will not be stashed.
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.
Method#owner
. However,#any_instance
itself is not supported on1.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).
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).
any_instance
blow up when used after spec is finished #167