backspin 0.11.0 → 0.13.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -0
  3. data/.gitignore +1 -0
  4. data/.ruby-version +1 -1
  5. data/.standard.yml +5 -0
  6. data/CHANGELOG.md +13 -4
  7. data/Gemfile.lock +27 -25
  8. data/README.md +80 -1
  9. data/Rakefile +27 -0
  10. data/backspin.gemspec +3 -1
  11. data/docs/compare-api-plan.md +193 -0
  12. data/fixtures/projects/dummy_cli_gem/.rspec +3 -0
  13. data/fixtures/projects/dummy_cli_gem/Gemfile +12 -0
  14. data/fixtures/projects/dummy_cli_gem/Gemfile.lock +55 -0
  15. data/fixtures/projects/dummy_cli_gem/LICENSE.txt +1 -0
  16. data/fixtures/projects/dummy_cli_gem/README.md +7 -0
  17. data/fixtures/projects/dummy_cli_gem/Rakefile +8 -0
  18. data/fixtures/projects/dummy_cli_gem/dummy_cli_gem.gemspec +22 -0
  19. data/fixtures/projects/dummy_cli_gem/exe/dummy_cli_gem +8 -0
  20. data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_echo.yml +18 -0
  21. data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_ls.yml +18 -0
  22. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/cli.rb +35 -0
  23. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/version.rb +5 -0
  24. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem.rb +7 -0
  25. data/fixtures/projects/dummy_cli_gem/mise.toml +2 -0
  26. data/fixtures/projects/dummy_cli_gem/spec/dummy_cli_gem_backspin_spec.rb +46 -0
  27. data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_echo.yml +18 -0
  28. data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_ls.yml +18 -0
  29. data/fixtures/projects/dummy_cli_gem/spec/fixtures/listing_target/alpha.txt +1 -0
  30. data/fixtures/projects/dummy_cli_gem/spec/spec_helper.rb +22 -0
  31. data/lib/backspin/configuration.rb +13 -0
  32. data/lib/backspin/record.rb +1 -1
  33. data/lib/backspin/recorder.rb +1 -1
  34. data/lib/backspin/version.rb +1 -1
  35. data/lib/backspin.rb +91 -25
  36. data/mise.toml +2 -0
  37. metadata +39 -5
  38. data/script/run_affected_tests +0 -179
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+
6
+ require "dummy_cli_gem"
7
+
8
+ exit(DummyCliGem::CLI.run(ARGV))
@@ -0,0 +1,18 @@
1
+ ---
2
+ format_version: '4.1'
3
+ first_recorded_at: '2026-02-11T01:57:42-06:00'
4
+ recorded_at: '2026-02-11T01:57:42-06:00'
5
+ record_count: 1
6
+ snapshot:
7
+ command_type: Open3::Capture3
8
+ args:
9
+ - ruby
10
+ - exe/dummy_cli_gem
11
+ - echo
12
+ - hello from dummy gem
13
+ stdout: 'hello from dummy gem
14
+
15
+ '
16
+ stderr: ''
17
+ status: 0
18
+ recorded_at: '2026-02-11T01:57:42-06:00'
@@ -0,0 +1,18 @@
1
+ ---
2
+ format_version: '4.1'
3
+ first_recorded_at: '2026-02-11T01:57:42-06:00'
4
+ recorded_at: '2026-02-11T01:57:42-06:00'
5
+ record_count: 1
6
+ snapshot:
7
+ command_type: Open3::Capture3
8
+ args:
9
+ - ruby
10
+ - exe/dummy_cli_gem
11
+ - list
12
+ - spec/fixtures/listing_target
13
+ stdout: 'alpha.txt
14
+
15
+ '
16
+ stderr: ''
17
+ status: 0
18
+ recorded_at: '2026-02-11T01:57:42-06:00'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module DummyCliGem
6
+ module CLI
7
+ module_function
8
+
9
+ def run(argv, out: $stdout, err: $stderr)
10
+ command = argv.first
11
+
12
+ case command
13
+ when "echo"
14
+ text = argv[1]
15
+ return usage(err) if text.nil? || text.empty?
16
+
17
+ stdout, stderr, status = Open3.capture3("echo", text)
18
+ when "list"
19
+ target = argv[1] || "."
20
+ stdout, stderr, status = Open3.capture3("ls", "-1", target)
21
+ else
22
+ return usage(err)
23
+ end
24
+
25
+ out.print(stdout)
26
+ err.print(stderr)
27
+ status.exitstatus
28
+ end
29
+
30
+ def usage(err)
31
+ err.puts("usage: dummy_cli_gem echo <text> | dummy_cli_gem list <path>")
32
+ 1
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DummyCliGem
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dummy_cli_gem/version"
4
+ require "dummy_cli_gem/cli"
5
+
6
+ module DummyCliGem
7
+ end
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.0"
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "DummyCliGem Backspin full-stack fixture" do
6
+ let(:project_root) { Pathname(__dir__).join("..").expand_path }
7
+
8
+ it "verifies echo command output from committed YAML" do
9
+ expect(BACKSPIN_DIR.join("dummy_echo.yml")).to exist
10
+
11
+ result = Backspin.run(
12
+ ["ruby", "exe/dummy_cli_gem", "echo", "hello from dummy gem"],
13
+ name: "dummy_echo"
14
+ )
15
+
16
+ expect(result).to be_verified
17
+ expect(result.actual.stdout).to eq("hello from dummy gem\n")
18
+ expect(result.actual.stderr).to eq("")
19
+ expect(result.actual.status).to eq(0)
20
+ end
21
+
22
+ it "verifies list command output from committed YAML" do
23
+ expect(BACKSPIN_DIR.join("dummy_ls.yml")).to exist
24
+
25
+ result = Backspin.run(
26
+ ["ruby", "exe/dummy_cli_gem", "list", "spec/fixtures/listing_target"],
27
+ name: "dummy_ls"
28
+ )
29
+
30
+ expect(result).to be_verified
31
+ expect(result.actual.stdout).to eq("alpha.txt\n")
32
+ expect(result.actual.stderr).to eq("")
33
+ expect(result.actual.status).to eq(0)
34
+ end
35
+
36
+ it "uses current Backspin record format for fixture YAML files" do
37
+ %w[dummy_echo dummy_ls].each do |record_name|
38
+ record_path = BACKSPIN_DIR.join("#{record_name}.yml")
39
+ expect(record_path).to exist
40
+
41
+ record_data = YAML.load_file(record_path)
42
+ expect(record_data["format_version"]).to eq("4.1")
43
+ expect(record_data["snapshot"]["command_type"]).to eq("Open3::Capture3")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ ---
2
+ format_version: '4.1'
3
+ first_recorded_at: '2026-02-11T07:43:21+00:00'
4
+ recorded_at: '2026-02-11T07:43:21+00:00'
5
+ record_count: 1
6
+ snapshot:
7
+ command_type: Open3::Capture3
8
+ args:
9
+ - ruby
10
+ - exe/dummy_cli_gem
11
+ - echo
12
+ - hello from dummy gem
13
+ stdout: 'hello from dummy gem
14
+
15
+ '
16
+ stderr: ''
17
+ status: 0
18
+ recorded_at: '2026-02-11T07:43:21+00:00'
@@ -0,0 +1,18 @@
1
+ ---
2
+ format_version: '4.1'
3
+ first_recorded_at: '2026-02-11T07:43:21+00:00'
4
+ recorded_at: '2026-02-11T07:43:21+00:00'
5
+ record_count: 1
6
+ snapshot:
7
+ command_type: Open3::Capture3
8
+ args:
9
+ - ruby
10
+ - exe/dummy_cli_gem
11
+ - list
12
+ - spec/fixtures/listing_target
13
+ stdout: 'alpha.txt
14
+
15
+ '
16
+ stderr: ''
17
+ status: 0
18
+ recorded_at: '2026-02-11T07:43:21+00:00'
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "pathname"
5
+ require "yaml"
6
+ require "backspin"
7
+ require "dummy_cli_gem"
8
+
9
+ BACKSPIN_DIR = Pathname(__dir__).join("fixtures", "backspin")
10
+
11
+ Backspin.configure do |config|
12
+ config.backspin_dir = BACKSPIN_DIR
13
+ config.logger = nil
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ config.disable_monkey_patching!
18
+
19
+ config.expect_with :rspec do |expectations|
20
+ expectations.syntax = :expect
21
+ end
22
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "logger"
3
4
  require "pathname"
