spy_rb 1.0.0 → 2.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 +4 -4
- data/lib/spy.rb +5 -0
- data/lib/spy/api.rb +24 -8
- data/lib/spy/core.rb +32 -12
- data/lib/spy/errors.rb +3 -3
- data/lib/spy/instance/api/internal.rb +6 -6
- data/lib/spy/instance/strategy.rb +17 -10
- data/lib/spy/method_call.rb +3 -6
- data/lib/spy/registry.rb +56 -0
- data/lib/spy/registry_entry.rb +13 -0
- data/lib/spy/registry_store.rb +28 -0
- data/lib/spy/version.rb +1 -1
- metadata +48 -6
- data/lib/spy/collection.rb +0 -42
- data/lib/spy/collection/entry.rb +0 -15
- data/lib/spy/collection/store.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb161a2c4eabb953bcc5eefefba4927bb7159a56
|
4
|
+
data.tar.gz: 569fda1c72a9b7a80d3efde9f9c3980623c10f3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/spy/api.rb
CHANGED
@@ -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
|
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
|
-
# @
|
38
|
+
# @example stop spying on every spied message
|
26
39
|
#
|
27
40
|
# Spy.restore(:all)
|
28
|
-
#
|
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
|
-
#
|
34
|
-
#
|
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
|
data/lib/spy/core.rb
CHANGED
@@ -1,31 +1,51 @@
|
|
1
1
|
require 'spy/instance'
|
2
|
-
require 'spy/
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
42
|
+
registry.remove_all(&:stop)
|
23
43
|
end
|
24
44
|
|
25
45
|
private
|
26
46
|
|
27
|
-
def
|
28
|
-
@
|
47
|
+
def registry
|
48
|
+
@registry ||= Registry.new
|
29
49
|
end
|
30
50
|
end
|
31
51
|
end
|
data/lib/spy/errors.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Spy
|
2
2
|
module Errors
|
3
|
-
MethodNotSpiedError
|
4
|
-
AlreadySpiedError
|
5
|
-
|
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)
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
data/lib/spy/method_call.rb
CHANGED
@@ -5,13 +5,10 @@ module Spy
|
|
5
5
|
|
6
6
|
def initialize(replayer, name, receiver, *args)
|
7
7
|
@replayer = replayer
|
8
|
-
@name
|
8
|
+
@name = name
|
9
9
|
@receiver = receiver
|
10
|
-
@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
|
data/lib/spy/registry.rb
ADDED
@@ -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
|
data/lib/spy/version.rb
CHANGED
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:
|
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-
|
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:
|
data/lib/spy/collection.rb
DELETED
@@ -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
|
data/lib/spy/collection/entry.rb
DELETED
@@ -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
|
data/lib/spy/collection/store.rb
DELETED
@@ -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
|