impersonator 0.1.2 → 0.1.3

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
  SHA256:
3
- metadata.gz: 44894f5dac3e7080abf3ffebf05ea83a876394629f60f9e486319f8e4e5304e2
4
- data.tar.gz: 994a96556cef09050e108cfd55539af2f61507afae6298a994da811f187076bb
3
+ metadata.gz: 88bdf9e7b7619dc442feaf3630312ef3ae0a30cedbd4c1593e4b896d93fac5a2
4
+ data.tar.gz: 073f80859044bd9852a62ab292cf029f52056bcf49446b03a94981cf8c9cde1b
5
5
  SHA512:
6
- metadata.gz: 83d2e1d38817599af079e1160c519a9529a820485ee851e7d9934a897f3f81ba97f3dc6add6c4e3f59f73d88095442971f9f4231666337cc75d9dd1cc7468b5b
7
- data.tar.gz: 7c8b47fb88e82aca2d80ffc27f09c1ae37842d0ca2a0356fdaed50bc9d5cb9bb659c6687759ea21c95949af215c16a8572b09dfc975e54c839461a17d71757a7
6
+ metadata.gz: c832394967e28325e1d021957dd14721595b77439c809fdfd15d6491c1ad447486f061f573b7371d3db8bc9e7812b57780181f3f0812cfda8d8de9806a9cd7bc
7
+ data.tar.gz: e2e0408e9c6f7ce5e35d51b9ccc214af75dcfbd30cf0003f6c8ce17725367ed1cec00eaf852f0d0d4249e711ca16c88929dcd58b9b4864b5457feb9ed0d1b0e9
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- impersonator (0.1.2)
4
+ impersonator (0.1.3)
5
5
  zeitwerk (~> 2.1.6)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -32,7 +32,7 @@ And then execute:
32
32
  Use `Impersonator.impersonate` passing in a list of methods to impersonate and a block that will instantiate the object at record time:
33
33
 
34
34
  ```ruby
35
- Impersonator.impersonate(:add, :divide) { Calculator.new }
35
+ calculator = Impersonator.impersonate(:add, :divide) { Calculator.new }
36
36
  ```
37
37
 
38
38
  * At record time, `Calculator` will be instantiated and their methods normally invoked, recording the returned values (and yielded values if any).
@@ -47,15 +47,15 @@ end
47
47
 
48
48
  # The first time it records...
49
49
  Impersonator.recording('calculator add') do
50
- impersonated_calculator = Impersonator.impersonate(:add) { Calculator.new }
51
- puts impersonated_calculator.add(2, 3) # 5
50
+ calculator = Impersonator.impersonate(:add) { Calculator.new }
51
+ puts calculator.add(2, 3) # 5
52
52
  end
53
53
 
54
54
  # The next time it replays
55
55
  Object.send :remove_const, :Calculator # Calculator does not even have to exist now
56
56
  Impersonator.recording('calculator add') do
57
- impersonated_calculator = Impersonator.impersonate(:add) { Calculator.new }
58
- puts impersonated_calculator.add(2, 3) # 5
57
+ calculator = Impersonator.impersonate(:add) { Calculator.new }
58
+ puts calculator.add(2, 3) # 5
59
59
  end
