spy_rb 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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