spy_rb 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1fc7717987d2a804c55203a19a3a665d33f96f58
4
- data.tar.gz: 517c1723f922c931ff5e745694b491b289798a7e
3
+ metadata.gz: 17d070307a491deecda090c82e836fdef23ee5cd
4
+ data.tar.gz: c50f4e881b3a1387ca83918a6f4213ccab5ca7bd
5
5
  SHA512:
6
- metadata.gz: 75da4d827d5b93e47dfe145321f17d8c8a41fca8f550477e9e3d10c7922c7a7567dcce3a7da3c645a322584079ad810b699d4e9b5ad35aa28505d0af40f4b8fe
7
- data.tar.gz: e91d8363fff8c5675af653b778d47447da235db493d6325d1d19fec06b0c4f8e4e8447910bcfc5ad2dd26052a8bce6c9743c9de88f7afd660f00cc93fdb367ad
6
+ metadata.gz: 28e4f0bc37039af35f5b6a0c75d14cc85148306bb34ddb4dd270c452cd7452864226601a70392620c300f2618ab7d5119520cc8aa0f1f79cbed99fe1aaa7ba11
7
+ data.tar.gz: 160210e00e62cc3effe46a3130d59bd746d0ca0d280ed9996cd858d2a39a4c9c4855921a15f91ae3e7f41afb28b0f2daf3e0163372f80a5aebed30c318a8fa53
data/lib/spy/api.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'spy/core'
2
+ require 'spy/blueprint'
2
3
 
3
4
  module Spy
4
5
  # The core module that users will interface. `Spy::API` is implemented
@@ -20,7 +21,13 @@ module Spy
20
21
  # @param [Symbol] msg - the name of the method to spy on
21
22
  # @returns [Spy::Instance]
22
23
  def on(target, msg)
23
- core.add_spy(target, target.method(msg))
24
+ if target.methods.include?(msg)
25
+ core.add_spy(Blueprint.new(target, msg, :method))
26
+ elsif target.respond_to?(msg)
27
+ core.add_spy(Blueprint.new(target, msg, :dynamic_delegation))
28
+ else
29
+ raise ArgumentError
30
+ end
24
31
  end
25
32
 
26
33
  # Spies on calls to a method made on any instance of some class or module
@@ -30,7 +37,23 @@ module Spy
30
37
  # @returns [Spy::Instance]
31
38
  def on_any_instance(target, msg)
32
39
  raise ArgumentError unless target.respond_to?(:instance_method)
33
- core.add_spy(target, target.instance_method(msg))
40
+ core.add_spy(Blueprint.new(target, msg, :instance_method))
41
+ end
42
+
43
+ # Spies on all of the calls made to the given object
44
+ #
45
+ # @param object - the thing to spy on
46
+ # @returns [Spy::Multi]
47
+ def on_object(object)
48
+ core.add_multi_spy(Blueprint.new(object, :all, :methods))
49
+ end
50
+
51
+ # Spies on all of the calls made to the given class or module
52
+ #
53
+ # @param klass - the thing to spy on
54
+ # @returns [Spy::Multi]
55
+ def on_class(klass)
56
+ core.add_multi_spy(Blueprint.new(klass, :all, :instance_methods))
34
57
  end
35
58
 
36
59
  # Stops spying on the method and restores its original functionality
@@ -43,9 +66,9 @@ module Spy
43
66
  #
44
67
  # Spy.restore(receiver, msg)
45
68
  #
46
- # @example stop spying on the given object, message, and method type (e.g. :instance_method)
69
+ # @example stop spying on the given object, message, and type (e.g. :method, :instance_method, :dynamic_delegation)
47
70
  #
48
- # Spy.restore(object, msg, method_type)
71
+ # Spy.restore(object, msg, type)
49
72
  #
50
73
  # @param args - supports multiple signatures
51
74
  def restore(*args)
@@ -54,10 +77,10 @@ module Spy
54
77
  core.remove_all_spies if args.first == :all
55
78
  when 2
56
79
  target, msg = *args
