betamax 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/lib/betamax/errors.rb +59 -0
- data/lib/betamax/method_player.rb +123 -0
- data/lib/betamax/method_recorder.rb +55 -0
- data/lib/betamax/player.rb +54 -0
- data/lib/betamax/recorded_method.rb +14 -0
- data/lib/betamax/recorded_object.rb +34 -0
- data/lib/betamax/recorded_yielding.rb +14 -0
- data/lib/betamax/recording.rb +20 -0
- data/lib/betamax/rspec.rb +17 -0
- data/lib/betamax/tape.rb +89 -0
- data/lib/betamax/version.rb +3 -0
- data/lib/betamax.rb +33 -0
- data/sig/betamax.rbs +4 -0
- metadata +60 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6f89807eb4aa9a8365294d743efddad3a94a6a6c9078615731bebc2fa1cf93f6
|
|
4
|
+
data.tar.gz: 31a8decf2ca628028820007eff48e83cbe45758fab781bc87503c3c172e61c8a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 92208fdfd5c7bff89094c394827da00a06425f3c0037e9caa498230a1afb3dec467ab5b32325018c0ce2162095ad9ae1c59be4ab36f8943699ffafffdf13407a
|
|
7
|
+
data.tar.gz: cf74600f45a068b9b01b3d268211d3992e6d836d7e80d2f8baafe446a80628588669cb68530e2cb1bea63bd3fca6ea2a5509009562d2bbdb67df538cfdc6650d
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Betamax
|
|
2
|
+
|
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
|
4
|
+
|
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/betamax`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
|
+
|
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Aesthetikx/betamax.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
module Errors
|
|
3
|
+
class Error < StandardError; end
|
|
4
|
+
|
|
5
|
+
class NoTapeInserted < Error
|
|
6
|
+
def initialize message = "No tape inserted"
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class PlaybackError < Error; end
|
|
12
|
+
|
|
13
|
+
class PlaybackFinished < PlaybackError
|
|
14
|
+
def initialize method_name
|
|
15
|
+
super "Received #{method_name} but playback has finished"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class MethodMismatch < PlaybackError
|
|
20
|
+
def initialize expected:, actual:
|
|
21
|
+
super "Expected method #{expected} but received #{actual}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class UnexpectedBlock < PlaybackError
|
|
26
|
+
def initialize method_name
|
|
27
|
+
super "Method #{method_name} was called with a block but was recorded without one"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class BlockExpected < PlaybackError
|
|
32
|
+
def initialize method_name
|
|
33
|
+
super "Method #{method_name} was called without a block but was recorded with one"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class ArgumentMismatch < PlaybackError
|
|
38
|
+
def initialize method_name, expected:, actual:
|
|
39
|
+
super "Method #{method_name} argument mismatch: " \
|
|
40
|
+
"expected #{expected.inspect}, got #{actual.inspect}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class KeywordArgumentMismatch < PlaybackError
|
|
45
|
+
def initialize method_name, expected_key:, expected_value:, actual_key:, actual_value:
|
|
46
|
+
super "Method #{method_name} keyword argument mismatch: " \
|
|
47
|
+
"expected #{expected_key}: #{expected_value.inspect}, " \
|
|
48
|
+
"got #{actual_key}: #{actual_value.inspect}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class UnusedRecordings < PlaybackError
|
|
53
|
+
def initialize unused_methods
|
|
54
|
+
method_names = unused_methods.map(&:method_name).join(", ")
|
|
55
|
+
super "Recording has #{unused_methods.size} unused method(s): #{method_names}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
class MethodPlayer
|
|
3
|
+
attr_reader :recording
|
|
4
|
+
|
|
5
|
+
def initialize recording:
|
|
6
|
+
@recording = recording
|
|
7
|
+
@playback_index = 0
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def fully_consumed?
|
|
11
|
+
@playback_index == @recording.size &&
|
|
12
|
+
@recording.all? { |record| nested_fully_consumed? record }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def unused_recordings
|
|
16
|
+
unused = @recording[@playback_index..] || []
|
|
17
|
+
nested_unused = @recording.first(@playback_index).flat_map do |record|
|
|
18
|
+
unused_from_record record
|
|
19
|
+
end
|
|
20
|
+
unused + nested_unused
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(method_name, *args, **kwargs, &)
|
|
24
|
+
record = advance_playback! method_name
|
|
25
|
+
|
|
26
|
+
validate_method_name! record.method_name, method_name
|
|
27
|
+
validate_args! method_name, args, record.args
|
|
28
|
+
validate_kwargs! method_name, kwargs, record.kwargs
|
|
29
|
+
validate_block! method_name, block_given?, record.block_given
|
|
30
|
+
|
|
31
|
+
replay_yieldings(record.block_yieldings, &)
|
|
32
|
+
|
|
33
|
+
record.result
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def nested_fully_consumed? record
|
|
39
|
+
consumed?(record.result) &&
|
|
40
|
+
record.block_yieldings.all? { |yielding| yielding_consumed? yielding }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def yielding_consumed? yielding
|
|
44
|
+
yielding.args.all? { |arg| consumed? arg } &&
|
|
45
|
+
yielding.kwargs.values.all? { |value| consumed? value }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def consumed? object
|
|
49
|
+
return true unless object.instance_of? RecordedObject
|
|
50
|
+
|
|
51
|
+
object._betamax_recorder.fully_consumed?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def unused_from_record record
|
|
55
|
+
result_unused = unused_from_object record.result
|
|
56
|
+
yielding_unused = record.block_yieldings.flat_map do |yielding|
|
|
57
|
+
unused_from_yielding yielding
|
|
58
|
+
end
|
|
59
|
+
result_unused + yielding_unused
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def unused_from_yielding yielding
|
|
63
|
+
args_unused = yielding.args.flat_map { |arg| unused_from_object arg }
|
|
64
|
+
kwargs_unused = yielding.kwargs.values.flat_map { |value| unused_from_object value }
|
|
65
|
+
args_unused + kwargs_unused
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unused_from_object object
|
|
69
|
+
return [] unless object.instance_of? RecordedObject
|
|
70
|
+
|
|
71
|
+
object._betamax_recorder.unused_recordings
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def replay_yieldings block_yieldings
|
|
75
|
+
block_yieldings.each do |yielding|
|
|
76
|
+
yield(*yielding.args, **yielding.kwargs)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def advance_playback! method_name
|
|
81
|
+
@recording[@playback_index].tap do |record|
|
|
82
|
+
raise Errors::PlaybackFinished, method_name if record.nil?
|
|
83
|
+
|
|
84
|
+
@playback_index += 1
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def validate_method_name! expected, actual
|
|
89
|
+
return if expected == actual
|
|
90
|
+
|
|
91
|
+
raise Errors::MethodMismatch.new expected:, actual:
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def validate_args! method_name, actual_args, expected_args
|
|
95
|
+
max_size = [actual_args.size, expected_args.size].max
|
|
96
|
+
max_size.times do |i|
|
|
97
|
+
actual = actual_args[i]
|
|
98
|
+
expected = expected_args[i]
|
|
99
|
+
next if actual == expected
|
|
100
|
+
|
|
101
|
+
raise Errors::ArgumentMismatch.new method_name, expected:, actual:
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def validate_kwargs! method_name, actual_kwargs, expected_kwargs
|
|
106
|
+
all_keys = (actual_kwargs.keys | expected_kwargs.keys)
|
|
107
|
+
all_keys.each do |key|
|
|
108
|
+
actual_value = actual_kwargs[key]
|
|
109
|
+
expected_value = expected_kwargs[key]
|
|
110
|
+
next if actual_value == expected_value
|
|
111
|
+
|
|
112
|
+
raise Errors::KeywordArgumentMismatch.new method_name,
|
|
113
|
+
expected_key: key, expected_value:,
|
|
114
|
+
actual_key: key, actual_value:
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def validate_block! method_name, actual_block_given, expected_block_given
|
|
119
|
+
raise Errors::BlockExpected, method_name if expected_block_given && !actual_block_given
|
|
120
|
+
raise Errors::UnexpectedBlock, method_name if actual_block_given && !expected_block_given
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
class MethodRecorder
|
|
3
|
+
PRIMITIVE_TYPES = [Integer, Float, String, Symbol, TrueClass, FalseClass, NilClass].freeze
|
|
4
|
+
|
|
5
|
+
attr_reader :recording
|
|
6
|
+
|
|
7
|
+
def initialize object:, recording: []
|
|
8
|
+
@object = object
|
|
9
|
+
@recording = recording
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(method_name, *args, **kwargs, &) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
13
|
+
block_yieldings = []
|
|
14
|
+
|
|
15
|
+
result = if block_given?
|
|
16
|
+
@object.send(method_name, *args, **kwargs) do |*block_args, **block_kwargs|
|
|
17
|
+
wrapped_args = block_args.map { |arg| wrap_object arg }
|
|
18
|
+
wrapped_kwargs = block_kwargs.transform_values { |value| wrap_object value }
|
|
19
|
+
|
|
20
|
+
yielding = RecordedYielding.new args: wrapped_args, kwargs: wrapped_kwargs
|
|
21
|
+
block_yieldings << yielding
|
|
22
|
+
|
|
23
|
+
yield(*wrapped_args, **wrapped_kwargs)
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
@object.send(method_name, *args, **kwargs, &)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
wrapped_result = wrap_object result
|
|
30
|
+
|
|
31
|
+
@recording << RecordedMethod.new(
|
|
32
|
+
method_name:,
|
|
33
|
+
args:,
|
|
34
|
+
kwargs:,
|
|
35
|
+
block_given: block_given?,
|
|
36
|
+
block_yieldings:,
|
|
37
|
+
result: wrapped_result
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
wrapped_result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def wrap_object object
|
|
46
|
+
case object
|
|
47
|
+
when *PRIMITIVE_TYPES
|
|
48
|
+
object
|
|
49
|
+
else
|
|
50
|
+
recorder = MethodRecorder.new object:, recording: []
|
|
51
|
+
RecordedObject.new object:, recorder:
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
class Player
|
|
3
|
+
attr_reader :tape, :root_proxy, :tapes_folder
|
|
4
|
+
|
|
5
|
+
def initialize example, tapes_folder:
|
|
6
|
+
@example = example
|
|
7
|
+
@tapes_folder = tapes_folder
|
|
8
|
+
@tape = Tape.from_rspec_example(example, tapes_folder:)
|
|
9
|
+
@root_proxy = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def play
|
|
13
|
+
insert_tape
|
|
14
|
+
@example.run
|
|
15
|
+
eject_tape unless @example.exception
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def record object
|
|
19
|
+
recorder = if @tape.exists?
|
|
20
|
+
MethodPlayer.new recording: @tape.recording
|
|
21
|
+
else
|
|
22
|
+
MethodRecorder.new object:, recording: @tape.recording
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@root_proxy = RecordedObject.new object:, recorder:
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def insert_tape
|
|
31
|
+
@tape.load
|
|
32
|
+
Fiber[:betamax_player] = self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def eject_tape
|
|
36
|
+
Fiber[:betamax_player] = nil
|
|
37
|
+
|
|
38
|
+
if @tape.exists?
|
|
39
|
+
verify_fully_consumed!
|
|
40
|
+
elsif @root_proxy
|
|
41
|
+
@tape.save @root_proxy
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def verify_fully_consumed!
|
|
46
|
+
return unless @root_proxy
|
|
47
|
+
|
|
48
|
+
recorder = @root_proxy._betamax_recorder
|
|
49
|
+
return if recorder.fully_consumed?
|
|
50
|
+
|
|
51
|
+
raise Errors::UnusedRecordings, recorder.unused_recordings
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
RecordedMethod = Data.define :method_name, :args, :kwargs, :block_given, :block_yieldings, :result
|
|
3
|
+
|
|
4
|
+
# Implement YAML serialization / deserialization
|
|
5
|
+
RecordedMethod.include Module.new {
|
|
6
|
+
def init_with coder
|
|
7
|
+
initialize **coder.map
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def encode_with coder
|
|
11
|
+
coder.map = to_h
|
|
12
|
+
end
|
|
13
|
+
}
|
|
14
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
class RecordedObject
|
|
3
|
+
attr_reader :_betamax_recorder
|
|
4
|
+
|
|
5
|
+
def initialize object:, recorder:
|
|
6
|
+
@_betamax_object = object
|
|
7
|
+
@_betamax_recorder = recorder
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def respond_to_missing? method_name, _include_private = false
|
|
11
|
+
@_betamax_object.respond_to? method_name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def method_missing(...)
|
|
15
|
+
@_betamax_recorder.call(...)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Implement YAML serialization / deserialization
|
|
20
|
+
RecordedObject.include Module.new {
|
|
21
|
+
def init_with coder
|
|
22
|
+
recording = coder.map.fetch :recording
|
|
23
|
+
recorder = MethodPlayer.new recording: recording
|
|
24
|
+
initialize object: nil, recorder: recorder
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def encode_with coder
|
|
28
|
+
coder.map = {
|
|
29
|
+
class_name: @_betamax_object.class.name,
|
|
30
|
+
recording: @_betamax_recorder.recording
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
}
|
|
34
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
RecordedYielding = Data.define :args, :kwargs
|
|
3
|
+
|
|
4
|
+
# Implement YAML serialization / deserialization
|
|
5
|
+
RecordedYielding.include Module.new {
|
|
6
|
+
def init_with coder
|
|
7
|
+
initialize **coder.map
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def encode_with coder
|
|
11
|
+
coder.map = to_h
|
|
12
|
+
end
|
|
13
|
+
}
|
|
14
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
Recording = Data.define :version, :objects
|
|
3
|
+
|
|
4
|
+
Recording::VERSION = 1.0
|
|
5
|
+
|
|
6
|
+
# Implement YAML serialization / deserialization
|
|
7
|
+
Recording.include Module.new {
|
|
8
|
+
def init_with coder
|
|
9
|
+
initialize **coder.map
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def encode_with coder
|
|
13
|
+
coder.map = to_h
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def default_recording
|
|
17
|
+
objects.fetch(:default)._betamax_recorder.recording
|
|
18
|
+
end
|
|
19
|
+
}
|
|
20
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Betamax
|
|
2
|
+
module RSpec
|
|
3
|
+
DEFAULT_TAPES_FOLDER = Pathname.new "spec/betamax_tapes/"
|
|
4
|
+
|
|
5
|
+
def play_rspec example, tapes_folder: DEFAULT_TAPES_FOLDER
|
|
6
|
+
Player.new(example, tapes_folder:).play
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def install_rspec!
|
|
10
|
+
::RSpec.configure do |config|
|
|
11
|
+
config.around :each, :betamax do |example|
|
|
12
|
+
Betamax.play_rspec example
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/betamax/tape.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "yaml"
|
|
3
|
+
|
|
4
|
+
module Betamax
|
|
5
|
+
class Tape
|
|
6
|
+
PERMITTED_YAML_CLASSES = [
|
|
7
|
+
Betamax::Recording,
|
|
8
|
+
Betamax::RecordedObject,
|
|
9
|
+
Betamax::RecordedMethod,
|
|
10
|
+
Betamax::RecordedYielding,
|
|
11
|
+
Symbol
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :path, :recording
|
|
15
|
+
|
|
16
|
+
def initialize path
|
|
17
|
+
@path = Pathname.new path
|
|
18
|
+
@recording = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.from_rspec_example example, tapes_folder:
|
|
22
|
+
name = cassette_name_for example.metadata
|
|
23
|
+
name.gsub! "::", "_"
|
|
24
|
+
name.gsub!(/[\s-]+/, "_")
|
|
25
|
+
name.gsub! %r{[^a-zA-Z0-9_/]}, ""
|
|
26
|
+
|
|
27
|
+
filename = Pathname.new(name).sub_ext ".yaml"
|
|
28
|
+
full_path = tapes_folder / filename
|
|
29
|
+
|
|
30
|
+
new full_path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.cassette_name_for metadata
|
|
34
|
+
# Build name from example group hierarchy + example description
|
|
35
|
+
description_parts = []
|
|
36
|
+
|
|
37
|
+
# Add the example description first
|
|
38
|
+
description_parts << metadata[:description] if metadata[:description]
|
|
39
|
+
|
|
40
|
+
# Walk up the example group hierarchy
|
|
41
|
+
current = metadata[:example_group]
|
|
42
|
+
while current
|
|
43
|
+
description_parts.unshift current[:description] if current[:description]
|
|
44
|
+
current = current[:parent_example_group]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
description_parts.join "/"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load
|
|
51
|
+
FileUtils.mkdir_p @path.dirname
|
|
52
|
+
|
|
53
|
+
if exists?
|
|
54
|
+
load_existing_recording
|
|
55
|
+
else
|
|
56
|
+
prepare_new_recording
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def save proxy
|
|
63
|
+
return if exists? # Don't overwrite existing recordings
|
|
64
|
+
|
|
65
|
+
recording = Betamax::Recording.new \
|
|
66
|
+
version: Recording::VERSION,
|
|
67
|
+
objects: { default: proxy }
|
|
68
|
+
|
|
69
|
+
File.open @path, "w" do |file|
|
|
70
|
+
file.puts recording.to_yaml
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def exists?
|
|
75
|
+
File.exist? @path
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def load_existing_recording
|
|
81
|
+
raw = YAML.safe_load_file @path, permitted_classes: PERMITTED_YAML_CLASSES
|
|
82
|
+
@recording = raw.default_recording
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def prepare_new_recording
|
|
86
|
+
@recording = []
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/betamax.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
|
|
3
|
+
require_relative "betamax/errors"
|
|
4
|
+
require_relative "betamax/method_player"
|
|
5
|
+
require_relative "betamax/method_recorder"
|
|
6
|
+
require_relative "betamax/player"
|
|
7
|
+
require_relative "betamax/recorded_method"
|
|
8
|
+
require_relative "betamax/recorded_object"
|
|
9
|
+
require_relative "betamax/recorded_yielding"
|
|
10
|
+
require_relative "betamax/recording"
|
|
11
|
+
require_relative "betamax/rspec"
|
|
12
|
+
require_relative "betamax/tape"
|
|
13
|
+
require_relative "betamax/version"
|
|
14
|
+
|
|
15
|
+
module Betamax
|
|
16
|
+
extend RSpec
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def record object
|
|
21
|
+
player = current_player
|
|
22
|
+
|
|
23
|
+
raise Errors::NoTapeInserted unless player
|
|
24
|
+
|
|
25
|
+
player.record object
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def current_player
|
|
29
|
+
Fiber[:betamax_player]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Betamax.install_rspec! if defined? RSpec
|
data/sig/betamax.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: betamax
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- John DeSilva
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Betamax allows for the recording and playback of arbitrary Ruby objects
|
|
13
|
+
to simplify testing external dependencies
|
|
14
|
+
email:
|
|
15
|
+
- john@aesthetikx.info
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- README.md
|
|
22
|
+
- Rakefile
|
|
23
|
+
- lib/betamax.rb
|
|
24
|
+
- lib/betamax/errors.rb
|
|
25
|
+
- lib/betamax/method_player.rb
|
|
26
|
+
- lib/betamax/method_recorder.rb
|
|
27
|
+
- lib/betamax/player.rb
|
|
28
|
+
- lib/betamax/recorded_method.rb
|
|
29
|
+
- lib/betamax/recorded_object.rb
|
|
30
|
+
- lib/betamax/recorded_yielding.rb
|
|
31
|
+
- lib/betamax/recording.rb
|
|
32
|
+
- lib/betamax/rspec.rb
|
|
33
|
+
- lib/betamax/tape.rb
|
|
34
|
+
- lib/betamax/version.rb
|
|
35
|
+
- sig/betamax.rbs
|
|
36
|
+
homepage: https://github.com/Aesthetikx/betamax
|
|
37
|
+
licenses: []
|
|
38
|
+
metadata:
|
|
39
|
+
homepage_uri: https://github.com/Aesthetikx/betamax
|
|
40
|
+
source_code_uri: https://github.com/Aesthetikx/betamax
|
|
41
|
+
changelog_uri: https://github.com/Aesthetikx/betamax/blob/main/CHANGELOG.md
|
|
42
|
+
rubygems_mfa_required: 'true'
|
|
43
|
+
rdoc_options: []
|
|
44
|
+
require_paths:
|
|
45
|
+
- lib
|
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 3.2.0
|
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '0'
|
|
56
|
+
requirements: []
|
|
57
|
+
rubygems_version: 4.0.10
|
|
58
|
+
specification_version: 4
|
|
59
|
+
summary: Record and playback of arbitrary Ruby objects
|
|
60
|
+
test_files: []
|