4
5
 
5
6
  module Backspin
@@ -10,6 +11,8 @@ module Backspin
10
11
  attr_accessor :backspin_dir
11
12
  # Whether to raise an exception when verification fails in `run`/`capture` - defaults to true
12
13
  attr_accessor :raise_on_verification_failure
14
+ # Logger for Backspin diagnostics - defaults to WARN level, logfmt-lite format
15
+ attr_accessor :logger
13
16
  # Regex patterns to scrub from saved output
14
17
  attr_reader :credential_patterns
15
18
 
@@ -18,6 +21,7 @@ module Backspin
18
21
  @raise_on_verification_failure = true
19
22
  @credential_patterns = default_credential_patterns
20
23
  @backspin_dir = Pathname(Dir.pwd).join("fixtures", "backspin")
24
+ @logger = default_logger
21
25
  end
22
26
 
23
27
  def add_credential_pattern(pattern)
@@ -34,6 +38,15 @@ module Backspin
34
38
 
35
39
  private
36
40
 
41
+ def default_logger
42
+ logger = Logger.new($stdout)
43
+ logger.level = Logger::WARN
44
+ logger.formatter = proc { |severity, _time, _progname, msg|
45
+ "level=#{severity.downcase} lib=backspin #{msg}\n"
46
+ }
47
+ logger
48
+ end
49
+
37
50
  # Some default patterns for common credential types