57
- core.remove_spy(target, target.method(msg))
80
+ core.remove_spy(Blueprint.new(target, msg, :method))
58
81
  when 3
59
- target, msg, method_type = *args
60
- core.remove_spy(target, target.send(method_type, msg))
82
+ target, msg, type = *args
83
+ core.remove_spy(Blueprint.new(target, msg, type))
61
84
  else
62
85
  raise ArgumentError
63
86
  end
@@ -0,0 +1,22 @@
1
+ module Spy
2
+ class Blueprint
3
+ attr_reader :target, :msg, :type
4
+
5
+ def initialize(target, msg, type)
6
+ @target = target
7
+ @msg = msg
8
+ @type = type
9
+ @caller = _caller
10
+ end
11
+
12
+ alias :_caller :caller
13
+
14
+ def caller
15
+ @caller
16
+ end
17
+
18
+ def to_s
19
+ [@target.object_id, @msg, @type].join("|")
20
+ end
21
+ end
22
+ end
data/lib/spy/core.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'spy/instance'
2
2
  require 'spy/registry'
3
+ require 'spy/multi'
3
4
  require 'spy/errors'
4
5
 
5
6
  module Spy
@@ -9,43 +10,60 @@ module Spy
9
10
  # Syntactic sugar (like `Spy.restore(object, msg)` vs `Spy.restore(:all)`)
10
11
  # should be handled in `Spy::API` and utilize `Spy::Core`
11
12
  class Core
13
+ UNSAFE_METHODS = [:object_id, :__send__, :__id__, :method, :singleton_class]
14
+
15
+ def initialize
16
+ @registry = Registry.new
17
+ end
18
+
12
19
  # Start spying on the given object and method
13
20
  #
14
- # @param [Object] object - the object to spy on
15
- # @param [Method, UnboundMethod] method - the method to spy on
21
+ # @param [Spy::Blueprint] blueprint - data for building the spy
16
22
  # @returns [Spy::Instance]
17
23
  # @raises [Spy::Errors::AlreadySpiedError] if the method is already
18
24
  # being spied on
19
- def add_spy(object, method)
20
- raise Errors::AlreadySpiedError if registry.include?(object, method)
21
- spy = Instance.new(object, method)
22
- registry.insert(object, method, spy)
25
+ def add_spy(blueprint)
26
+ if prev = @registry.get(blueprint)
27
+ raise Errors::AlreadySpiedError.new("Already spied on here:\n\t#{prev[0].caller.join("\n\t")}")
28
+ end
29
+ spy = Instance.new(blueprint)
30
+ @registry.insert(blueprint, spy)
23
31
  spy.start
24
32
  end
25
33
 
34
+ # Start spying on all of the given objects and methods
35
+ #
36
+ # @param [Spy::Blueprint] blueprint - data for building the spy
37
+ # @returns [Spy::Multi]
38
+ def add_multi_spy(multi_blueprint)
39
+ target = multi_blueprint.target
40
+ type = multi_blueprint.type
41
+ methods = target.public_send(type).reject(&method(:unsafe_method?))
42
+ spies = methods.map do |method_name|
43
+ singular_type = type.to_s.sub(/s$/, '').to_sym
44
+ add_spy(Blueprint.new(multi_blueprint.target, method_name, singular_type))
45
+ end
46
+ Multi.new(spies)
47
+ end
48
+
26
49
  # Stop spying on the given object and method
27
50
  #
28
- # @param [Object] object - the object being spied on
29
- # @param [Method, UnboundMethod] method - the method to stop spying on
30
51
  # @raises [Spy::Errors::MethodNotSpiedError] if the method is not already
31
52
  # being spied on
32
- def remove_spy(object, method)
33
- spy = registry.remove(object, method)
53
+ def remove_spy(blueprint)
54
+ spy = @registry.remove(blueprint)
34
55
  spy.stop
35
56
  end
36
57
 
37
58
  # Stops spying on all objects and methods
38
- #
39
- # @raises [Spy::Errors::UnableToEmptySpyRegistryError] if for some reason
40
- # a spy was not removed
41
59
  def remove_all_spies