60
60
  ```
61
61
 
@@ -189,6 +189,7 @@ end
189
189
 
190
190
  ## Links
191
191
 
192
+ - [API documentation at rubydoc.info](https://www.rubydoc.info/github/jorgemanrubia/impersonator)
192
193
  - [Blog post](https://www.jorgemanrubia.com/2019/06/16/impersonator-a-ruby-library-to-record-and-replay-object-interactions/)
193
194
 
194
195
  ## Contributing
@@ -67,14 +67,7 @@ module Impersonator
67
67
  matching_configuration = method_matching_configurations_by_method[method_name.to_sym]
68
68
  method = Method.new(name: method_name, arguments: args, block: block,
69
69
  matching_configuration: matching_configuration)
70
- if recording.replay_mode?
71
- recording.replay(method)
72
- else
73
- spiable_block = method&.block_spy&.block
74
- @impersonated_object.send(method_name, *args, &spiable_block).tap do |return_value|
75
- recording.record(method, return_value)
76
- end
77
- end
70
+ recording.invoke(@impersonated_object, method, args)
78
71
  end
79
72
  end
80
73
  end
@@ -0,0 +1,56 @@
1
+ module Impersonator
2
+ # The state of a {Recording recording} in record mode
3
+ class RecordMode
4
+ include HasLogger
5
+
6
+ # recording file path
7
+ attr_reader :recording_path
8
+
9
+ # @param [String] recording_path the file path to the recording file
10
+ def initialize(recording_path)
11
+ @recording_path = recording_path
12
+ end
13
+
14
+ # Start a recording session
15
+ def start
16
+ logger.debug 'Recording mode'
17
+ make_sure_recordings_dir_exists
18
+ @method_invocations = []
19
+ end
20
+
21
+ # Records the method invocation
22
+ #
23
+ # @param [Object, Double] impersonated_object
24
+ # @param [MethodInvocation] method
25
+ # @param [Array<Object>] args
26
+ def invoke(impersonated_object, method, args)
27
+ spiable_block = method&.block_spy&.block
28
+ impersonated_object.send(method.name, *args, &spiable_block).tap do |return_value|
29
+ record(method, return_value)
30
+ end
31
+ end
32
+
33
+ # Finishes the record session
34
+ def finish
35
+ File.open(recording_path, 'w') do |file|
36
+ YAML.dump(@method_invocations, file)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ # Record a {MethodInvocation method invocation} with a given return value
43
+ # @param [Method] method
44
+ # @param [Object] return_value
45
+ def record(method, return_value)
46
+ method_invocation = MethodInvocation.new(method_instance: method, return_value: return_value)
47
+
48
+ @method_invocations << method_invocation
49
+ end
50
+
51
+ def make_sure_recordings_dir_exists
52
+ dirname = File.dirname(recording_path)
53
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
54
+ end
55
+ end
56
+ end
@@ -3,6 +3,21 @@
3
3
  module Impersonator
4
4
  # A recording is responsible for saving interactions at record time, and replaying them at
5
5
  # replay time.
6
+ #
7
+ # A recording is always in one of two states.
8
+ #
9
+ # * {RecordMode Record mode}
10
+ # * {ReplayMode Replay mode}
11
+ #
12
+ # The state objects are responsible of dealing with the recording logic, which happens in 3
13
+ # moments:
14
+ #
15
+ # * {#start}
16
+ # * {#invoke}
17
+ # * {#finish}
18
+ #
19
+ # @see RecordMode
20
+ # @see ReplayMode
6
21
  class Recording
7
22
  include HasLogger
8
23
 
@@ -15,57 +30,39 @@ module Impersonator
15
30
  @label = label
16
31
  @recordings_path = recordings_path
17
32
  @disabled = disabled
33
+
34
+ initialize_current_mode
18
35
  end
19
36
 
20
37
  # Start a recording/replay session
21
38
  def start
22
39
  logger.debug "Starting recording #{label}..."
23
- if can_replay?
24
- start_in_replay_mode
25
- else
26
- start_in_record_mode
27
- end
40
+ current_mode.start
28
41
  end
29
42
 
30
- # Record a {MethodInvocation method invocation} with a given return value
31
- # @param [Method] method
32
- # @param [Object] return_value
33
- def record(method, return_value)
34
- method_invocation = MethodInvocation.new(method_instance: method, return_value: return_value)
35
-
36
- @method_invocations << method_invocation
37
- end
38
-
39
- # Replay a method invocation
40
- # @param [Method] method
41
- def replay(method)
42
- method_invocation = @method_invocations.shift
43
- unless method_invocation
44
- raise Impersonator::Errors::MethodInvocationError, 'Unexpected method invocation received:'\
45
- "#{method}"
46
- end
47
-
48
- validate_method_signature!(method, method_invocation.method_instance)
49
- replay_block(method_invocation, method)
50
-
51
- method_invocation.return_value
43
+ # Handles the invocation of a given method on the impersonated object
44
+ #
45
+ # It will either record the interaction or replay it dependening on if there
46
+ # is a recording available or not
47
+ #
48
+ # @param [Object, Double] impersonated_object
49
+ # @param [MethodInvocation] method
50
+ # @param [Array<Object>] args
51
+ def invoke(impersonated_object, method, args)
52
+ current_mode.invoke(impersonated_object, method, args)
52
53
  end
53
54
 
54
55
  # Finish a record/replay session.
55
56
  def finish
56
57
  logger.debug "Recording #{label} finished"
57
- if record_mode?
58
- finish_in_record_mode
59
- else
60
- finish_in_replay_mode
61
- end
58
+ current_mode.finish
62
59
  end
63
60
 
64
61
  # Return whether it is currently at replay mode
65
62
  #
66
63
  # @return [Boolean]
67
64
  def replay_mode?
68
- @replay_mode
65
+ @current_mode == replay_mode
69
66
  end
70
67
 
71
68
  # Return whether it is currently at record mode
@@ -77,66 +74,34 @@ module Impersonator
77
74
 
78
75
  private
79
76
 
80
- def can_replay?
81
- !@disabled && File.exist?(file_path)
82
- end
83
-
84
- def replay_block(recorded_method_invocation, method_to_replay)
85
- block_spy = recorded_method_invocation.method_instance.block_spy
86
- block_spy&.block_invocations&.each do |block_invocation|
87
- method_to_replay.block.call(*block_invocation.arguments)
88
- end
89
- end
77
+ attr_reader :current_mode
90
78
 
91
- def start_in_replay_mode
92
- logger.debug 'Replay mode'
93
- @replay_mode = true
94
- @method_invocations = YAML.load_file(file_path)
79
+ def initialize_current_mode
80
+ @current_mode = if can_replay?
81
+ replay_mode
82
+ else
83
+ record_mode
84
+ end
95
85
  end
96
86
 
97
- def start_in_record_mode
98
- logger.debug 'Recording mode'
99
- @replay_mode = false
100
- make_sure_recordings_dir_exists
101
- @method_invocations = []
87
+ def can_replay?
88
+ !@disabled && File.exist?(recording_path)
102
89
  end
103
90
 
104
- def finish_in_record_mode
105
- File.open(file_path, 'w') do |file|
106
- YAML.dump(@method_invocations, file)
107
- end
91
+ def record_mode
92
+ @record_mode ||= RecordMode.new(recording_path)
108
93
  end
109
94
 
110
- def finish_in_replay_mode
111
- unless @method_invocations.empty?
112
- raise Impersonator::Errors::MethodInvocationError,
113
- "Expecting #{@method_invocations.length} method invocations"\
114
- " that didn't happen: #{@method_invocations.inspect}"
115
- end
95
+ def replay_mode
96
+ @replay_mode ||= ReplayMode.new(recording_path)
116
97
  end
117
98
 
118
- def file_path
99
+ def recording_path
119
100
  File.join(@recordings_path, "#{label_as_file_name}.yml")
120
101
  end
121
102
 
122
103
  def label_as_file_name
123
104
  label.downcase.gsub(/[\(\)\s \#:]/, '-').gsub(/[\-]+/, '-').gsub(/(^-)|(-$)/, '')
124
105
  end
125
-
126
- def make_sure_recordings_dir_exists
127
- dirname = File.dirname(file_path)
128
- FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
129
- end
130
-
131
- def validate_method_signature!(expected_method, actual_method)
132
- unless actual_method == expected_method
133
- raise Impersonator::Errors::MethodInvocationError, <<~ERROR
134
- Expecting:
135
- #{expected_method}
136
- But received:
137
- #{actual_method}
138
- ERROR
139
- end
140
- end
141
106
  end
142
107
  end
@@ -0,0 +1,68 @@
1
+ module Impersonator
2
+ # The state of a {Recording recording} in replay mode
3
+ class ReplayMode
4
+ include HasLogger
5
+
6
+ # recording file path
7
+ attr_reader :recording_path
8
+
9
+ # @param [String] recording_path the file path to the recording file
10
+ def initialize(recording_path)
11
+ @recording_path = recording_path
12
+ end
13
+
14
+ # Start a replay session
15
+ def start
16
+ logger.debug 'Replay mode'
17
+ @replay_mode = true
18
+ @method_invocations = YAML.load_file(recording_path)
19
+ end
20
+
21
+ # Replays the method invocation
22
+ #
23
+ # @param [Object, Double] impersonated_object not used in replay mode
24
+ # @param [MethodInvocation] method
25
+ # @param [Array<Object>] args not used in replay mode
26
+ def invoke(_impersonated_object, method, _args)
27
+ method_invocation = @method_invocations.shift
28
+ unless method_invocation
29
+ raise Impersonator::Errors::MethodInvocationError, 'Unexpected method invocation received:'\
30
+ "#{method}"
31
+ end
32
+
33
+ validate_method_signature!(method, method_invocation.method_instance)
34
+ replay_block(method_invocation, method)
35
+
36
+ method_invocation.return_value
37
+ end
38
+
39
+ # Finishes the record session
40
+ def finish
41
+ unless @method_invocations.empty?
42
+ raise Impersonator::Errors::MethodInvocationError,
43
+ "Expecting #{@method_invocations.length} method invocations"\
44
+ " that didn't happen: #{@method_invocations.inspect}"
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def replay_block(recorded_method_invocation, method_to_replay)
51
+ block_spy = recorded_method_invocation.method_instance.block_spy
52
+ block_spy&.block_invocations&.each do |block_invocation|
53
+ method_to_replay.block.call(*block_invocation.arguments)
54
+ end
55
+ end
56
+
57
+ def validate_method_signature!(expected_method, actual_method)
58
+ unless actual_method == expected_method
59
+ raise Impersonator::Errors::MethodInvocationError, <<~ERROR
60
+ Expecting:
61
+ #{expected_method}
62
+ But received:
63
+ #{actual_method}
64
+ ERROR
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module Impersonator
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.1.3'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: impersonator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jorge Manrubia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-06-29 00:00:00.000000000 Z
11
+ date: 2019-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -117,7 +117,9 @@ files:
117
117
  - lib/impersonator/method_invocation.rb
118
118
  - lib/impersonator/method_matching_configuration.rb
119
119
  - lib/impersonator/proxy.rb
120
+ - lib/impersonator/record_mode.rb
120
121
  - lib/impersonator/recording.rb
122
+ - lib/impersonator/replay_mode.rb
121
123
  - lib/impersonator/version.rb
122
124
  homepage: https://github.com/jorgemanrubia/impersonator
123
125
  licenses: