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 +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
|