lib/spy/subroutine.rb in spy-1.0.1 vs lib/spy/subroutine.rb in spy-1.0.2
- old
+ new
@@ -31,10 +31,11 @@
# @param singleton_method <Boolean> spy on the singleton method or the normal method
def initialize(object, method_name, singleton_method = true)
@base_object, @method_name = object, method_name
@singleton_method = singleton_method
@plan = nil
+ @call_through = false
reset!
end
# hooks the method into the object and stashes original method if it exists
# @param [Hash] opts what do do when hooking into a method
@@ -56,18 +57,18 @@
original_method.owner.send(:remove_method, method_name)
end
if singleton_method
if base_object.singleton_class.method_defined?(method_name) || base_object.singleton_class.private_method_defined?(method_name)
- base_object.singleton_class.alias_method(method_name, method_name)
+ base_object.singleton_class.send(:alias_method, method_name, method_name)
end
base_object.define_singleton_method(method_name, override_method)
else
if base_object.method_defined?(method_name) || base_object.private_method_defined?(method_name)
- base_object.alias_method(method_name, method_name)
+ base_object.send(:alias_method, method_name, method_name)
end
- base_object.define_method(method_name, override_method)
+ base_object.send(:define_method, method_name, override_method)
end
if [:public, :protected, :private].include? hook_opts[:visibility]
method_owner.send(hook_opts[:visibility], method_name)
end
@@ -146,32 +147,11 @@
end
# tells the spy to call the original method
# @return [self]
def and_call_through
- if @base_object.is_a? Class
- @plan = Proc.new do |object, *args, &block|
- if original_method
- if original_method.is_a? UnboundMethod
- bound_method = original_method.bind(object)
- bound_method.call(*args, &block)
- else
- original_method.call(*args, &block)
- end
- else
- base_object.send(:method_missing, method_name, *args, &block)
- end
- end
- else
- @plan = Proc.new do |*args, &block|
- if original_method
- original_method.call(*args, &block)
- else
- base_object.send(:method_missing, method_name, *args, &block)
- end
- end
- end
+ @call_through = true
self
end
# @overload and_raise
@@ -230,21 +210,21 @@
# invoke that the method has been called. You really shouldn't use this
# method.
def invoke(object, args, block, called_from)
check_arity!(args.size)
- if base_object.is_a? Class
- result = if @plan
- check_for_too_many_arguments!(@plan)
- @plan.call(object, *args, &block)
- end
- else
- result = if @plan
- check_for_too_many_arguments!(@plan)
- @plan.call(*args, &block)
- end
- end
+ result =
+ if @call_through
+ call_plan(build_call_through_plan(object), block, *args)
+ elsif @plan
+ check_for_too_many_arguments!(@plan)
+ if base_object.is_a? Class
+ call_plan(@plan, block, object, *args)
+ else
+ call_plan(@plan, block, *args)
+ end
+ end
ensure
calls << CallLog.new(object, called_from, args, block, result)
end
# reset the call log
@@ -351,9 +331,42 @@
singleton_method ? base_object.method(method_name) : base_object.instance_method(method_name)
end
def method_owner
@method_owner ||= current_method.owner
+ end
+
+ def build_call_through_plan(object)
+ if original_method
+ if @base_object.is_a?(Class) && original_method.is_a?(UnboundMethod)
+ original_method.bind(object)
+ else
+ original_method
+ end
+ else
+ Proc.new do |*args, &block|
+ base_object.send(:method_missing, method_name, *args, &block)
+ end
+ end
+ end
+
+ def call_plan(plan, block, *args)
+ if ruby_27_last_arg_hash?(args)
+ *prefix, last = args
+ plan.call(*prefix, **last, &block)
+ else
+ plan.call(*args, &block)
+ end
+ end
+
+ # Ruby 2.7 gives a deprecation warning about passing hash as last argument for a method
+ # with a double-splat operator (**), and Ruby 3 raises an ArgumentError exception.
+ # This checks if args has a hash as last element to extract it and pass it with double-splat to avoid an exception.
+ def ruby_27_last_arg_hash?(args)
+ last = args.last
+ last.instance_of?(Hash) &&
+ !last.empty? &&
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
end
class << self
# retrieve the method spy from an object or create a new one