Skip to content

Commit

Permalink
Merge e2ac7a8 into 4b305ab
Browse files Browse the repository at this point in the history
  • Loading branch information
jbodah committed Jan 31, 2015
2 parents 4b305ab + e2ac7a8 commit 266c80f
Show file tree
Hide file tree
Showing 10 changed files with 657 additions and 253 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,5 @@ end
- record call signatures/return values
- checking return values

- add exceptions around .on_any_instance
- clean up tests around when {}, call count, exclusive spying (e.g. one instance/including class and not the other)
13 changes: 8 additions & 5 deletions lib/spy/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ module API
def on(*args)
case args.length
when 2
return core.add_spy *(args << :method)
spied, msg = *args
return core.add_spy(spied, spied.method(msg))
end
raise ArgumentError
end

# TODO docs
def on_any_instance(mod, msg)
core.add_spy(mod, msg, :instance_method)
def on_any_instance(spied, msg)
core.add_spy(spied, spied.instance_method(msg))
end

# Stops spying on the method and restores its original functionality
Expand All @@ -37,9 +38,11 @@ def restore(*args)
when 1
return core.remove_all_spies if args.first == :all
when 2
return core.remove_spy *(args << :method)
spied, msg = *args
return core.remove_spy(spied, spied.method(msg))
when 3
return core.remove_spy *args
spied, msg, method_type = *args
return core.remove_spy(spied, spied.send(method_type, msg))
end
raise ArgumentError
end
Expand Down
12 changes: 9 additions & 3 deletions lib/spy/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ def each
# Add a slicker interface that abstracts away Collection::Entry
module SpyHelper
def <<(spy)
entry = Collection::Entry.new(spy.receiver, spy.msg, spy.method_type)
receiver = spy.original.is_a?(Method) ? spy.original.receiver : nil
name = spy.original.name
klass = spy.original.class
entry = Collection::Entry.new(receiver, name, klass)
entry.value = spy
insert entry
end

def pop(receiver, msg, method_type)
remove Collection::Entry.new(receiver, msg, method_type)
def pop(method)
receiver = method.is_a?(Method) ? method.receiver : nil
name = method.name
klass = method.class
remove Collection::Entry.new(receiver, name, klass)
end
end

Expand Down
8 changes: 4 additions & 4 deletions lib/spy/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ def spy_collection
@spy_collection ||= Collection.new
end

def add_spy(receiver, msg, method_type)
spy = Instance.new(receiver, msg, method_type)
def add_spy(spied, method)
spy = Instance.new(spied, method)
spy_collection << spy
spy.start
end

def remove_spy(receiver, msg, method_type)
spy = spy_collection.pop(receiver, msg, method_type)
def remove_spy(spied, method)
spy = spy_collection.pop(method)
spy.stop
end

Expand Down
39 changes: 11 additions & 28 deletions lib/spy/instance.rb
Original file line number Diff line number Diff line change
@@ -1,48 +1,31 @@
require 'spy/callbacks/with_args'
require 'spy/callbacks/when'
require 'spy/instance/strategy'

# An instance of a spied method
# - Holds a reference to the original method
# - Wraps the original method
# - Provides hooks for callbacks
module Spy
class Instance
attr_reader :receiver, :method_type, :msg, :original, :call_count, :visibility
attr_reader :original, :spied, :strategy, :call_count, :visibility

def initialize(receiver, msg, method_type)
@msg = msg
@receiver = receiver
@method_type = method_type
def initialize(spied, original)
@spied = spied
@original = original
@visibility = extract_visibility
@before_filters = []
@call_count = 0

# Cache the original method for unwrapping later
@original = @receiver.send(method_type, msg)
@visibility = extract_visibility
@strategy = Strategy.factory_build(self)
end

def start
context = self
original.owner.instance_eval do
define_method context.msg do |*args|
context.before_call(*args)
if context.original.respond_to? :bind
result = context.original.bind(self).call(*args)
else
result = context.original.call(*args)
end
result
end
send(context.visibility, context.msg)
end
@strategy.apply
self
end

def stop
context = self
original.owner.instance_eval do
define_method context.msg, context.original
end
@strategy.undo
self
end

Expand All @@ -69,11 +52,11 @@ def extract_visibility
owner = @original.owner
[:public, :protected, :private].each do |vis|
query = "#{vis}_method_defined?"
if owner.respond_to?(query) && owner.send(query, @msg)
if owner.respond_to?(query) && owner.send(query, @original.name)
return vis
end
end
raise NoMethodError, "couldn't find method #{@msg} belonging to #{owner}"
raise NoMethodError, "couldn't find method #{@original.name} belonging to #{owner}"
end
end
end
26 changes: 26 additions & 0 deletions lib/spy/instance/strategy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'spy/instance/strategy/wrap'
require 'spy/instance/strategy/intercept'

module Spy
class Instance
module Strategy
class << self
def factory_build(spy)
if spy.original.is_a?(Method)
if spy.original.owner == spy.spied.singleton_class
Strategy::Wrap.new(spy)
else
Strategy::Intercept.new(spy, spy.spied.singleton_class)
end
else
if spy.original.owner == spy.spied
Strategy::Wrap.new(spy)
else
Strategy::Intercept.new(spy, spy.spied)
end
end
end
end
end
end
end
30 changes: 30 additions & 0 deletions lib/spy/instance/strategy/intercept.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Spy
class Instance
module Strategy
class Intercept
def initialize(spy, intercept_target)
@spy = spy
@intercept_target = intercept_target
end

def apply
spy = @spy
@intercept_target.class_eval do
define_method spy.original.name do |*args|
spy.before_call(*args)
spy.original.call(*args)
end
send(spy.visibility, spy.original.name)
end
end

def undo
spy = @spy
@intercept_target.class_eval do
remove_method spy.original.name
end
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/spy/instance/strategy/wrap.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Spy
class Instance
module Strategy
class Wrap
def initialize(spy)
@spy = spy
end

def apply
spy = @spy
spy.original.owner.class_eval do
define_method spy.original.name do |*args|
spy.before_call(*args)
if spy.original.is_a?(UnboundMethod)
spy.original.bind(self).call(*args)
else
spy.original.call(*args)
end
end
send(spy.visibility, spy.original.name)
end
end

def undo
spy = @spy
spy.original.owner.class_eval do
remove_method spy.original.name
define_method spy.original.name, spy.original
send(spy.visibility, spy.original.name)
end
end
end
end
end
end
Loading

0 comments on commit 266c80f

Please sign in to comment.