42
- registry.remove_all(&:stop)
60
+ @registry.remove_all.each(&:stop)
43
61
  end
44
62
 
45
63
  private
46
64
 
47
- def registry
48
- @registry ||= Registry.new
65
+ def unsafe_method?(name)
66
+ UNSAFE_METHODS.include?(name)
49
67
  end
50
68
  end
51
69
  end
@@ -0,0 +1,16 @@
1
+ module Spy
2
+ module DetermineVisibility
3
+ # @param [Method, UnboundMethod] method
4
+ # @returns [Symbol] whether the method is public, private, or protected
5
+ def self.call(method)
6
+ owner = method.owner
7
+ %w(public private protected).each do |vis|
8
+ query = "#{vis}_method_defined?"
9
+ if owner.respond_to?(query) && owner.send(query, method.name)
10
+ return vis
11
+ end
12
+ end
13
+ raise NoMethodError, "couldn't find method #{method.name} belonging to #{owner}"
14
+ end
15
+ end
16
+ end
data/lib/spy/errors.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Spy
2
2
  module Errors
3
- MethodNotSpiedError = Class.new(StandardError)
4
- AlreadySpiedError = Class.new(StandardError)
5
- UnableToEmptySpyRegistryError = Class.new(StandardError)
3
+ Error = Class.new(StandardError)
4
+ MethodNotSpiedError = Class.new(Spy::Errors::Error)
5
+ AlreadySpiedError = Class.new(Spy::Errors::Error)
6
6
  end
7
7
  end
@@ -0,0 +1,14 @@
1
+ module Spy
2
+ class FakeMethod
3
+ attr_reader :name
4
+
5
+ def initialize(name, &block)
6
+ @name = name
7
+ @block = block
8
+ end
9
+
10
+ def call(*args, &block)
11
+ @block.call(*args, &block)
12
+ end
13
+ end
14
+ end
data/lib/spy/instance.rb CHANGED
@@ -1,5 +1,6 @@
1
- require 'spy/instance/strategy'
2
- require 'spy/instance/api/internal'
1
+ require 'spy/fake_method'
2
+ require 'spy/strategy/wrap'
3
+ require 'spy/strategy/intercept'
3
4
 
4
5
  # An instance of a spied method
5
6
  # - Holds a reference to the original method
@@ -7,21 +8,30 @@ require 'spy/instance/api/internal'
7
8
  # - Provides hooks for callbacks
8
9
  module Spy
9
10
  class Instance
10
- include API::Internal
11
+ attr_reader :original, :spied, :strategy, :call_history
11
12
 
12
- attr_reader :original, :spied, :strategy, :visibility, :call_history
13
+ def initialize(blueprint)
14
+ original =
15
+ case blueprint.type
16
+ when :dynamic_delegation
17
+ FakeMethod.new(blueprint.msg) { |*args, &block| blueprint.target.method_missing(blueprint.msg, *args, &block) }
18
+ when :instance_method
19
+ blueprint.target.instance_method(blueprint.msg)
20
+ else
21
+ blueprint.target.method(blueprint.msg)
22
+ end
13
23
 
14
- def initialize(spied, original)
15
- @spied = spied
16
24
  @original = original
17
- @visibility = extract_visibility
18
- @conditional_filters = []
19
- @before_callbacks = []
20
- @after_callbacks = []
21
- @around_procs = []
25
+ @spied = blueprint.target
26
+ @strategy = choose_strategy(blueprint)
22
27
  @call_history = []
23
- @strategy = Strategy.factory_build(self)
24
- @instead = nil
28
+
29
+ @internal = {}
30
+ @internal[:conditional_filters] = []
31
+ @internal[:before_callbacks] = []
32
+ @internal[:after_callbacks]= []
33
+ @internal[:around_procs] = []
34
+ @internal[:instead]= nil
25
35
  end
26
36
 
27
37
  def name
@@ -47,42 +57,42 @@ module Spy
47
57
  end
48
58
 
49
59
  def when(&block)
50
- @conditional_filters << block
60
+ @internal[:conditional_filters] << block
51
61
  self
