spy_rb 0.3.0 → 0.4.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: 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