spy_rb 1.0.0 → 2.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: fb54969a1052380817f46a078bf3ebafe57acb2c
4
- data.tar.gz: e29c5522dfd48423fe78b387fc06ce29e005d16d
3
+ metadata.gz: fb161a2c4eabb953bcc5eefefba4927bb7159a56
4
+ data.tar.gz: 569fda1c72a9b7a80d3efde9f9c3980623c10f3d
5
5
  SHA512:
6
- metadata.gz: e415ad43572895dc1e3d94f25e61690b2b143fc32ac6fcdc581258b218db44eef37425afd44e0c05ea0ee5ab6e87e03e938ee4ced101eacb592ed625ffe2089f
7
- data.tar.gz: ff2432d2149adacf34bd528f6b5ea62464eff36dc6d5eeb429431d254977893079062cb74b9f322f0230098df1e96fc3846053a42398f834bd495298b23a2b1b
6
+ metadata.gz: bf682739d04fb7f083606dba03ea4be0d24482be2ab9bfd413fbcef9e1488ac2887d5d7bde04d44d2462416a34b0adece5a6a11f7eca259716c2b9d2b928c23e
7
+ data.tar.gz: 2b75eacf01aa6781efea38a9efb23dd2b8f9a88d831d26ace9a8e8af20e079cf8497c0b460d9b8b888bafac967bc24834c90cbc5bb238017677025dc3e618b72
data/lib/spy.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  require 'spy/version'
2
2
  require 'spy/api'
3
3
 
4
+ # Top-level module that implements the Spy::API
5
+ #
6
+ # Spy::API was pulled out to make it easy to create multiple
7
+ # different modules that implement Spy::API (which effectively
8
+ # namespaces the spies)
4
9
  module Spy
5
10
  extend API
6
11
  end
@@ -1,11 +1,24 @@
1
1
  require 'spy/core'
2
2
 
3
3
  module Spy
4
+ # The core module that users will interface. `Spy::API` is implemented
5
+ # in a module via `::extend`:
6
+ #
7
+ # MySpy.exted Spy::API
8
+ # spy = MySpy.on(Object, :name)
9
+ #
10
+ # By default `Spy` implements `Spy::API`
11
+ #
12
+ # `Spy::API` is primarily responsible for maps user arguments into
13
+ # a format that `Spy::Core` can understand
14
+ #
15
+ # See `Spy::Instance` for the API for interacting with individual spies
4
16
  module API
5
- # Spies on calls to a method made on an object
17
+ # Spies on calls to a method made on a target object
6
18
  #
7
- # @param target - the object you want to spy on
8
- # @param msg - the name of the method to spy on
19
+ # @param [Object] target - the object you want to spy on
20
+ # @param [Symbol] msg - the name of the method to spy on
21
+ # @returns [Spy::Instance]
9
22
  def on(target, msg)
10
23
  core.add_spy(target, target.method(msg))
11
24
  end
@@ -22,16 +35,19 @@ module Spy
22
35
 
23
36
  # Stops spying on the method and restores its original functionality
24
37
  #
25
- # @param args - supports multiple signatures
38
+ # @example stop spying on every spied message
26
39
  #
27
40
  # Spy.restore(:all)
28
- # => stops spying on every spied message
41
+ #
42
+ # @example stop spying on the given receiver and message
29
43
  #
30
44
  # Spy.restore(receiver, msg)
31
- # => stops spying on the given receiver and message (assumes :method)
32
45
  #
33
- # Spy.restore(reciever, msg, method_type)
34
- # => stops spying on the given receiver and message of method_type
46
+ # @example stop spying on the given object, message, and method type (e.g. :instance_method)
47
+ #
48
+ # Spy.restore(object, msg, method_type)
49
+ #
50
+ # @param args - supports multiple signatures
35
51
  def restore(*args)
36
52
  case args.length
37
53
  when 1
@@ -1,31 +1,51 @@
1
1
  require 'spy/instance'
2
- require 'spy/collection'
2
+ require 'spy/registry'
3
3
  require 'spy/errors'
4
4
 
5
5
  module Spy
6
+ # The main internal API. This is used directly by `Spy::API` and
7
+ # is the primary control center for creating and removing spies.
8
+ #
9
+ # Syntactic sugar (like `Spy.restore(object, msg)` vs `Spy.restore(:all)`)
10
+ # should be handled in `Spy::API` and utilize `Spy::Core`
6
11
  class Core
7
- def add_spy(spied, method)
8
- if collection.include?(spied, method)
9
- raise Errors::AlreadySpiedError
10
- end
11
- spy = Instance.new(spied, method)
12
- collection.insert(spied, method, spy)
12
+ # Start spying on the given object and method
13
+ #
14
+ # @param [Object] object - the object to spy on
15
+ # @param [Method, UnboundMethod] method - the method to spy on
16
+ # @returns [Spy::Instance]
17
+ # @raises [Spy::Errors::AlreadySpiedError] if the method is already
18
+ # 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)
13
23
  spy.start