52
62
  end
53
63
 
54
64
  # Expect block to yield. Call the rest of the chain
55
65
  # when it does
56
66
  def wrap(&block)
57
- @around_procs << block
67
+ @internal[:around_procs] << block
58
68
  self
59
69
  end
60
70
 
61
71
  def before(&block)
62
- @before_callbacks << block
72
+ @internal[:before_callbacks] << block
63
73
  self
64
74
  end
65
75
 
66
76
  def after(&block)
67
- @after_callbacks << block
77
+ @internal[:after_callbacks] << block
68
78
  self
69
79
  end
70
80
 
71
81
  def instead(&block)
72
- @instead = block
82
+ @internal[:instead] = block
83
+ self
73
84
  end
74
85
 
75
86
  private
76
87
 
77
- def extract_visibility
78
- owner = @original.owner
79
- [:public, :protected, :private].each do |vis|
80
- query = "#{vis}_method_defined?"
81
- if owner.respond_to?(query) && owner.send(query, @original.name)
82
- return vis
83
- end
88
+ def choose_strategy(blueprint)
89
+ if blueprint.type == :dynamic_delegation
90
+ Strategy::Intercept.new(self)
91
+ elsif @original.owner == @spied || @original.owner == @spied.singleton_class
92
+ Strategy::Wrap.new(self)
93
+ else
94
+ Strategy::Intercept.new(self)
84
95
  end
85
- raise NoMethodError, "couldn't find method #{@original.name} belonging to #{owner}"
86
96
  end
87
97
  end
88
98
  end
@@ -1,14 +1,15 @@
1
1
  module Spy
2
2
  class MethodCall
3
- attr_reader :name, :receiver, :args, :block
3
+ attr_reader :name, :receiver, :caller, :args, :block
4
4
  attr_accessor :result
5
5
 
6
- def initialize(replayer, name, receiver, *args)
6
+ def initialize(replayer, name, receiver, method_caller, *args)
7
7
  @replayer = replayer
8
8
  @name = name
9
9
  @receiver = receiver
10
10
  @args = args
11
- @block = proc { receiver.instance_eval &Proc.new } if block_given?
11
+ @caller = method_caller
12
+ @block = proc { receiver.instance_eval(&Proc.new) } if block_given?
12
13
  end
13
14
 
14
15
  def replay
data/lib/spy/multi.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Spy
2
+ class Multi
3
+ attr_reader :spies
4
+
5
+ def initialize(spies)
6
+ @spies = spies
7
+ end
8
+
9
+ def call_count
10
+ @spies.map(&:call_count).reduce(&:+)
11
+ end
12
+
13
+ def [](name)
14
+ @spies.find { |spy| spy.name == name }
15
+ end
16
+
17
+ def uncalled
18
+ @spies.select { |spy| spy.call_count == 0 }
19
+ end
20
+ end
21
+ end
data/lib/spy/registry.rb CHANGED
@@ -1,56 +1,44 @@
1
1
  require 'spy/errors'
2
- require 'spy/registry_store'
3
- require 'spy/registry_entry'
4
2
 
5
3
  module Spy
6
4
  # Responsible for managing the top-level state of which spies exist.
7
5
  class Registry
6
+ def initialize
7
+ @store = {}
8
+ end
9
+
8
10
  # Keeps track of the spy for later management. Ensures spy uniqueness
9
11
  #
10
- # @param [Object] spied - the object being spied on
11
- # @param [Method, UnboundMethod] method - the method being spied on
12
+ # @param [Spy::Blueprint]
12
13
  # @param [Spy::Instance] spy - the instantiated spy
13
14
  # @raises [Spy::Errors::AlreadySpiedError] if the spy is already being
14
15
  # tracked
15
- def insert(spied, method, spy)
16
- entry = RegistryEntry.new(spied, method, spy)
17
- raise Errors::AlreadySpiedError if store.include? entry
18
- store.insert(entry)
16
+ def insert(blueprint, spy)
17
+ key = blueprint.to_s
18
+ raise Errors::AlreadySpiedError if @store[key]
19
+ @store[key] = [blueprint, spy]
19
20
  end
