backspin 0.5.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/lib/backspin/command.rb +7 -0
- data/lib/backspin/recorder.rb +144 -0
- data/lib/backspin/version.rb +1 -1
- data/lib/backspin.rb +37 -0
- data/release.rake +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e49507f2d196ebd4d7b4d39a6013d840224a357272fb5ba4880702e26d28c8d9
|
4
|
+
data.tar.gz: cfee20f6ff8b65c622bfc9482d85e49c78f78e1e9a2c91b5f09f71e2b1d61663
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 660d3af239a57512fbd74eecf2f7fc81c91a6fc42c0a95957952f01931bcd2b5f5387af2004557af985c8c3ef6f37c6b284e41b757af2919a241d5408060b25e
|
7
|
+
data.tar.gz: 6a0b7f7e2bc4533e80e8011fb17c1f851c31ddd6e831300efec0589423f541640e1ace953c75f066e8a9efc26c6e3fef886cc87941d204e09d1a2302d58adcbc
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.5.0 - 2025-06-11
|
4
|
+
* Simplify matcher API so user provided matchers override defaults - [#14](https://github.com/rsanheim/backspin/pull/14)
|
5
|
+
* Also extract a proper `Matcher` object
|
6
|
+
|
3
7
|
## 0.4.2 - 2025-06-10
|
4
8
|
Unified `:match` API for customizing how actual commands are matched against recorded commands. - [#11](https://github.com/rsanheim/backspin/pull/11)
|
5
9
|
|
data/Gemfile.lock
CHANGED
data/lib/backspin/command.rb
CHANGED
@@ -52,6 +52,8 @@ module Backspin
|
|
52
52
|
Open3::Capture3
|
53
53
|
when "Kernel::System"
|
54
54
|
::Kernel::System
|
55
|
+
when "Backspin::Capturer"
|
56
|
+
Backspin::Capturer
|
55
57
|
else
|
56
58
|
# Default to capture3 for backwards compatibility
|
57
59
|
Open3::Capture3
|
@@ -97,3 +99,8 @@ end
|
|
97
99
|
module ::Kernel
|
98
100
|
class System; end
|
99
101
|
end
|
102
|
+
|
103
|
+
# Define the Backspin::Capturer class for identification
|
104
|
+
module Backspin
|
105
|
+
class Capturer; end
|
106
|
+
end
|
data/lib/backspin/recorder.rb
CHANGED
@@ -136,6 +136,150 @@ module Backspin
|
|
136
136
|
)
|
137
137
|
end
|
138
138
|
|
139
|
+
# Performs capture recording by intercepting all stdout/stderr output
|
140
|
+
def perform_capture_recording
|
141
|
+
require "tempfile"
|
142
|
+
|
143
|
+
# Create temporary files for capturing output
|
144
|
+
stdout_tempfile = Tempfile.new("backspin_stdout")
|
145
|
+
stderr_tempfile = Tempfile.new("backspin_stderr")
|
146
|
+
|
147
|
+
begin
|
148
|
+
# Save original file descriptors
|
149
|
+
original_stdout_fd = $stdout.dup
|
150
|
+
original_stderr_fd = $stderr.dup
|
151
|
+
|
152
|
+
# Redirect both Ruby IO and file descriptors
|
153
|
+
$stdout.reopen(stdout_tempfile)
|
154
|
+
$stderr.reopen(stderr_tempfile)
|
155
|
+
|
156
|
+
# Execute the block
|
157
|
+
result = yield
|
158
|
+
|
159
|
+
# Flush and read captured output
|
160
|
+
$stdout.flush
|
161
|
+
$stderr.flush
|
162
|
+
stdout_tempfile.rewind
|
163
|
+
stderr_tempfile.rewind
|
164
|
+
|
165
|
+
captured_stdout = stdout_tempfile.read
|
166
|
+
captured_stderr = stderr_tempfile.read
|
167
|
+
|
168
|
+
# Create a single command representing all captured output
|
169
|
+
command = Command.new(
|
170
|
+
method_class: Backspin::Capturer,
|
171
|
+
args: ["<captured block>"],
|
172
|
+
stdout: captured_stdout,
|
173
|
+
stderr: captured_stderr,
|
174
|
+
status: 0,
|
175
|
+
recorded_at: Time.now.iso8601
|
176
|
+
)
|
177
|
+
|
178
|
+
record.add_command(command)
|
179
|
+
record.save(filter: @filter)
|
180
|
+
|
181
|
+
RecordResult.new(output: result, mode: :record, record: record)
|
182
|
+
ensure
|
183
|
+
# Restore original file descriptors
|
184
|
+
$stdout.reopen(original_stdout_fd)
|
185
|
+
$stderr.reopen(original_stderr_fd)
|
186
|
+
original_stdout_fd.close
|
187
|
+
original_stderr_fd.close
|
188
|
+
|
189
|
+
# Clean up temp files
|
190
|
+
stdout_tempfile.close!
|
191
|
+
stderr_tempfile.close!
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Performs capture verification by capturing output and comparing with recorded values
|
196
|
+
def perform_capture_verification
|
197
|
+
raise RecordNotFoundError, "Record not found: #{record.path}" unless record.exists?
|
198
|
+
raise RecordNotFoundError, "No commands found in record #{record.path}" if record.empty?
|
199
|
+
|
200
|
+
require "tempfile"
|
201
|
+
|
202
|
+
# Create temporary files for capturing output
|
203
|
+
stdout_tempfile = Tempfile.new("backspin_stdout")
|
204
|
+
stderr_tempfile = Tempfile.new("backspin_stderr")
|
205
|
+
|
206
|
+
begin
|
207
|
+
# Save original file descriptors
|
208
|
+
original_stdout_fd = $stdout.dup
|
209
|
+
original_stderr_fd = $stderr.dup
|
210
|
+
|
211
|
+
# Redirect both Ruby IO and file descriptors
|
212
|
+
$stdout.reopen(stdout_tempfile)
|
213
|
+
$stderr.reopen(stderr_tempfile)
|
214
|
+
|
215
|
+
# Execute the block
|
216
|
+
output = yield
|
217
|
+
|
218
|
+
# Flush and read captured output
|
219
|
+
$stdout.flush
|
220
|
+
$stderr.flush
|
221
|
+
stdout_tempfile.rewind
|
222
|
+
stderr_tempfile.rewind
|
223
|
+
|
224
|
+
captured_stdout = stdout_tempfile.read
|
225
|
+
captured_stderr = stderr_tempfile.read
|
226
|
+
|
227
|
+
# Get the recorded command (should be only one for capture)
|
228
|
+
recorded_command = record.commands.first
|
229
|
+
|
230
|
+
# Create actual command from captured output
|
231
|
+
actual_command = Command.new(
|
232
|
+
method_class: Backspin::Capturer,
|
233
|
+
args: ["<captured block>"],
|
234
|
+
stdout: captured_stdout,
|
235
|
+
stderr: captured_stderr,
|
236
|
+
status: 0
|
237
|
+
)
|
238
|
+
|
239
|
+
# Create CommandDiff for comparison
|
240
|
+
command_diff = CommandDiff.new(
|
241
|
+
recorded_command: recorded_command,
|
242
|
+
actual_command: actual_command,
|
243
|
+
matcher: @matcher
|
244
|
+
)
|
245
|
+
|
246
|
+
RecordResult.new(
|
247
|
+
output: output,
|
248
|
+
mode: :verify,
|
249
|
+
verified: command_diff.verified?,
|
250
|
+
record: record,
|
251
|
+
command_diffs: [command_diff]
|
252
|
+
)
|
253
|
+
ensure
|
254
|
+
# Restore original file descriptors
|
255
|
+
$stdout.reopen(original_stdout_fd)
|
256
|
+
$stderr.reopen(original_stderr_fd)
|
257
|
+
original_stdout_fd.close
|
258
|
+
original_stderr_fd.close
|
259
|
+
|
260
|
+
# Clean up temp files
|
261
|
+
stdout_tempfile.close!
|
262
|
+
stderr_tempfile.close!
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Performs capture playback - executes block normally but could optionally suppress output
|
267
|
+
def perform_capture_playback
|
268
|
+
raise RecordNotFoundError, "Record not found: #{record.path}" unless record.exists?
|
269
|
+
raise RecordNotFoundError, "No commands found in record" if record.empty?
|
270
|
+
|
271
|
+
# For now, just execute the block normally
|
272
|
+
# In the future, we could optionally suppress output or return recorded output
|
273
|
+
output = yield
|
274
|
+
|
275
|
+
RecordResult.new(
|
276
|
+
output: output,
|
277
|
+
mode: :playback,
|
278
|
+
verified: true,
|
279
|
+
record: record
|
280
|
+
)
|
281
|
+
end
|
282
|
+
|
139
283
|
private
|
140
284
|
|
141
285
|
def setup_capture3_replay_stub
|
data/lib/backspin/version.rb
CHANGED
data/lib/backspin.rb
CHANGED
@@ -116,6 +116,43 @@ module Backspin
|
|
116
116
|
result
|
117
117
|
end
|
118
118
|
|
119
|
+
# Captures all stdout/stderr output from a block
|
120
|
+
#
|
121
|
+
# @param record_name [String] Name for the record file
|
122
|
+
# @param mode [Symbol] Recording mode - :auto, :record, :verify, :playback
|
123
|
+
# @param matcher [Proc, Hash] Custom matcher for verification
|
124
|
+
# @param filter [Proc] Custom filter for recorded data
|
125
|
+
# @return [RecordResult] Result object with captured output
|
126
|
+
def capture(record_name, mode: :auto, matcher: nil, filter: nil, &block)
|
127
|
+
raise ArgumentError, "record_name is required" if record_name.nil? || record_name.empty?
|
128
|
+
raise ArgumentError, "block is required" unless block_given?
|
129
|
+
|
130
|
+
record_path = Record.build_record_path(record_name)
|
131
|
+
mode = determine_mode(mode, record_path)
|
132
|
+
|
133
|
+
# Create or load the record based on mode
|
134
|
+
record = if mode == :record
|
135
|
+
Record.create(record_name)
|
136
|
+
else
|
137
|
+
Record.load_or_create(record_path)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Create recorder with all needed context
|
141
|
+
recorder = Recorder.new(record: record, mode: mode, matcher: matcher, filter: filter)
|
142
|
+
|
143
|
+
# Execute the appropriate mode
|
144
|
+
case mode
|
145
|
+
when :record
|
146
|
+
recorder.perform_capture_recording(&block)
|
147
|
+
when :verify
|
148
|
+
recorder.perform_capture_verification(&block)
|
149
|
+
when :playback
|
150
|
+
recorder.perform_capture_playback(&block)
|
151
|
+
else
|
152
|
+
raise ArgumentError, "Unknown mode: #{mode}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
119
156
|
private
|
120
157
|
|
121
158
|
def determine_mode(mode_option, record_path)
|
data/release.rake
CHANGED
@@ -4,7 +4,7 @@ require "bundler/gem_tasks"
|
|
4
4
|
|
5
5
|
# Simplified release tasks using gem-release
|
6
6
|
# Install with: gem install gem-release
|
7
|
-
|
7
|
+
# https://github.com/svenfuchs/gem-release
|
8
8
|
namespace :release do
|
9
9
|
desc "Release a new version (bump, tag, release)"
|
10
10
|
task :version, [:level] do |t, args|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backspin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Sanheim
|
@@ -153,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
153
|
- !ruby/object:Gem::Version
|
154
154
|
version: '0'
|
155
155
|
requirements: []
|
156
|
-
rubygems_version: 3.6.
|
156
|
+
rubygems_version: 3.6.7
|
157
157
|
specification_version: 4
|
158
158
|
summary: Record and replay CLI interactions for testing
|
159
159
|
test_files: []
|