impersonator 0.1.2 → 0.1.3

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