20
21
 
21
22
  # Stops tracking the spy
22
23
  #
23
- # @param [Object] spied - the object being spied on
24
- # @param [Method, UnboundMethod] method - the method being spied on
24
+ # @param [Spy::Blueprint]
25
25
  # @raises [Spy::Errors::MethodNotSpiedError] if the spy isn't being tracked
26
- def remove(spied, method)
27
- entry = RegistryEntry.new(spied, method, nil)
28
- raise Errors::MethodNotSpiedError unless store.include? entry
29
- store.remove(entry).spy
26
+ def remove(blueprint)
27
+ key = blueprint.to_s
28
+ raise Errors::MethodNotSpiedError unless @store[key]
29
+ @store.delete(key)[1]
30
30
  end
31
31
 
32
32
  # Stops tracking all spies
33
- #
34
- # @raises [Spy::Errors::UnableToEmptySpyRegistryError] if any spies were
35
- # still being tracked after removing all of the spies
36
33
  def remove_all
37
- store.map { |e| yield remove(e.spied, e.method) }
38
- raise Errors::UnableToEmptySpyRegistryError unless store.empty?
34
+ store = @store
35
+ @store = {}
36
+ store.values.map(&:last)
39
37
  end
40
38
 
41
- # Returns whether or not the object and method are already being spied on
42
- #
43
- # @returns [Boolean] whether or not the object and method are already being
44
- # spied on
45
- def include?(spied, method)
46
- entry = RegistryEntry.new(spied, method)
47
- store.include? entry
48
- end
49
-
50
- private
51
-
52
- def store
53
- @store ||= RegistryStore.new
39
+ def get(blueprint)
40
+ key = blueprint.to_s
41
+ @store[key]
54
42
  end
55
43
  end
56
44
  end
