spy_rb 0.3.0 → 0.4.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: 47b608d05670cc1e49baec91115ecfdf0e87feb6
4
- data.tar.gz: a7519779804fa1ac600149a87bfed851cacca168
3
+ metadata.gz: 96ccd939b58cbad0c0ab01af781177fb2d974e14
4
+ data.tar.gz: 352f27f11583dd07e856ce6522cd89dbe8d4d0bb
5
5
  SHA512:
6
- metadata.gz: 8a3853df7bcb3ae2440b6585ea519d4466b7130ddb061a2d29db1ba88a9e31a410c1a22adb614782c337a035356d82d8350d9024aeabb1b588ae00c97cc04078
7
- data.tar.gz: dc92ea6560aa6b5fbfbc260cdbc9b2cdb2acae4bc92b0662b56b65b40c34dd149c0a195cf18872f41f5a05284dce90eaf514ea9ca973979828ffecbd025e3f47
6
+ metadata.gz: f459c3813f339b9f5c7a6a59ba934e1f47f68b1a25a1ec609550e435190415827b6407858a5865bb120d17d7e221d9477b4ff67c1d965dedb573837fcb35ba95
7
+ data.tar.gz: 50c0d68b2043f6f91646f983c24ac4acabdd8baab4d97e71a7f1dbeade9c56a9677274fa29025351536686f2e106f47474cb046bc5480a07871ff269df7f1bcd
@@ -1,36 +1,15 @@
1
1
  module Spy
2
2
  class Collection
3
- class Entry < Struct.new(:receiver, :msg, :method_type)
4
- attr_accessor :value
5
-
3
+ # Abstraction to isolate domain logic
4
+ class Entry < Struct.new(:spied, :method, :spy)
6
5
  def key
7
- "#{receiver.object_id}|#{msg}|#{method_type}"
6
+ receiver = method.is_a?(Method) ? method.receiver : nil
7
+ "#{receiver.object_id}|#{method.name}|#{method.class}"
8
8
  end
9
9
 
10
10
  def ==(other)
11
11
  key == other.key
12
12
  end
13
-
14
- module ClassMethods
15
- def parse(key)
16
- new *parse_key(key)
17
- end
18
-
19
- def parse_key(key)
20
- parts = key.split('|')
21
- parts[0] = find_object(parts[0].to_i)
22
- parts[1] = parts[1].to_sym
23
- parts[2] = parts[2].to_sym
24
- parts
25
- end
26
-
27
- # Looks up an object in the global ObjectSpace
28
- def find_object(object_id)
29
- ObjectSpace._id2ref(object_id)
30
- end
31
- end
32
-
33
- extend ClassMethods
34
13
  end
35
14
  end
36
15
  end
@@ -0,0 +1,32 @@
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
@@ -1,52 +1,42 @@
1
+ require 'spy/errors'
2
+ require 'spy/collection/store'
1
3
  require 'spy/collection/entry'
2
4
 
3
5
  module Spy
6
+ # Responsible for error handling and mapping to Entry
4
7
  class Collection
5
- include Enumerable
6
-
7
- def initialize
8
- @store = {}
9
- end
10
-
11
- def insert(entry)
12
- raise Errors::AlreadySpiedError if include?(entry)
13
- @store[entry.key] = entry
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
14
  end
15
15
 
16
- def remove(entry)
17
- raise Errors::MethodNotSpiedError unless include?(entry)
18
- @store.delete(entry.key).value
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
19
22
  end
20
23
 
21
- # Removes each element from the collection and calls the block
22
- # with each deleted element
23
24
  def remove_all
24
- map {|e| yield remove(e)}
25
+ store.map {|e| yield remove(e.spied, e.method)}
26
+ if !store.empty?
27
+ raise Errors::UnableToEmptySpyCollectionError
28
+ end
25
29
  end
26
30
 
27
- def each
28
- @store.keys.each {|k| yield Entry.parse(k)}
31
+ def include?(spied, method)
32
+ entry = Entry.new(spied, method)
33
+ store.include? entry
29
34
  end
30
35
 
31
- # Add a slicker interface that abstracts away Collection::Entry
32
- module SpyHelper
33
- def <<(spy)
34
- receiver = spy.original.is_a?(Method) ? spy.original.receiver : nil
35
- name = spy.original.name
36
- klass = spy.original.class
37
- entry = Collection::Entry.new(receiver, name, klass)
38
- entry.value = spy
39
- insert entry
40
- end
36
+ private
41
37
 
42
- def pop(method)
43
- receiver = method.is_a?(Method) ? method.receiver : nil
44
- name = method.name
45
- klass = method.class
46
- remove Collection::Entry.new(receiver, name, klass)
47
- end
38
+ def store
39
+ @store ||= Store.new
48
40
  end
49
-
50
- include SpyHelper
51
41
  end
52
42
  end
data/lib/spy/core.rb CHANGED
@@ -4,23 +4,28 @@ require 'spy/errors'
4
4
 
5
5
  module Spy
6
6
  class Core
7
- def spy_collection
8
- @spy_collection ||= Collection.new
9
- end
10
-
11
7
  def add_spy(spied, method)
8
+ if collection.include?(spied, method)
9
+ raise Errors::AlreadySpiedError
10
+ end
12
11
  spy = Instance.new(spied, method)
13
- spy_collection << spy
12
+ collection.insert(spied, method, spy)
14
13
  spy.start
15
14
  end
16
15
 
17
16
  def remove_spy(spied, method)
18
- spy = spy_collection.pop(method)
17
+ spy = collection.remove(spied, method)
19
18
  spy.stop
20
19
  end
21
20
 
22
21
  def remove_all_spies