14
24
  end
15
25
 
16
- def remove_spy(spied, method)
17
- spy = collection.remove(spied, method)
26
+ # Stop spying on the given object and method
27
+ #
28
+ # @param [Object] object - the object being spied on
29
+ # @param [Method, UnboundMethod] method - the method to stop spying on
30
+ # @raises [Spy::Errors::MethodNotSpiedError] if the method is not already
31
+ # being spied on
32
+ def remove_spy(object, method)
33
+ spy = registry.remove(object, method)
18
34
  spy.stop
19
35
  end
20
36
 
37
+ # Stops spying on all objects and methods
38
+ #
39
+ # @raises [Spy::Errors::UnableToEmptySpyRegistryError] if for some reason
40
+ # a spy was not removed
21
41
  def remove_all_spies
22
- collection.remove_all { |spy| spy.stop }
42
+ registry.remove_all(&:stop)
23
43
  end
24
44
 
25
45
  private
26
46
 
27
- def collection
28
- @collection ||= Collection.new
47
+ def registry
48
+ @registry ||= Registry.new
29
49
  end
30
50
  end
31
51
  end
@@ -1,7 +1,7 @@
1
1
  module Spy
2
2
  module Errors
3
- MethodNotSpiedError = Class.new(StandardError)
4
- AlreadySpiedError = Class.new(StandardError)
5
- UnableToEmptySpyCollection = Class.new(StandardError)
3
+ MethodNotSpiedError = Class.new(StandardError)
4
+ AlreadySpiedError = Class.new(StandardError)
5
+ UnableToEmptySpyRegistryError = Class.new(StandardError)
6
6
  end
7
7
  end
@@ -30,16 +30,16 @@ module Spy
30
30
  # it isn't just a data struct
31
31
  is_active = if @conditional_filters.any?
32
32
  mc = build_method_call(receiver, *args, &block)
33
- @conditional_filters.all? {|f| f.call(mc)}
33
+ @conditional_filters.all? { |f| f.call(mc) }
34
34
  else
35
35
  true
36
36
  end
37
37
 
38
- return call_original(receiver, *args, &block) if !is_active
38
+ return call_original(receiver, *args, &block) unless is_active
39
39
 
40
40
  if @before_callbacks.any?
41
41
  mc = build_method_call(receiver, *args, &block)
42
- @before_callbacks.each {|f| f.call(mc)}
42
+ @before_callbacks.each { |f| f.call(mc) }
43
43
  end
44
44
 
45
45
  if @around_procs.any?
@@ -48,13 +48,13 @@ module Spy
48
48
  # Procify the original call
49
49
  # Still return the result from it
50
50
  result = nil
51
- original_proc = Proc.new do
51
+ original_proc = proc do
52
52
  result = call_and_record(receiver, args, { :record => mc }, &block)
53
53
  end
54
54
 
55
55
  # Keep wrapping the original proc with each around_proc
56
56
  @around_procs.reduce(original_proc) do |p, wrapper|
57
- Proc.new { wrapper.call(mc, &p) }
57
+ proc { wrapper.call(mc, &p) }
58
58
  end.call
59
59
  else
60
60
  result = call_and_record(receiver, args, &block)
@@ -62,7 +62,7 @@ module Spy
62
62
 
63
63
  if @after_callbacks.any?
64
64
  mc = @call_history.last
65
- @after_callbacks.each {|f| f.call(mc)}
65
+ @after_callbacks.each { |f| f.call(mc) }
66
66
  end
67
67
 
68
68
  result
@@ -7,17 +7,24 @@ module Spy
7
7
  class << self
8
8
  def factory_build(spy)
9
9
  if spy.original.is_a?(Method)
10
- if spy.original.owner == spy.spied.singleton_class
11
- Strategy::Wrap.new(spy)
12
- else
13
- Strategy::Intercept.new(spy, spy.spied.singleton_class)
14
- end
10
+ pick_strategy(spy, spy.spied.singleton_class)
15
11
  else
16
- if spy.original.owner == spy.spied
17
- Strategy::Wrap.new(spy)
18
- else
19
- Strategy::Intercept.new(spy, spy.spied)
20
- end
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)
21
28
  end
22
29
  end
23
30
  end
@@ -5,13 +5,10 @@ module Spy
5
5
 
6
6
  def initialize(replayer, name, receiver, *args)
7
7
  @replayer = replayer
8
- @name = name
8
+ @name = name
9
9
  @receiver = receiver
10
- @args = args
11
-
12
- if block_given?
13
- @block = proc { receiver.instance_eval &Proc.new }
14
- end
10
+ @args = args
11
+ @block = proc { receiver.instance_eval &Proc.new } if block_given?
15
12
  end
16
13
 
17
14
  def replay