@@ -0,0 +1,89 @@
1
+ require 'spy/method_call'
2
+
3
+ module Spy
4
+ module Strategy
5
+ module Base
6
+ class << self
7
+ def call(spy, receiver, *args, &block)
8
+ spy.instance_eval do
9
+ # TODO - abstract the method call into an object and cache this in
10
+ # method using an instance variable instead of a local variable.
11
+ # This will let us be a bit more elegant about how we do before/after
12
+ # callbacks. We can also merge MethodCall with this responsibility so
13
+ # it isn't just a data struct
14
+ is_active = if @internal[:conditional_filters].any?
15
+ mc = Spy::Strategy::Base._build_method_call(spy, receiver, *args, &block)
16
+ @internal[:conditional_filters].all? { |f| f.call(mc) }
17
+ else
18
+ true
19
+ end
20
+
21
+ return Spy::Strategy::Base._call_original(spy, receiver, *args, &block) unless is_active
22
+
23
+ if @internal[:before_callbacks].any?
24
+ mc = Spy::Strategy::Base._build_method_call(spy, receiver, *args, &block)
25
+ @internal[:before_callbacks].each { |f| f.call(mc) }
26
+ end
27
+
28
+ if @internal[:around_procs].any?
29
+ mc = Spy::Strategy::Base._build_method_call(spy, receiver, *args, &block)
30
+
31
+ # Procify the original call
32
+ # Still return the result from it
33
+ result = nil
34
+ original_proc = proc do
35
+ result = Spy::Strategy::Base._call_and_record(spy, receiver, args, { :record => mc }, &block)
36
+ end
37
+
38
+ # Keep wrapping the original proc with each around_proc
39
+ @internal[:around_procs].reduce(original_proc) do |p, wrapper|
40
+ proc { wrapper.call(mc, &p) }
41
+ end.call
42
+ else
43
+ result = Spy::Strategy::Base._call_and_record(spy, receiver, args, &block)
44
+ end
45
+
46
+ if @internal[:after_callbacks].any?
47
+ mc = @call_history.last
48
+ @internal[:after_callbacks].each { |f| f.call(mc) }
49
+ end
50
+
51
+ result
52
+ end
53
+ end
54
+
55
+ def _build_method_call(spy, receiver, *args, &block)
56
+ Spy::MethodCall.new(
57
+ proc { Spy::Strategy::Base._call_original(spy, receiver, *args, &block) },
58
+ spy.original.name,
59
+ receiver,
60
+ caller[7..-1],
61
+ *args,
62
+ &block)
63
+ end
64
+
65
+ def _call_and_record(spy, receiver, args, opts = {}, &block)
66
+ spy.instance_eval do
67
+ if @internal[:instead]
68
+ @internal[:instead].call(Spy::Strategy::Base._build_method_call(spy, receiver, *args, &block))
69
+ else
70
+ record = opts[:record] || Spy::Strategy::Base._build_method_call(spy, receiver, *args, &block)
71
+ @call_history << record
72
+
73
+ result = Spy::Strategy::Base._call_original(spy, receiver, *args, &block)
74
+ record.result = result
75
+ end
76
+ end
77
+ end
78
+
79
+ def _call_original(spy, receiver, *args, &block)
80
+ if spy.original.is_a?(UnboundMethod)
81
+ spy.original.bind(receiver).call(*args, &block)
82
+ else
83
+ spy.original.call(*args, &block)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,44 @@
1
+ require 'spy/determine_visibility'
2
+ require 'spy/strategy/base'
3
+
4
+ module Spy
5
+ module Strategy
6
+ class Intercept
7
+ def initialize(spy)
8
+ @spy = spy
9
+ @target =
10
+ case spy.original
11
+ when Method
12
+ spy.spied.singleton_class
13
+ when UnboundMethod
14
+ spy.spied
15
+ when FakeMethod
16
+ spy.spied.singleton_class
17
+ end
18
+ end
19
+
20
+ def apply
21
+ spy = @spy
22
+ @target.class_eval do
23
+ # Add the spy to the intercept target
24
+ define_method spy.original.name do |*args, &block|
25
+ Spy::Strategy::Base.call(spy, self, *args, &block)
26
+ end
27
+
28
+ # Make the visibility of the spy match the spied original
29
+ unless spy.original.is_a?(FakeMethod)
30
+ visibility = DetermineVisibility.call(spy.original)
31
+ send(visibility, spy.original.name)
32
+ end
33
+ end
34
+ end
35
+
36
+ def undo
37
+ spy = @spy
38
+ @target.class_eval do
39
+ remove_method spy.original.name
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ require 'spy/determine_visibility'
2
+ require 'spy/strategy/base'
3
+
4
+ module Spy
5
+ module Strategy
6
+ class Wrap
7
+ def initialize(spy)
8
+ @spy = spy
9
+ @visibility = DetermineVisibility.call(spy.original)
10
+ end
11
+
12
+ def apply
13
+ spy = @spy
14
+ visibility = @visibility
15
+ @spy.original.owner.class_eval do
16
+ undef_method spy.original.name
17
+
18
+ # Replace the method with the spy
19
+ define_method spy.original.name do |*args, &block|
20
+ Spy::Strategy::Base.call(spy, self, *args, &block)
21
+ end
22
+
23
+ # Make the visibility of the spy match the spied original
24
+ send(visibility, spy.original.name)
25
+ end
26
+ end
27
+
28
+ def undo
29
+ spy = @spy
30
+ visibility = @visibility
31
+ spy.original.owner.class_eval do
32
+ remove_method spy.original.name
33
+ define_method spy.original.name, spy.original
34
+ send(visibility, spy.original.name)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/spy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Spy
2
- VERSION = '2.1.0'
2
+ VERSION = '3.0.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spy_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Bodah
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-01 00:00:00.000000000 Z
11
+ date: 2018-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -38,24 +38,10 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: minitest-tagz
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- description: Spy brings everything that's great about Sinon.JS to Ruby. Mocking frameworks
56
- work by stubbing out functionality. Spy works by listening in on functionality and
57
- allowing it to run in the background. Spy is designed to be lightweight and work
58
- alongside Mocking frameworks instead of trying to replace them entirely.
41
+ description: Mocking frameworks work by stubbing out functionality. Spy works by listening
42
+ in on functionality and allowing it to run in the background. Spy is designed to
43
+ be lightweight and work alongside Mocking frameworks instead of trying to replace
44
+ them entirely.
59
45
  email: jb3689@yahoo.com
