Skip to content

Commit

Permalink
Merge 4b0abd6 into 046dd46
Browse files Browse the repository at this point in the history
  • Loading branch information
alindeman committed Aug 21, 2013
2 parents 046dd46 + 4b0abd6 commit 8a57102
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 47 deletions.
10 changes: 5 additions & 5 deletions lib/rspec/mocks/any_instance/message_chains.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ def add(method_name, chain)
# @private
def remove_stub_chains_for!(method_name)
@chains_by_method_name[method_name].reject! do |chain|
chain.is_a?(StubChain)
StubChain === chain
end
end

# @private
def has_expectation?(method_name)
@chains_by_method_name[method_name].find do |chain|
chain.is_a?(ExpectationChain)
ExpectationChain === chain
end
end

Expand All @@ -42,7 +42,7 @@ def all_expectations_fulfilled?
# @private
def unfulfilled_expectations
@chains_by_method_name.map do |method_name, chains|
method_name.to_s if chains.last.is_a?(ExpectationChain) unless chains.last.expectation_fulfilled?
method_name.to_s if ExpectationChain === chains.last unless chains.last.expectation_fulfilled?
end.compact
end

Expand All @@ -64,8 +64,8 @@ def playback!(instance, method_name)
private

def raise_if_second_instance_to_receive_message(instance)
@instance_with_expectation ||= instance if instance.is_a?(ExpectationChain)
if instance.is_a?(ExpectationChain) && !@instance_with_expectation.equal?(instance)
@instance_with_expectation ||= instance if ExpectationChain === instance
if ExpectationChain === instance && !@instance_with_expectation.equal?(instance)
raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/any_instance/recorder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def initialize(klass)
#
# @see Methods#stub
def stub(method_name_or_method_map, &block)
if method_name_or_method_map.is_a?(Hash)
if Hash === method_name_or_method_map
method_name_or_method_map.each do |method_name, return_value|
stub(method_name).and_return(return_value)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/argument_list_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def args_match?(*args)

def matcher_for(arg)
return ArgumentMatchers::MatcherMatcher.new(arg) if is_matcher?(arg)
return ArgumentMatchers::RegexpMatcher.new(arg) if arg.is_a?(Regexp)
return ArgumentMatchers::RegexpMatcher.new(arg) if Regexp === arg
return ArgumentMatchers::EqualityProxy.new(arg)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/argument_matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def hash_including(*args)
# object.should_receive(:message).with(array_including(1,2,3))
# object.should_receive(:message).with(array_including([1,2,3]))
def array_including(*args)
actually_an_array = args.first.is_a?(Array) && args.count == 1 ? args.first : args
actually_an_array = Array === args.first && args.count == 1 ? args.first : args
ArrayIncludingMatcher.new(actually_an_array)
end