38
51
  def default_credential_patterns
39
52
  [
@@ -44,7 +44,7 @@ module Backspin
44
44
 
45
45
  def set_snapshot(snapshot)
46
46
  @snapshot = snapshot
47
- snapshot_recorded_at = snapshot.recorded_at || Time.now.iso8601
47
+ snapshot_recorded_at = snapshot.recorded_at || Time.now.utc.iso8601
48
48
  @first_recorded_at ||= snapshot_recorded_at
49
49
  @recorded_at = snapshot_recorded_at
50
50
  self
@@ -26,7 +26,7 @@ module Backspin
26
26
  stdout: captured_stdout,
27
27
  stderr: captured_stderr,
28
28
  status: 0,
29
- recorded_at: Time.now.iso8601
29
+ recorded_at: Time.now.utc.iso8601
30
30
  )
31
31
 
32
32
  record.set_snapshot(actual_snapshot)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Backspin
4
- VERSION = "0.11.0"
4
+ VERSION = "0.13.0"
5
5
  end
data/lib/backspin.rb CHANGED
@@ -4,6 +4,7 @@ require "yaml"
4
4
  require "fileutils"
5
5
  require "open3"
6
6
  require "pathname"
7
+ require "time"
7
8
  require "backspin/version"
8
9
  require "backspin/configuration"
9
10
  require "backspin/snapshot"
@@ -14,8 +15,14 @@ require "backspin/backspin_result"
14
15
  require "backspin/recorder"
15
16
 
16
17
  module Backspin
18
+ VALID_MODES = %i[auto record verify].freeze
19
+
17
20
  class RecordNotFoundError < StandardError; end
18
21
 
22
+ # Raised when the reference command in a compare produced no output at all,
23
+ # which almost always means it failed to run rather than that it is correct.
24
+ class ReferenceCommandError < StandardError; end
25
+
19
26
  class VerificationError < StandardError
20
27
  attr_reader :result
21
28
 
@@ -113,6 +120,49 @@ module Backspin
113
120
  perform_capture(record_name, mode: mode, matcher: matcher, filter: filter, filter_on: filter_on, &block)
114
121
  end
115
122
 
123
+ # Differential testing - runs a reference command and a command under test
124
+ # and compares their filtered output. Nothing is recorded to disk.
125
+ #
126
+ # Only stdout, stderr, and status are compared, so the two commands may
127
+ # differ in argv and env without any normalization.
128
+ #
129
+ # @param reference [String, Array] Command that defines the correct output
130
+ # @param actual [String, Array] Command under test
131
+ # @param env [Hash] Environment variables passed to both commands
132
+ # @param matcher [Proc, Hash] Custom matcher for verification
133
+ # @param filter [Proc] Custom filter applied to both sides before comparing
134
+ # @return [BackspinResult] expected = reference, actual = command under test
135
+ def compare(reference:, actual:, env: nil, matcher: nil, filter: nil)
136
+ normalized_env = normalize_env(env)
137
+
138
+ expected_snapshot, = capture_command_snapshot(reference, normalized_env)
139
+ if expected_snapshot.stdout.empty? && expected_snapshot.stderr.empty?
140
+ raise ReferenceCommandError,
141
+ "Reference command produced no output (exit status #{expected_snapshot.status}): #{reference.inspect}"
142
+ end
143
+
144
+ actual_snapshot, = capture_command_snapshot(actual, normalized_env)
145
+
146
+ command_diff = CommandDiff.new(
147
+ expected: expected_snapshot,
148
+ actual: actual_snapshot,
149
+ matcher: matcher,
150
+ filter: filter
151
+ )
152
+ result = BackspinResult.new(
153
+ mode: :verify,
154
+ record_path: nil,
155
+ actual: actual_snapshot,
156
+ expected: expected_snapshot,
157
+ verified: command_diff.verified?,
158
+ command_diff: command_diff
159
+ )
160
+
161
+ raise_on_verification_failure!(result)
162
+
163
+ result
164
+ end
165
+
116
166
  private
