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