Expand Down
13 changes: 8 additions & 5 deletions lib/rspec/mocks/error_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,14 @@ def expected_part_of_expectation_error(expected_received_count, expectation_coun

# @private
def method_call_args_description(args)
if args.first.is_a?(ArgumentMatchers::AnyArgsMatcher)
" with any arguments"
elsif args.first.is_a?(ArgumentMatchers::NoArgsMatcher)
" with no arguments"
elsif args.length > 0
case args.first
when ArgumentMatchers::AnyArgsMatcher
return " with any arguments"
when ArgumentMatchers::NoArgsMatcher
return " with no arguments"
end

if args.length > 0
" with arguments: #{args.inspect.gsub(/\A\[(.+)\]\z/, '(\1)')}"
else
""
Expand Down
83 changes: 58 additions & 25 deletions lib/rspec/mocks/instance_method_stasher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,72 @@ module RSpec
module Mocks
# @private
class InstanceMethodStasher
attr_reader :original_method

def initialize(object, method)
@object = object
@method = method
@klass = (class << object; self; end)

@original_method = nil
@method_is_stashed = false
end

# @private
def method_is_stashed?
!!@original_method
end
attr_reader :original_method

# @private
def stash
return if !method_defined_directly_on_klass?
@original_method ||= ::RSpec::Mocks.method_handle_for(@object, @method)
if RUBY_VERSION.to_f < 1.9
# @private
def method_is_stashed?
@method_is_stashed
end

# @private
def stash
return if !method_defined_directly_on_klass? || @method_is_stashed

@klass.__send__(:alias_method, stashed_method_name, @method)
@method_is_stashed = true
end

# @private
def stashed_method_name
"obfuscated_by_rspec_mocks__#{@method}"
end
private :stashed_method_name

# @private
def restore
return unless @method_is_stashed

if @klass.__send__(:method_defined?, @method)
@klass.__send__(:undef_method, @method)
end
@klass.__send__(:alias_method, @method, stashed_method_name)
@klass.__send__(:remove_method, stashed_method_name)
@method_is_stashed = false
end
else

# @private
def method_is_stashed?
!!@original_method
end

# @private
def stash
return if !method_defined_directly_on_klass?
@original_method ||= ::RSpec::Mocks.method_handle_for(@object, @method)
end

# @private
def restore
return unless @original_method

if @klass.__send__(:method_defined?, @method)
@klass.__send__(:undef_method, @method)
end

@klass.__send__(:define_method, @method, @original_method)
@original_method = nil
end
end

private
Expand All @@ -41,7 +88,7 @@ def method_owned_by_klass?
# On Ruby 2.0.0+ the owner of a method on a class which has been
# `prepend`ed may actually be an instance, e.g.
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
owner = owner.class unless owner.is_a? Class
owner = owner.class unless Module === owner

# On some 1.9s (e.g. rubinius) aliased methods
# can report the wrong owner. Example:
Expand All @@ -59,20 +106,6 @@ def method_owned_by_klass?
# that the method is actually owned by @klass.
owner == @klass || !(method_defined_on_klass?(owner))
end

public

# @private
def restore
return unless @original_method

if @klass.__send__(:method_defined?, @method)
@klass.__send__(:undef_method, @method)
end

@klass.__send__(:define_method, @method, &@original_method)
@original_method = nil
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/message_expectation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def yield_receiver_to_implementation_block?
# counter.increment
# expect(counter.count).to eq(original_count + 1)
def and_call_original
if @method_double.object.is_a?(RSpec::Mocks::TestDouble)
if RSpec::Mocks::TestDouble === @method_double.object
@error_generator.raise_only_valid_on_a_partial_mock(:and_call_original)
else
@implementation = AndCallOriginalImplementation.new(@method_double.original_method)
Expand Down
8 changes: 6 additions & 2 deletions lib/rspec/mocks/method_double.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def initialize(object, method_name, proxy)
@object = object
@proxy = proxy

@original_visibility = nil
@method_stasher = InstanceMethodStasher.new(object, method_name)
@method_is_proxied = false
@expectations = []
Expand Down Expand Up @@ -72,15 +73,18 @@ def restore_original_method
object_singleton_class.__send__(:remove_method, @method_name)
if @method_stasher.method_is_stashed?
@method_stasher.restore
restore_original_visibility
end
restore_original_visibility

@method_is_proxied = false
end

# @private
def restore_original_visibility
return unless object_singleton_class.method_defined?(@method_name) || object_singleton_class.private_method_defined?(@method_name)
return unless @original_visibility &&
(object_singleton_class.method_defined?(@method_name) ||
object_singleton_class.private_method_defined?(@method_name))

object_singleton_class.__send__(*@original_visibility)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/rspec/mocks/mutate_const.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def recursive_const_get(const_name)

def recursive_const_defined?(const_name)
normalize_const_name(const_name).split('::').inject([Object, '']) do |(mod, full_name), name|
yield(full_name, name) if block_given? && !mod.is_a?(Module)
yield(full_name, name) if block_given? && !(Module === mod)
return false unless const_defined_on?(mod, name)
[get_const_defined_on(mod, name), [mod, name].join('::')]
end
Expand Down Expand Up @@ -291,7 +291,7 @@ def verify_constants_to_transfer!
end
end

if @transfer_nested_constants.is_a?(Array)
if Array === @transfer_nested_constants
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)

Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def message_received(message, *args, &block)
elsif stub = find_almost_matching_stub(message, *args)
stub.advise(*args)
raise_missing_default_stub_error(stub, *args)
elsif @object.is_a?(Class)
elsif Class === @object
@object.superclass.__send__(message, *args, &block)
else
@object.__send__(:method_missing, message, *args, &block)
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/mocks/test_double.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def __build_mock_proxy
private

def __initialize_as_test_double(name=nil, stubs={})
if name.is_a?(Hash) && stubs.empty?
if Hash === name && stubs.empty?
stubs = name
@name = nil
else
Expand Down
4 changes: 2 additions & 2 deletions spec/rspec/mocks/instance_method_stasher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ def obj.hello; :overridden_hello; end;
expect(obj.hello).to eql :overridden_hello
end

it "does not unnecessarily create obfuscated aliased methods" do
it "does not unnecessarily create obfuscated aliased methods", :if => (RUBY_VERSION.to_f > 1.8) do
obj = Object.new
def obj.hello; :hello_defined_on_singleton_class; end;

stashed_method = InstanceMethodStasher.new(singleton_class_for(obj), :hello)
stashed_method = stasher_for(obj, :hello)

expect {
stashed_method.stash
Expand Down
19 changes: 19 additions & 0 deletions spec/rspec/mocks/stub_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,25 @@ def existing_private_instance_method
expect(@instance.existing_instance_method).to eq(:original_value)
end

it "restores existing singleton methods with the appropriate context" do
klass = Class.new do
def self.say_hello
@hello if defined?(@hello)
end
end

subclass = Class.new(klass)

subclass.instance_variable_set(:@hello, "Hello")
expect(subclass.say_hello).to eq("Hello")

klass.stub(:say_hello) { "Howdy" }
expect(subclass.say_hello).to eq("Howdy")

reset klass
expect(subclass.say_hello).to eq("Hello")
end

it "restores existing private instance methods" do
# See bug reports 8302 and 7805
@instance.stub(:existing_private_instance_method) { :stub_value }
Expand Down

0 comments on commit 8a57102

Please sign in to comment.