117
167
 
118
168
  def perform_capture(record_name, mode:, matcher:, filter:, filter_on:, &block)
@@ -145,27 +195,18 @@ module Backspin
145
195
 
146
196
  record = Record.load_or_create(record_path)
147
197
 
148
- normalized_env = env.nil? ? nil : normalize_env(env)
198
+ normalized_env = normalize_env(env)
149
199
 
150
200
  result = case mode
151
201
  when :record
152
- stdout, stderr, status = execute_command(command, normalized_env)
153
- actual_snapshot = Snapshot.new(
154
- command_type: Open3::Capture3,
155
- args: command,
156
- env: normalized_env,
157
- stdout: stdout,
158
- stderr: stderr,
159
- status: status.exitstatus,
160
- recorded_at: Time.now.iso8601
161
- )
202
+ actual_snapshot, output = capture_command_snapshot(command, normalized_env)
162
203
  record.set_snapshot(actual_snapshot)
163
204
  record.save(filter: filter)
164
205
  BackspinResult.new(
165
206
  mode: :record,
166
207
  record_path: record.path,
167
208
  actual: actual_snapshot,
168
- output: [stdout, stderr, status]
209
+ output: output
169
210
  )
170
211
  when :verify
171
212
  raise RecordNotFoundError, "Record not found: #{record.path}" unless record.exists?
@@ -176,15 +217,7 @@ module Backspin
176
217
  raise RecordFormatError, "Invalid record format: expected Open3::Capture3 for run"
177
218
  end
178
219
 
179
- stdout, stderr, status = execute_command(command, normalized_env)
180
- actual_snapshot = Snapshot.new(
181
- command_type: Open3::Capture3,
182
- args: command,
183
- env: normalized_env,
184
- stdout: stdout,
185
- stderr: stderr,
186
- status: status.exitstatus
187
- )
220
+ actual_snapshot, output = capture_command_snapshot(command, normalized_env)
188
221
  command_diff = CommandDiff.new(
189
222
  expected: expected_snapshot,
190
223
  actual: actual_snapshot,
@@ -199,7 +232,7 @@ module Backspin
199
232
  expected: expected_snapshot,
200
233
  verified: command_diff.verified?,
201
234
  command_diff: command_diff,
202
- output: [stdout, stderr, status]
235
+ output: output
203
236
  )
204
237
  else
205
238
  raise ArgumentError, "Unknown mode: #{mode}"
@@ -210,7 +243,22 @@ module Backspin
210
243
  result
211
244
  end
212
245
 
246
+ def capture_command_snapshot(command, env)
247
+ stdout, stderr, status = execute_command(command, env)
248
+ snapshot = Snapshot.new(
249
+ command_type: Open3::Capture3,
250
+ args: command,
251
+ env: env,
252
+ stdout: stdout,
253
+ stderr: stderr,
254
+ status: status.exitstatus,
255
+ recorded_at: Time.now.utc.iso8601
256
+ )
257
+ [snapshot, [stdout, stderr, status]]
258
+ end
259
+
213
260
  def normalize_env(env)
261
+ return nil if env.nil?
214
262
  raise ArgumentError, "env must be a Hash" unless env.is_a?(Hash)
215
263
 
216
264
  env.empty? ? nil : env
@@ -232,7 +280,7 @@ module Backspin
232
280
  return unless configuration.raise_on_verification_failure && result.verified? == false
233
281
 
234
282
  error_message = "Backspin verification failed!\n"
235
- error_message += "Record: #{result.record_path}\n"
283
+ error_message += "Record: #{result.record_path}\n" if result.record_path
236
284
  details = result.error_message || result.diff