60
46
  executables: []
61
47
  extensions: []
@@ -63,17 +49,18 @@ extra_rdoc_files: []
63
49
  files:
64
50
  - lib/spy.rb
65
51
  - lib/spy/api.rb
52
+ - lib/spy/blueprint.rb
66
53
  - lib/spy/core.rb
54
+ - lib/spy/determine_visibility.rb
67
55
  - lib/spy/errors.rb
56
+ - lib/spy/fake_method.rb
68
57
  - lib/spy/instance.rb
69
- - lib/spy/instance/api/internal.rb
70
- - lib/spy/instance/strategy.rb
71
- - lib/spy/instance/strategy/intercept.rb
72
- - lib/spy/instance/strategy/wrap.rb
73
58
  - lib/spy/method_call.rb
59
+ - lib/spy/multi.rb
74
60
  - lib/spy/registry.rb
75
- - lib/spy/registry_entry.rb
76
- - lib/spy/registry_store.rb
61
+ - lib/spy/strategy/base.rb
62
+ - lib/spy/strategy/intercept.rb
63
+ - lib/spy/strategy/wrap.rb
77
64
  - lib/spy/version.rb
78
65
  homepage: https://github.com/jbodah/spy_rb
79
66
  licenses:
@@ -95,8 +82,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
82
  version: '0'
96
83
  requirements: []
97
84
  rubyforge_project:
98
- rubygems_version: 2.4.8
85
+ rubygems_version: 2.5.2
99
86
  signing_key:
100
87
  specification_version: 4
101
- summary: SinonJS-style Test Spies for Ruby
88
+ summary: Test Spies for Ruby
102
89
  test_files: []