@@ -0,0 +1,56 @@
1
+ require 'spy/errors'
2
+ require 'spy/registry_store'
3
+ require 'spy/registry_entry'
4
+
5
+ module Spy
6
+ # Responsible for managing the top-level state of which spies exist.
7
+ class Registry
8
+ # Keeps track of the spy for later management. Ensures spy uniqueness
9
+ #
10
+ # @param [Object] spied - the object being spied on
11
+ # @param [Method, UnboundMethod] method - the method being spied on
12
+ # @param [Spy::Instance] spy - the instantiated spy
13
+ # @raises [Spy::Errors::AlreadySpiedError] if the spy is already being
14
+ # 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)
19
+ end
20
+
21
+ # Stops tracking the spy
22
+ #
23
+ # @param [Object] spied - the object being spied on
24
+ # @param [Method, UnboundMethod] method - the method being spied on
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
30
+ end
31
+
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
+ def remove_all
37
+ store.map { |e| yield remove(e.spied, e.method) }
38
+ raise Errors::UnableToEmptySpyRegistryError unless store.empty?
39
+ end
40
+
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
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
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
@@ -0,0 +1,28 @@
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
@@ -1,3 +1,3 @@
1
1
  module Spy
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spy_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.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-02-10 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2016-07-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
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'
13
55
  description: Spy brings everything that's great about Sinon.JS to Ruby. Mocking frameworks
14
56
  work by stubbing out functionality. Spy works by listening in on functionality and
15
57
  allowing it to run in the background. Spy is designed to be lightweight and work
@@ -21,9 +63,6 @@ extra_rdoc_files: []
21
63
  files:
22
64
  - lib/spy.rb
23
65
  - lib/spy/api.rb
24
- - lib/spy/collection.rb
25
- - lib/spy/collection/entry.rb
26
- - lib/spy/collection/store.rb
27
66
  - lib/spy/core.rb
28
67
  - lib/spy/errors.rb
29
68
  - lib/spy/instance.rb
@@ -32,6 +71,9 @@ files:
32
71
  - lib/spy/instance/strategy/intercept.rb
33
72
  - lib/spy/instance/strategy/wrap.rb
34
73
  - lib/spy/method_call.rb
74
+ - lib/spy/registry.rb
75
+ - lib/spy/registry_entry.rb
76
+ - lib/spy/registry_store.rb
35
77
  - lib/spy/version.rb
36
78
  homepage: https://github.com/jbodah/spy_rb
37
79
  licenses:
@@ -1,42 +0,0 @@
1
- require 'spy/errors'
2
- require 'spy/collection/store'
3
- require 'spy/collection/entry'
4
-
5
- module Spy
6
- # Responsible for error handling and mapping to Entry
7
- class Collection
8
- def insert(spied, method, spy)
9
- entry = Entry.new(spied, method, spy)
10
- if store.include? entry
11
- raise Errors::AlreadySpiedError
12
- end
13
- store.insert(entry)
14
- end
15
-
16
- def remove(spied, method)
17
- entry = Entry.new(spied, method, nil)
18
- if !store.include? entry
19
- raise Errors::MethodNotSpiedError
20
- end
21
- store.remove(entry).spy
22
- end
23
-
24
- def remove_all
25
- store.map {|e| yield remove(e.spied, e.method)}
26
- if !store.empty?
27
- raise Errors::UnableToEmptySpyCollectionError
28
- end
29
- end
30
-
31
- def include?(spied, method)
32
- entry = Entry.new(spied, method)
33
- store.include? entry
34
- end
35
-
36
- private
37
-
38
- def store
39
- @store ||= Store.new
40
- end
41
- end
42
- end
@@ -1,15 +0,0 @@
1
- module Spy
2
- class Collection
3
- # Abstraction to isolate domain logic
4
- class Entry < Struct.new(:spied, :method, :spy)
5
- def key
6
- receiver = method.is_a?(Method) ? method.receiver : nil
7
- "#{receiver.object_id}|#{method.name}|#{method.class}"
8
- end
9
-
10
- def ==(other)
11
- key == other.key
12
- end
13
- end
14
- end
15
- end
@@ -1,32 +0,0 @@
1
- module Spy
2
- class Collection
3
- # Works with Entry abstractions to allow the
4
- # store data structure to be easily swapped
5
- class Store
6
- include Enumerable
7
-
8
- def initialize
9
- @internal = {}
10
- end
11
-
12
- def insert(entry)
13
- @internal[entry.key] = entry
14
- end
15
-
16
- def remove(entry)
17
- @internal.delete(entry.key)
18
- end
19
-
20
- def each
21
- e = Enumerator.new do |y|
22
- @internal.values.each {|v| y << v}
23
- end
24
- block_given? ? e.each(&Proc.new) : e
25
- end
26
-
27
- def empty?
28
- none?
29
- end
30
- end
31
- end
32
- end