lib/spy/subroutine.rb in spy-0.2.1 vs lib/spy/subroutine.rb in spy-0.2.2
- old
+ new
@@ -36,14 +36,15 @@
def hook(opts = {})
@hook_opts = opts
raise "#{base_object} method '#{method_name}' has already been hooked" if hooked?
hook_opts[:force] ||= base_object.is_a?(Double)
- if (base_object_respond_to?(method_name, true)) || !hook_opts[:force]
+ hook_opts[:visibility] ||= original_method_visibility
+
+ if original_method_visibility || !hook_opts[:force]
@original_method = current_method
end
- hook_opts[:visibility] ||= method_visibility
base_object.send(define_method_with, method_name, override_method)
if [:public, :protected, :private].include? hook_opts[:visibility]
method_owner.send(hook_opts[:visibility], method_name)
@@ -59,11 +60,11 @@
def unhook
raise "'#{method_name}' method has not been hooked" unless hooked?
if original_method && method_owner == original_method.owner
original_method.owner.send(:define_method, method_name, original_method)
- original_method.owner.send(method_visibility, method_name) if method_visibility
+ original_method.owner.send(original_method_visibility, method_name) if original_method_visibility
else
method_owner.send(:remove_method, method_name)
end
clear_method!
Agency.instance.retire(self)
@@ -81,22 +82,14 @@
#
# Tells the spy to return a value when the method is called.
#
# @return [self]
def and_return(value = nil)
+ raise ArgumentError.new("value and block conflict. Choose one") if !(value.nil? || value.is_a?(Hash) && value.has_key?(:force)) && block_given?
if block_given?
@plan = Proc.new
- if value.nil? || value.is_a?(Hash) && value.has_key?(:force)
- if !(value.is_a?(Hash) && value[:force]) &&
- original_method &&
- original_method.arity >=0 &&
- @plan.arity > original_method.arity
- raise ArgumentError.new "The original method only has an arity of #{original_method.arity} you have an arity of #{@plan.arity}"
- end
- else
- raise ArgumentError.new("value and block conflict. Choose one") if !value.nil?
- end
+ check_for_too_many_arguments!(@plan)
else
@plan = Proc.new { value }
end
self
end
@@ -112,15 +105,14 @@
end
# tells the spy to call the original method
# @return [self]
def and_call_through
- raise "can only call through if original method is set" unless method_visibility
- if original_method
- @plan = original_method
- else
- @plan = Proc.new do |*args, &block|
+ @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
self
end
@@ -180,13 +172,16 @@
# 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)
- result = @plan ? @plan.call(*args, &block) : nil
+ result = if @plan
+ check_for_too_many_arguments!(@plan)
+ @plan.call(*args, &block)
+ end
ensure
- calls << CallLog.new(object,called_from, args, block, result)
+ calls << CallLog.new(object, called_from, args, block, result)
end
# reset the call log
def reset!
@was_hooked = false
@@ -204,60 +199,79 @@
__method_spy__.invoke(self, __spy_args_#{self.object_id}, block, caller(1)[0])
end
METHOD
end
- def call_with_yield(&block)
- raise "no block sent" unless block
- value = nil
- @args_to_yield.each do |args|
- if block.arity > -1 && args.length != block.arity
- @error_generator.raise_wrong_arity_error args, block.arity
- end
- value = @eval_context ? @eval_context.instance_exec(*args, &block) : block.call(*args)
- end
- value
- end
-
def clear_method!
@hooked = false
- @hook_opts = @original_method = @arity_range = @method_visibility = @method_owner= nil
+ @hook_opts = @original_method = @arity_range = @original_method_visibility = @method_owner= nil
end
- def method_visibility
- @method_visibility ||=
- if base_object_respond_to?(method_name)
- if original_method && original_method.owner.protected_method_defined?(method_name)
- :protected
- else
- :public
- end
- elsif base_object_respond_to?(method_name, true)
- :private
- end
+ def original_method_visibility
+ @original_method_visibility ||= method_visibility_of(method_name)
end
- def base_object_respond_to?(method_name, include_private = false)
+ def method_visibility_of(method_name, all = true)
if @singleton_method
- base_object.respond_to?(method_name, include_private)
+ if base_object.public_methods(all).include?(method_name)
+ :public
+ elsif base_object.protected_methods(all).include?(method_name)
+ :protected
+ elsif base_object.private_methods(all).include?(method_name)
+ :private
+ end
else
- base_object.instance_methods.include?(method_name) || (
- include_private && base_object.private_instance_methods.include?(method_name)
- )
+ if base_object.public_instance_methods(all).include?(method_name)
+ :public
+ elsif base_object.protected_instance_methods(all).include?(method_name)
+ :protected
+ elsif base_object.private_instance_methods(all).include?(method_name)
+ :private
+ end
end
end
def define_method_with
@singleton_method ? :define_singleton_method : :define_method
end
def check_arity!(arity)
- self.class.check_arity_against_range!(arity_range, arity)
+ return unless arity_range
+ if arity < arity_range.min
+ raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.min})")
+ elsif arity > arity_range.max
+ raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.max})")
+ end
end
+ def check_for_too_many_arguments!(block)
+ return unless arity_range
+ min_arity = block.arity
+ min_arity = min_arity.abs - 1 if min_arity < 0
+
+ if min_arity > arity_range.max
+ raise ArgumentError.new("block requires #{min_arity} arguments while original_method require a maximum of #{arity_range.max}")
+ end
+ end
+
def arity_range
- @arity_range ||= self.class.arity_range_of(original_method) if original_method
+ @arity_range ||=
+ if original_method
+ min = max = 0
+ original_method.parameters.each do |type,_|
+ case type
+ when :req
+ min += 1
+ max += 1
+ when :opt
+ max += 1
+ when :rest
+ max = Float::INFINITY
+ end
+ end
+ (min..max)
+ end
end
def current_method
@singleton_method ? base_object.method(method_name) : base_object.instance_method(method_name)
end
@@ -265,38 +279,10 @@
def method_owner
@method_owner ||= current_method.owner
end
class << self
- # @private
- def arity_range_of(block)
- raise "#{block.inspect} does not respond to :parameters" unless block.respond_to?(:parameters)
- min = max = 0
- block.parameters.each do |type,_|
- case type
- when :req
- min += 1
- max += 1
- when :opt
- max += 1
- when :rest
- max = Float::INFINITY
- end
- end
- (min..max)
- end
-
- # @private
- def check_arity_against_range!(arity_range, arity)
- return unless arity_range
- if arity < arity_range.min
- raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.min})")
- elsif arity > arity_range.max
- raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.max})")
- end
- end
-
# retrieve the method spy from an object
# @param base_object
# @param method_name [Symbol]
# @param singleton_method [Boolean] this a singleton method or a instance method?
# @return [Array<Subroutine>]
@@ -314,15 +300,21 @@
end
end
# retrieve all the spies from a given object
# @param base_object
+ # @param singleton_method [Boolean] (true) only get singleton_method_spies
# @return [Array<Subroutine>]
- def get_spies(base_object)
+ def get_spies(base_object, singleton_methods = true)
+ if singleton_methods
all_methods = base_object.public_methods(false) +
base_object.protected_methods(false) +
base_object.private_methods(false)
- all_methods += base_object.instance_methods(false) + base_object.private_instance_methods(false) if base_object.respond_to?(:instance_methods)
+ else
+ all_methods = base_object.instance_methods(false) +
+ base_object.private_instance_methods(false)
+ end
+
all_methods.map do |method_name|
Agency.instance.find(get_spy_id(base_object.method(method_name)))
end.compact
end