23
- spy_collection.remove_all { |spy| spy.stop }
22
+ collection.remove_all { |spy| spy.stop }
23
+ end
24
+
25
+ private
26
+
27
+ def collection
28
+ @collection ||= Collection.new
24
29
  end
25
30
  end
26
31
  end
data/lib/spy/errors.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Spy
2
2
  module Errors
3
- MethodNotSpiedError = Class.new(StandardError)
4
- AlreadySpiedError = Class.new(StandardError)
3
+ MethodNotSpiedError = Class.new(StandardError)
4
+ AlreadySpiedError = Class.new(StandardError)
5
+ UnableToEmptySpyCollection = Class.new(StandardError)
5
6
  end
6
7
  end
@@ -11,62 +11,72 @@ module Spy
11
11
  def attach_to(target)
12
12
  spy = self
13
13
  target.class_eval do
14
- define_method spy.original.name do |*args|
15
- spy.call(self, *args)
14
+ define_method spy.original.name do |*args, &block|
15
+ spy.call(self, *args, &block)
16
16
  end
17
17
  send(spy.visibility, spy.original.name)
18
18
  end
19
19
  end
20
20
 
21
- # Call the spied method using the given context and arguments.
21
+ # Call the spied method using the given receiver and arguments.
22
22
  #
23
- # Context is required for calling UnboundMethods such as
23
+ # receiver is required to allow calling of UnboundMethods such as
24
24
  # instance methods defined on a Class
25
- def call(context, *args)
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
26
31
  is_active = @conditional_filters.all? {|f| f.call(*args)}
27
32
 
28
- if is_active
33
+ if !is_active
34
+ call_original(receiver, *args, &block)
35
+ else
29
36
  @before_callbacks.each {|f| f.call(*args)}
30
- end
31
37
 
32
- if @around_procs.any?
33
- # Procify the original call
34
- original_proc = Proc.new do
35
- record = track_call(context, *args) if is_active
36
- result = call_original(context, *args)
37
- record.result = result if is_active
38
- end
38
+ if @around_procs.any?
39
+ # Procify the original call
40
+ # Still return the result from it
41
+ result = nil
42
+ original_proc = Proc.new do
43
+ result = call_and_record(receiver, *args, &block)
44
+ end
39
45
 
40
- # Keep wrapping the original proc with each around_proc
41
- @around_procs.reduce(original_proc) do |p, wrapper|
42
- Proc.new { wrapper.call context, *args, &p }
43
- end.call
44
- else
45
- record = track_call(context, *args) if is_active
46
- result = call_original(context, *args)
47
- record.result = result if is_active
48
- end
46
+ # Keep wrapping the original proc with each around_proc
47
+ @around_procs.reduce(original_proc) do |p, wrapper|
48
+ Proc.new { wrapper.call receiver, *args, &p }
49
+ end.call
50
+ else
51
+ result = call_and_record(receiver, *args, &block)
52
+ end
49
53
 
50
- if is_active
51
54
  @after_callbacks.each {|f| f.call(*args)}
52
- end
53
55
 
54
- result
56
+ result
57
+ end
55
58
  end
56
59
 
57
60
  private
58
61
 
59
- def track_call(context, *args)
60
- record = Spy::MethodCall.new(context, *args)
62
+ def call_and_record(receiver, *args, &block)
63
+ record = track_call(receiver, *args, &block)
64
+ result = call_original(receiver, *args, &block)
65
+ record.result = result
66
+ end
67
+
68
+ def track_call(receiver, *args, &block)
69
+ replayer = proc { call_original(receiver, *args, &block) }
70
+ record = Spy::MethodCall.new(replayer, original.name, receiver, *args, &block)
61
71
  @call_history << record
62
72
  record
63
73
  end
64
74
 
65
- def call_original(context, *args)
75
+ def call_original(receiver, *args, &block)
66
76
  if original.is_a?(UnboundMethod)
67
- original.bind(context).call(*args)
77
+ original.bind(receiver).call(*args, &block)
68
78
  else
69
- original.call(*args)
79
+ original.call(*args, &block)
70
80
  end
71
81
  end
72
82
  end
data/lib/spy/instance.rb CHANGED
@@ -27,6 +27,10 @@ module Spy
27
27
  @call_history.size
28
28
  end
29
29
 
30
+ def replay_all
31
+ @call_history.map(&:replay)
32
+ end
33
+
30
34
  def start
31
35
  @strategy.apply
32
36
  self
@@ -1,11 +1,21 @@
1
1
  module Spy
2
2
  class MethodCall
3
- attr_reader :context, :args
3
+ attr_reader :name, :receiver, :args, :block
4
4
  attr_accessor :result
5
5
 
6
- def initialize(context, *args)
7
- @context = context
6
+ def initialize(replayer, name, receiver, *args)
7
+ @replayer = replayer
8
+ @name = name
9
+ @receiver = receiver
8
10
  @args = args
11
+
12
+ if block_given?
13
+ @block = proc { receiver.instance_eval &Proc.new }
14
+ end
15
+ end
16
+
17
+ def replay
18
+ @replayer.call
9
19
  end
10
20
  end
11
21
  end
data/lib/spy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Spy
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.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: 0.3.0
4
+ version: 0.4.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: 2015-05-20 00:00:00.000000000 Z
11
+ date: 2015-06-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Spy brings everything that's great about Sinon.JS to Ruby. Mocking frameworks
14
14
  work by stubbing out functionality. Spy works by listening in on functionality and
@@ -23,6 +23,7 @@ files:
23
23
  - lib/spy/api.rb
24
24
  - lib/spy/collection.rb
25
25
  - lib/spy/collection/entry.rb
26
+ - lib/spy/collection/store.rb
26
27
  - lib/spy/core.rb
27
28
  - lib/spy/errors.rb
28
29
  - lib/spy/instance.rb