@@ -1,104 +0,0 @@
1
- require 'spy/method_call'
2
-
3
- module Spy
4
- class Instance
5
- module API
6
- # The API we expose internally to our collaborators
7
- module Internal
8
- # TODO: Not sure if this is the best place for this
9
- #
10
- # Defines the spy on the target object
11
- def attach_to(target)
12
- spy = self
13
- target.class_eval do
14
- define_method spy.original.name do |*args, &block|
15
- spy.call(self, *args, &block)
16
- end
17
- send(spy.visibility, spy.original.name)
18
- end
19
- end
20
-
21
- # Call the spied method using the given receiver and arguments.
22
- #
23
- # receiver is required to allow calling of UnboundMethods such as
24
- # instance methods defined on a Class
25
- def call(receiver, *args, &block)
26
- # TODO - abstract the method call into an object and cache this in
27
- # method using an instance variable instead of a local variable.
28
- # This will let us be a bit more elegant about how we do before/after
29
- # callbacks. We can also merge MethodCall with this responsibility so
30
- # it isn't just a data struct
31
- is_active = if @conditional_filters.any?
32
- mc = build_method_call(receiver, *args, &block)
33
- @conditional_filters.all? { |f| f.call(mc) }
34
- else
35
- true
36
- end
37
-
38
- return call_original(receiver, *args, &block) unless is_active
39
-
40
- if @before_callbacks.any?
41
- mc = build_method_call(receiver, *args, &block)
42
- @before_callbacks.each { |f| f.call(mc) }
43
- end
44
-
45
- if @around_procs.any?
46
- mc = build_method_call(receiver, *args, &block)
47
-
48
- # Procify the original call
49
- # Still return the result from it
50
- result = nil
51
- original_proc = proc do
52
- result = call_and_record(receiver, args, { :record => mc }, &block)
53
- end
54
-
55
- # Keep wrapping the original proc with each around_proc
56
- @around_procs.reduce(original_proc) do |p, wrapper|
57
- proc { wrapper.call(mc, &p) }
58
- end.call
59
- else
60
- result = call_and_record(receiver, args, &block)
61
- end
62
-
63
- if @after_callbacks.any?
64
- mc = @call_history.last
65
- @after_callbacks.each { |f| f.call(mc) }
66
- end
67
-
68
- result
69
- end
70
-
71
- private
72
-
73
- def build_method_call(receiver, *args, &block)
74
- Spy::MethodCall.new(
75
- proc { call_original(receiver, *args, &block) },
76
- original.name,
77
- receiver,
78
- *args,
79
- &block)
80
- end
81
-
82
- def call_and_record(receiver, args, opts = {}, &block)
83
- if @instead
84
- @instead.call build_method_call(receiver, *args, &block)
85
- else
86
- record = opts[:record] || build_method_call(receiver, *args, &block)
87
- @call_history << record
88
-
89
- result = call_original(receiver, *args, &block)
90
- record.result = result
91
- end
92
- end
93
-
94
- def call_original(receiver, *args, &block)
95
- if original.is_a?(UnboundMethod)
96
- original.bind(receiver).call(*args, &block)
97
- else
98
- original.call(*args, &block)
99
- end
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,23 +0,0 @@
1
- module Spy
2
- class Instance
3
- module Strategy
4
- class Intercept
5
- def initialize(spy, intercept_target)
6
- @spy = spy
7
- @intercept_target = intercept_target
8
- end
9
-
10
- def apply
11
- @spy.attach_to(@intercept_target)
12
- end
13
-
14
- def undo
15
- spy = @spy
16
- @intercept_target.class_eval do
17
- remove_method spy.original.name
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,24 +0,0 @@
1
- module Spy
2
- class Instance
3
- module Strategy
4
- class Wrap
5
- def initialize(spy)
6
- @spy = spy
7
- end
8
-
9
- def apply
10
- @spy.attach_to(@spy.original.owner)
11
- end
12
-
13
- def undo
14
- spy = @spy
15
- spy.original.owner.class_eval do
16
- remove_method spy.original.name
17
- define_method spy.original.name, spy.original
18
- send(spy.visibility, spy.original.name)
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
@@ -1,33 +0,0 @@
1
- require 'spy/instance/strategy/wrap'
2
- require 'spy/instance/strategy/intercept'
3
-
4
- module Spy
5
- class Instance
6
- module Strategy
7
- class << self
8
- def factory_build(spy)
9
- if spy.original.is_a?(Method)
10
- pick_strategy(spy, spy.spied.singleton_class)
11
- else
12
- pick_strategy(spy, spy.spied)
13
- end
14
- end
15
-
16
- private
17
-
18
- def pick_strategy(spy, spied_on)
19
- if spy.original.owner == spied_on
20
- # If the object we're spying on is the owner of
21
- # the method under spy then we need to wrap that
22
- # method
23
- Strategy::Wrap.new(spy)
24
- else
25
- # Otherwise we can intercept it by abusing the
26
- # inheritance hierarchy
27
- Strategy::Intercept.new(spy, spied_on)
28
- end
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,13 +0,0 @@
1
- module Spy
2
- # Isolates the format we serialize spies in when we track them
3
- class RegistryEntry < Struct.new(:spied, :method, :spy)
4
- def key
5
- receiver = method.is_a?(Method) ? method.receiver : nil
6
- "#{receiver.object_id}|#{method.name}|#{method.class}"
7
- end
8
-
9
- def ==(other)
10
- key == other.key
11
- end
12
- end
13
- end
@@ -1,28 +0,0 @@
1
- module Spy
2
- # Works with RegistryEntry abstractions to allow the
3
- # store data structure to be easily swapped
4
- class RegistryStore
5
- include Enumerable
6
-
7
- def initialize
8
- @internal = {}
9
- end
10
-
11
- def insert(entry)
12
- @internal[entry.key] = entry
13
- end
14
-
15
- def remove(entry)
16
- @internal.delete(entry.key)
17
- end
18
-
19
- def each
20
- return to_enum unless block_given?
21
- @internal.values.each { |v| yield v }
22
- end
23
-
24
- def empty?
25
- none?
26
- end
27
- end
28
- end