237
285
  error_message += "\n#{details}" if details
238
286
 
@@ -242,8 +290,26 @@ module Backspin
242
290
  def determine_mode(mode_option, record_path)
243
291
  return mode_option if mode_option && mode_option != :auto
244
292
 
245
- # Auto mode: record if file doesn't exist, verify if it does
246
- File.exist?(record_path) ? :verify : :record
293
+ env_mode = mode_from_env
294
+ if env_mode && env_mode != :auto
295
+ configuration.logger&.debug { "event=mode_resolved mode=#{env_mode} source=env record=#{record_path}" }
296
+ return env_mode
297
+ end
298
+
299
+ resolved = File.exist?(record_path) ? :verify : :record
300
+ configuration.logger&.debug { "event=mode_resolved mode=#{resolved} source=auto record=#{record_path}" }
301
+ resolved
302
+ end
303
+
304
+ def mode_from_env
305
+ raw = ENV["BACKSPIN_MODE"]
306
+ return if raw.nil? || raw.strip.empty?
307
+
308
+ mode = raw.strip.downcase.to_sym
309
+ return mode if VALID_MODES.include?(mode)
310
+
311
+ raise ArgumentError,
312
+ "Invalid BACKSPIN_MODE value: #{raw.inspect}. Allowed values: auto, record, verify"
247
313
  end
248
314
 
249
315
  def validate_mode!(mode)
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.0"
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backspin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Sanheim
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: logger
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  description: Backspin is a Ruby library for characterization testing of command-line
13
27
  interfaces. Inspired by VCR's cassette-based approach, it records and replays CLI
14
28
  interactions to make testing faster and more deterministic.
@@ -38,8 +52,28 @@ files:
38
52
  - bin/rspec
39
53
  - bin/setup
40
54
  - docs/backspin-result-api-sketch.md
55
+ - docs/compare-api-plan.md
41
56
  - examples/match_on_example.rb
42
57
  - fixtures/backspin/.gitkeep
58
+ - fixtures/projects/dummy_cli_gem/.rspec
59
+ - fixtures/projects/dummy_cli_gem/Gemfile
60
+ - fixtures/projects/dummy_cli_gem/Gemfile.lock
61
+ - fixtures/projects/dummy_cli_gem/LICENSE.txt
62
+ - fixtures/projects/dummy_cli_gem/README.md
63
+ - fixtures/projects/dummy_cli_gem/Rakefile
64
+ - fixtures/projects/dummy_cli_gem/dummy_cli_gem.gemspec
65
+ - fixtures/projects/dummy_cli_gem/exe/dummy_cli_gem
66
+ - fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_echo.yml
67
+ - fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_ls.yml
68
+ - fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem.rb
69
+ - fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/cli.rb
70
+ - fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/version.rb
71
+ - fixtures/projects/dummy_cli_gem/mise.toml
72
+ - fixtures/projects/dummy_cli_gem/spec/dummy_cli_gem_backspin_spec.rb
73
+ - fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_echo.yml
74
+ - fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_ls.yml
75
+ - fixtures/projects/dummy_cli_gem/spec/fixtures/listing_target/alpha.txt
76
+ - fixtures/projects/dummy_cli_gem/spec/spec_helper.rb
43
77
  - lib/backspin.rb
44
78
  - lib/backspin/backspin_result.rb
45
79
  - lib/backspin/command_diff.rb
@@ -49,9 +83,9 @@ files:
49
83
  - lib/backspin/recorder.rb
50
84
  - lib/backspin/snapshot.rb
51
85
  - lib/backspin/version.rb
86
+ - mise.toml
52
87
  - release.rake
53
88
  - script/lint
54
- - script/run_affected_tests
55
89
  homepage: https://github.com/rsanheim/backspin
56
90
  licenses:
57
91
  - MIT
@@ -66,14 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
100
  requirements:
67
101
  - - ">="
68
102
  - !ruby/object:Gem::Version
69
- version: 3.1.0
103
+ version: 3.2.0
70
104
  required_rubygems_version: !ruby/object:Gem::Requirement
71
105
  requirements:
72
106
  - - ">="
73
107
  - !ruby/object:Gem::Version
74
108
  version: '0'
75
109
  requirements: []
76
- rubygems_version: 4.0.3
110
+ rubygems_version: 4.0.16
77
111
  specification_version: 4
78
112
  summary: Record and replay CLI interactions for testing
79
113
  test_files: []