backspin 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3937af0a8fc0d808e6cc2f8788481ee479a46b6c21353a7c73820711995f9b96
4
- data.tar.gz: a30e4ebd5608ad6718fc0d5cbda3685adc49b97cbb5af188db604f3b1b0de3be
3
+ metadata.gz: c8a6c6a0ef97c99ace7fb068e7b85bc40c2f45594bc30ce5002920fd62fcd384
4
+ data.tar.gz: f62c15526c0a19a4ae876b8c737b172e2557c54a55f4d5045a0dbc499ccfcd51
5
5
  SHA512:
6
- metadata.gz: 1343b731c00281871a50f4d61e276068d4aa18d54ba60f80f7ad780f88191c06f6af5168d53c7559f82c9d722e67521b8ca56aeb7998a2f61f410b925caff1aa
7
- data.tar.gz: a3e496f886dae99cc6f624399655955b18b131991f332b2470ecd57ed74fb7e4c8e58a8cf89237f2faa9e10456ab67566ebc18da3c6fdc253716a8abb4728061
6
+ metadata.gz: 117a79e8e448bae03c68b44e8a6de23671d746d2ea664094e7dc82792d8ff323af7c6faf73f85a9aa020bb259127660abc8d7687cf861b627049b6e3b88ff41f
7
+ data.tar.gz: dfeabf728ef8448d01889dbe2d62d9d2a8236da296d9f6ee21619f6e3f976218cd0af3da2ac8b7f4ab1ff9cbc19dd4afaef1ca64ac777f3f9215180a1349c1c0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 - 2026-02-11
4
+ * Breaking: `Backspin.run` and `Backspin.capture` now return `Backspin::BackspinResult` with explicit `result.actual` / `result.expected` snapshots.
5
+ * Breaking: result convenience accessors (`result.stdout`, `result.stderr`, `result.status`) were removed in favor of snapshot access.
6
+ * Breaking: record format bumped to 4.0 and now persists a single `snapshot` object (v3 records are rejected).
7
+ * Simplification: removed legacy `Command`, `CommandResult`, and `RecordResult` layers; matcher/diff now operate directly on snapshots.
8
+ * Added focused coverage for the new result contract and capture stream restoration behavior.
9
+ * Updated project docs to reflect the BackspinResult + Snapshot API surface.
10
+
3
11
  ## 0.8.0 - 2026-02-05
4
12
  * Breaking: new `Backspin.run("command", name:, env:)` command API plus block capture via `Backspin.run(name:) { ... }` and `Backspin.capture("name") { ... }`
5
13
  * Breaking: remove `run!` and `:playback`
data/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Project Overview
6
6
 
7
- Backspin is a Ruby gem for characterization testing of command-line interfaces. It records and replays CLI interactions by capturing stdout, stderr, and exit status from shell commands - similar to how VCR works for HTTP interactions. Backspin uses "records" (YAML files) to store recorded command outputs.
7
+ Backspin is a Ruby gem for characterization testing of command-line interfaces. It records and verifies CLI interactions by capturing stdout, stderr, and exit status from shell commands, similar to how VCR works for HTTP interactions. Backspin uses YAML "records" to store snapshots.
8
8
 
9
9
  ## Development Commands
10
10
 
@@ -45,16 +45,21 @@ bin/rake standard # Alternative: Run via Rake task
45
45
  - Credential scrubbing logic
46
46
  - Configuration management (including `raise_on_verification_failure` which defaults to `true`)
47
47
 
48
- **Command Class** (`lib/backspin/command.rb`)
49
- - Represents a single CLI execution
50
- - Stores: args, stdout, stderr, status, recorded_at
48
+ **Snapshot Class** (`lib/backspin/snapshot.rb`)
49
+ - Represents a single captured execution snapshot
50
+ - Stores: command type, args, env, stdout, stderr, status, recorded_at
51
+
52
+ **BackspinResult Class** (`lib/backspin/backspin_result.rb`)
53
+ - Return object from `run` and `capture`
54
+ - Exposes `actual` and `expected` snapshots plus verification metadata
51
55
 
52
56
  **Record Class** (`lib/backspin/record.rb`)
53
57
  - Manages YAML record files
54
- - Handles recording/playback sequencing
58
+ - Handles record/verify sequencing
55
59
 
56
- **RSpecMetadata** (`lib/backspin/rspec_metadata.rb`)
57
- - Auto-generates record names from RSpec context
60
+ **Recorder Class** (`lib/backspin/recorder.rb`)
61
+ - Implements block capture recording and verification
62
+ - Restores stdout/stderr streams safely after capture
58
63
 
59
64
  ### Key Design Patterns
60
65
 
@@ -86,4 +91,4 @@ bin/rake standard # Alternative: Run via Rake task
86
91
 
87
92
  ### Updating Credential Patterns
88
93
  - Add patterns to `DEFAULT_CREDENTIAL_PATTERNS` in `lib/backspin.rb`
89
- - Test with appropriate fixtures in specs
94
+ - Test with appropriate fixtures in specs
data/CONTRIBUTING.md CHANGED
@@ -17,7 +17,7 @@ Note that Backspin is in early development and the API _will_ change before stab
17
17
 
18
18
  ## Getting Started
19
19
 
20
- Backspin is a Ruby gem for characterization testing of command-line interfaces. It records and replays CLI interactions by capturing stdout, stderr, and exit status from shell commands - similar to how VCR works for HTTP interactions.
20
+ Backspin is a Ruby gem for characterization testing of command-line interfaces. It records and verifies CLI interactions by capturing stdout, stderr, and exit status from shell commands, similar to how VCR works for HTTP interactions.
21
21
 
22
22
  ### Prerequisites
23
23
 
@@ -75,13 +75,17 @@ Backspin is a Ruby gem for characterization testing of command-line interfaces.
75
75
  - Credential scrubbing logic
76
76
  - Configuration management
77
77
 
78
- - **Command Class** (`lib/backspin/command.rb`)
79
- - Represents a single CLI execution
80
- - Stores: args, stdout, stderr, status, recorded_at, etc
78
+ - **Snapshot Class** (`lib/backspin/snapshot.rb`)
79
+ - Represents a single execution snapshot
80
+ - Stores: command type, args, env, stdout, stderr, status, recorded_at
81
+
82
+ - **BackspinResult Class** (`lib/backspin/backspin_result.rb`)
83
+ - Return object from `Backspin.run` / `Backspin.capture`
84
+ - Exposes `actual` and `expected` snapshots plus verify details
81
85
 
82
86
  - **Record Class** (`lib/backspin/record.rb`)
83
87
  - Manages YAML record files
84
- - Handles recording/playback sequencing
88
+ - Handles record/verify sequencing
85
89
 
86
90
  ### Common Development Tasks
87
91
 
@@ -134,7 +138,7 @@ RSpec.describe "Feature name" do
134
138
  it "does something specific" do
135
139
  result = Backspin.run(["echo", "hello"], name: "my_test_record")
136
140
 
137
- expect(result.stdout).to eq("hello\n")
141
+ expect(result.actual.stdout).to eq("hello\n")
138
142
  end
139
143
  end
140
144
  ```
@@ -212,4 +216,4 @@ If you have questions about contributing, feel free to:
212
216
  - Check existing issues and pull requests
213
217
  - Review the test suite for examples
214
218
 
215
- Thank you for contributing to Backspin!
219
+ Thank you for contributing to Backspin!
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- backspin (0.8.0)
4
+ backspin (0.9.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -134,7 +134,7 @@ For more matcher examples and detailed documentation, see [MATCHERS.md](MATCHERS
134
134
 
135
135
  ### Working with the Result Object
136
136
 
137
- The API returns a `RecordResult` object with helpful methods:
137
+ The API returns a `Backspin::BackspinResult` object with helpful methods:
138
138
 
139
139
  ```ruby
140
140
  result = Backspin.run(["sh", "-c", "echo out; echo err >&2; exit 42"], name: "my_test")
@@ -143,11 +143,12 @@ result = Backspin.run(["sh", "-c", "echo out; echo err >&2; exit 42"], name: "my
143
143
  result.recorded? # true on first run
144
144
  result.verified? # true/false on subsequent runs, nil when recording
145
145
 
146
- # Access output (first command)
147
- result.stdout # "out\n"
148
- result.stderr # "err\n"
149
- result.status # 42
150
- result.success? # false (non-zero exit)
146
+ # Access output snapshots
147
+ result.actual.stdout # "out\n"
148
+ result.actual.stderr # "err\n"
149
+ result.actual.status # 42
150
+ result.expected # nil in :record mode, populated in :verify mode
151
+ result.success? # false (non-zero exit)
151
152
  result.output # [stdout, stderr, status] for command runs
152
153
 
153
154
  # Debug information
@@ -0,0 +1,203 @@
1
+ # Backspin Result API Sketch
2
+
3
+ Date: 2026-02-11
4
+ Branch: `spike-backspin-result-api`
5
+
6
+ ## Goals
7
+
8
+ - Keep the public API small and predictable.
9
+ - Make runtime output and baseline output explicit.
10
+ - Remove multi-command semantics from the result object.
11
+ - Keep command run and block capture under one consistent return type.
12
+
13
+ ## Public API
14
+
15
+ ### Entry points
16
+
17
+ ```ruby
18
+ Backspin.run(command = nil, name:, env: nil, mode: :auto, matcher: nil, filter: nil, &block)
19
+ Backspin.capture(name, mode: :auto, matcher: nil, filter: nil, &block)
20
+ ```
21
+
22
+ Both return `BackspinResult`.
23
+
24
+ ### `BackspinResult`
25
+
26
+ Top-level aggregate with one responsibility: represent this run and its comparison.
27
+
28
+ ```ruby
29
+ class BackspinResult
30
+ attr_reader :mode, :record_path, :actual, :expected
31
+
32
+ def recorded?; end
33
+ def verified?; end
34
+ def diff; end
35
+ def error_message; end
36
+ def success?; end
37
+ def failure?; end
38
+ end
39
+ ```
40
+
41
+ Rules:
42
+
43
+ - `actual` is always present and represents what just ran.
44
+ - `expected` is baseline snapshot when one exists.
45
+ - In `:record` mode, `expected` is `nil`, `verified?` is `nil`.
46
+ - In `:verify` mode, `expected` is present, `verified?` is boolean.
47
+
48
+ ### `Snapshot`
49
+
50
+ Value object for one recorded/captured execution.
51
+
52
+ ```ruby
53
+ class Snapshot
54
+ attr_reader :command_type, :args, :env, :stdout, :stderr, :status, :recorded_at
55
+
56
+ def success?; end
57
+ def failure?; end
58
+ end
59
+ ```
60
+
61
+ Notes:
62
+
63
+ - `command_type` is `Open3::Capture3` for command runs.
64
+ - `command_type` is `Backspin::Capturer` for block capture.
65
+ - Capture status remains placeholder `0`.
66
+
67
+ ## Usage Examples
68
+
69
+ ### Command verify mismatch
70
+
71
+ ```ruby
72
+ result = Backspin.run(["echo", "changed"], name: "echo_case", mode: :verify)
73
+
74
+ result.actual.stdout # "changed\n"
75
+ result.expected.stdout # "original\n"
76
+ result.verified? # false
77
+ result.diff # unified-ish stdout/stderr/status diff
78
+ ```
79
+
80
+ ### First record
81
+
82
+ ```ruby
83
+ result = Backspin.run(["echo", "hello"], name: "hello_case")
84
+
85
+ result.mode # :record
86
+ result.actual.stdout # "hello\n"
87
+ result.expected # nil
88
+ result.verified? # nil
89
+ ```
90
+
91
+ ### Capture verify
92
+
93
+ ```ruby
94
+ result = Backspin.capture("capture_case", mode: :verify) do
95
+ puts "runtime output"
96
+ end
97
+
98
+ result.actual.stdout
99
+ result.expected.stdout
100
+ ```
101
+
102
+ ### Unix CLI examples
103
+
104
+ ```ruby
105
+ # 1) Record + verify a simple command
106
+ Backspin.run(["echo", "hello"], name: "echo_hello")
107
+ result = Backspin.run(["echo", "hello"], name: "echo_hello")
108
+ result.verified? # true
109
+ result.actual.stdout # "hello\n"
110
+ result.expected.stdout # "hello\n"
111
+
112
+ # 2) Verify mismatch with a common command
113
+ Backspin.run(["date", "+%Y-%m-%d"], name: "today", mode: :record)
114
+ result = Backspin.run(["date", "+%Y-%m-%d"], name: "today", mode: :verify)
115
+ result.verified? # true/false depending on day change
116
+
117
+ # 3) Capture a small shell pipeline output
118
+ result = Backspin.capture("grep_wc") do
119
+ system("printf 'alpha\\nbeta\\nalpha\\n' | grep alpha | wc -l")
120
+ end
121
+ result.actual.stdout
122
+
123
+ # 4) Verify a directory listing snapshot
124
+ Backspin.run(["ls", "-1"], name: "project_listing", mode: :record)
125
+ result = Backspin.run(["ls", "-1"], name: "project_listing", mode: :verify)
126
+ result.actual.stdout
127
+ result.expected.stdout
128
+ ```
129
+
130
+ ## Matcher and Filter Semantics
131
+
132
+ - `matcher:` applies only during verify and compares `expected` vs `actual`.
133
+ - `filter:` applies only when writing snapshots to disk.
134
+ - Default match still compares stdout/stderr/status only.
135
+
136
+ ## Error Semantics
137
+
138
+ - `Backspin::VerificationError` still raised by default when verification fails.
139
+ - Error message is generated from `BackspinResult#error_message`.
140
+ - Do not duplicate `diff` content in exception formatting.
141
+
142
+ ## Record Format Sketch (v4)
143
+
144
+ Single-snapshot format to match single-snapshot runtime model:
145
+
146
+ ```yaml
147
+ ---
148
+ format_version: "4.0"
149
+ recorded_at: "2026-02-11T00:00:00Z"
150
+ snapshot:
151
+ command_type: "Open3::Capture3"
152
+ args: ["echo", "hello"]
153
+ env:
154
+ MY_VAR: value
155
+ stdout: "hello\n"
156
+ stderr: ""
157
+ status: 0
158
+ ```
159
+
160
+ For capture snapshots:
161
+
162
+ ```yaml
163
+ snapshot:
164
+ command_type: "Backspin::Capturer"
165
+ args: ["<captured block>"]
166
+ stdout: "..."
167
+ stderr: "..."
168
+ status: 0
169
+ ```
170
+
171
+ ## Implemented Simplifications
172
+
173
+ - Unified all run/capture return values under `BackspinResult`.
174
+ - Introduced `Snapshot` as the shared value object for `actual` and `expected`.
175
+ - Removed multi-command result semantics from the public return API.
176
+ - Kept `CommandDiff`, now operating directly on snapshots.
177
+ - Simplified persistence to one snapshot per record file.
178
+
179
+ ## Current Status
180
+
181
+ Status date: 2026-02-11
182
+
183
+ 1. `Snapshot` and `BackspinResult` classes are implemented and wired into runtime paths.
184
+ 2. `Backspin.run` and `Backspin.capture` now return `BackspinResult`.
185
+ 3. `Record` persistence moved to v4 single-snapshot format (`snapshot` key, no `commands` array).
186
+ 4. `Matcher` and `CommandDiff` now operate on expected/actual snapshots.
187
+ 5. Legacy result/command layering was removed from `lib/`.
188
+ 6. Specs have been migrated to the new result contract and v4 format.
189
+ 7. Validation is green: `66 examples, 0 failures` and Standard lint passes.
190
+ 8. Public docs now use `result.actual` / `result.expected` terminology.
191
+
192
+ ## Success Criteria
193
+
194
+ 1. `Backspin.run` and `Backspin.capture` always return `BackspinResult` with `actual` populated.
195
+ 2. In `:record` mode, `result.expected` is `nil` and `result.verified?` is `nil`.
196
+ 3. In `:verify` mode, `result.expected` is present, `result.verified?` is boolean, and mismatch cases populate `result.diff` plus `result.error_message`.
197
+ 4. No multi-command result API remains in the public result contract.
198
+ 5. Snapshot object exposes a stable single-command shape: `stdout`, `stderr`, `status`, `args`, `env`, `command_type`.
199
+ 6. Record format uses one snapshot (v4), not a commands array.
200
+ 7. Existing strict verification behavior remains: default raises `Backspin::VerificationError`, while `raise_on_verification_failure = false` returns a failed result without raising.
201
+ 8. End-to-end Unix command examples are covered in specs: `echo` record/verify, `ls -1` record/verify, `date` mismatch behavior (or matcher override), and captured `grep | wc` pipeline output via `Backspin.capture`.
202
+ 9. Matcher behavior is preserved: default matching remains stdout/stderr/status, and custom `matcher:` contract (Proc, hash fields, `:all`) continues to work for both run and capture verification.
203
+ 10. Credential scrubbing behavior is preserved: stdout/stderr/args/env are scrubbed on persistence, capture output is scrubbed, custom patterns still apply, and verification diffs/error messages do not re-expose scrubbed secrets.
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Backspin
4
+ # Aggregate result returned by Backspin.run/capture.
5
+ class BackspinResult
6
+ attr_reader :mode, :record_path, :actual, :expected, :output
7
+
8
+ def initialize(mode:, record_path:, actual:, expected: nil, verified: nil, command_diff: nil, output: nil)
9
+ @mode = mode
10
+ @record_path = record_path
11
+ @actual = actual
12
+ @expected = expected
13
+ @verified = verified
14
+ @command_diff = command_diff
15
+ @output = output
16
+ end
17
+
18
+ def recorded?
19
+ mode == :record
20
+ end
21
+
22
+ # true/false for verify mode, nil for record mode
23
+ def verified?
24
+ return nil if mode == :record
25
+
26
+ @verified
27
+ end
28
+
29
+ def diff
30
+ return nil unless verified? == false
31
+
32
+ @command_diff&.diff
33
+ end
34
+
35
+ def error_message
36
+ return nil unless verified? == false
37
+ return "Output verification failed" unless @command_diff
38
+
39
+ msg = "Output verification failed:\n\n"
40
+ msg += @command_diff.summary
41
+ msg += "\n#{@command_diff.diff}" if @command_diff.diff
42
+ msg
43
+ end
44
+
45
+ def success?
46
+ actual&.success? || false
47
+ end
48
+
49
+ def failure?
50
+ !success?
51
+ end
52
+
53
+ def to_h
54
+ hash = {
55
+ mode: mode,
56
+ record_path: record_path,
57
+ actual: actual&.to_h
58
+ }
59
+
60
+ hash[:expected] = expected&.to_h
61
+ hash[:verified] = verified? unless verified?.nil?
62
+ hash[:diff] = diff if diff
63
+ hash
64
+ end
65
+ end
66
+ end
@@ -1,24 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Backspin
4
- # Represents the difference between a recorded command and actual execution
5
- # Handles verification and diff generation for a single command
4
+ # Represents the difference between expected and actual snapshots.
6
5
  class CommandDiff
7
- attr_reader :recorded_command, :actual_command, :matcher
6
+ attr_reader :expected, :actual, :matcher
8
7
 
9
- def initialize(recorded_command:, actual_command:, matcher: nil)
10
- @recorded_command = recorded_command
11
- @actual_command = actual_command
8
+ def initialize(expected:, actual:, matcher: nil)
9
+ @expected = expected
10
+ @actual = actual
12
11
  @matcher = Matcher.new(
13
12
  config: matcher,
14
- recorded_command: recorded_command,
15
- actual_command: actual_command
13
+ expected: expected,
14
+ actual: actual
16
15
  )
17
16
  end
18
17
 
19
- # @return [Boolean] true if the command output matches
18
+ # @return [Boolean] true if the snapshot output matches.
20
19
  def verified?
21
- return false unless method_classes_match?
20
+ return false unless command_types_match?
22
21
 
23
22
  @matcher.match?
24
23
  end
@@ -28,23 +27,23 @@ module Backspin
28
27
  return nil if verified?
29
28
 
30
29
  parts = []
31
- recorded_hash = recorded_command.to_h
32
- actual_hash = actual_command.to_h
30
+ expected_hash = expected.to_h
31
+ actual_hash = actual.to_h
33
32
 
34
- unless method_classes_match?
35
- parts << "Command type mismatch: expected #{recorded_command.method_class.name}, got #{actual_command.method_class.name}"
33
+ unless command_types_match?
34
+ parts << "Command type mismatch: expected #{expected.command_type.name}, got #{actual.command_type.name}"
36
35
  end
37
36
 
38
- if recorded_hash["stdout"] != actual_hash["stdout"]
39
- parts << stdout_diff(recorded_hash["stdout"], actual_hash["stdout"])
37
+ if expected_hash["stdout"] != actual_hash["stdout"]
38
+ parts << stdout_diff(expected_hash["stdout"], actual_hash["stdout"])
40
39
  end
41
40
 
42
- if recorded_hash["stderr"] != actual_hash["stderr"]
43
- parts << stderr_diff(recorded_hash["stderr"], actual_hash["stderr"])
41
+ if expected_hash["stderr"] != actual_hash["stderr"]
42
+ parts << stderr_diff(expected_hash["stderr"], actual_hash["stderr"])
44
43
  end
45
44
 
46
- if recorded_hash["status"] != actual_hash["status"]
47
- parts << "Exit status: expected #{recorded_hash["status"]}, got #{actual_hash["status"]}"
45
+ if expected_hash["status"] != actual_hash["status"]
46
+ parts << "Exit status: expected #{expected_hash["status"]}, got #{actual_hash["status"]}"
48
47
  end
49
48
 
50
49
  parts.join("\n\n")
@@ -61,12 +60,12 @@ module Backspin
61
60
 
62
61
  private
63
62
 
64
- def method_classes_match?
65
- recorded_command.method_class == actual_command.method_class
63
+ def command_types_match?
64
+ expected.command_type == actual.command_type
66
65
  end
67
66
 
68
67
  def failure_reason
69
- unless method_classes_match?
68
+ unless command_types_match?
70
69
  return "command type mismatch"
71
70
  end
72
71
 
@@ -1,23 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Backspin
4
- # Handles matching logic between recorded and actual commands
4
+ # Handles matching logic between expected and actual snapshots.
5
5
  class Matcher
6
- attr_reader :config, :recorded_command, :actual_command
6
+ attr_reader :config, :expected, :actual
7
7
 
8
- def initialize(config:, recorded_command:, actual_command:)
8
+ def initialize(config:, expected:, actual:)
9
9
  @config = normalize_config(config)
10
- @recorded_command = recorded_command
11
- @actual_command = actual_command
10
+ @expected = expected
11
+ @actual = actual
12
12
  end
13
13
 
14
- # @return [Boolean] true if commands match according to the configured matcher
14
+ # @return [Boolean] true if snapshots match according to configured matcher
15
15
  def match?
16
16
  if config.nil?
17
- # Default behavior: check all fields for equality
18
- default_matcher.call(recorded_command.to_h, actual_command.to_h)
17
+ default_matcher.call(expected.to_h, actual.to_h)
19
18
  elsif config.is_a?(Proc)
20
- config.call(recorded_command.to_h, actual_command.to_h)
19
+ config.call(expected.to_h, actual.to_h)
21
20
  elsif config.is_a?(Hash)
22
21
  verify_with_hash_matcher
23
22
  else
@@ -30,24 +29,22 @@ module Backspin
30
29
  reasons = []
31
30
 
32
31
  if config.nil?
33
- # Default matcher checks all fields
34
- recorded_hash = recorded_command.to_h
35
- actual_hash = actual_command.to_h
32
+ expected_hash = expected.to_h
33
+ actual_hash = actual.to_h
36
34
 
37
- reasons << "stdout differs" if recorded_hash["stdout"] != actual_hash["stdout"]
38
- reasons << "stderr differs" if recorded_hash["stderr"] != actual_hash["stderr"]
39
- reasons << "exit status differs" if recorded_hash["status"] != actual_hash["status"]
35
+ reasons << "stdout differs" if expected_hash["stdout"] != actual_hash["stdout"]
36
+ reasons << "stderr differs" if expected_hash["stderr"] != actual_hash["stderr"]
37
+ reasons << "exit status differs" if expected_hash["status"] != actual_hash["status"]
40
38
  elsif config.is_a?(Hash)
41
- recorded_hash = recorded_command.to_h
42
- actual_hash = actual_command.to_h
39
+ expected_hash = expected.to_h
40
+ actual_hash = actual.to_h
43
41
 
44
- # Only check matchers that were provided
45
42
  config.each do |field, matcher_proc|
46
43
  case field
47
44
  when :all
48
- reasons << ":all matcher failed" unless matcher_proc.call(recorded_hash, actual_hash)
45
+ reasons << ":all matcher failed" unless matcher_proc.call(expected_hash, actual_hash)
49
46
  when :stdout, :stderr, :status
50
- unless matcher_proc.call(recorded_hash[field.to_s], actual_hash[field.to_s])
47
+ unless matcher_proc.call(expected_hash[field.to_s], actual_hash[field.to_s])
51
48
  reasons << "#{field} custom matcher failed"
52
49
  end
53
50
  end
@@ -79,19 +76,16 @@ module Backspin
79
76
  end
80
77
 
81
78
  def verify_with_hash_matcher
82
- recorded_hash = recorded_command.to_h
83
- actual_hash = actual_command.to_h
79
+ expected_hash = expected.to_h
80
+ actual_hash = actual.to_h
84
81
 
85
- # Override-based: only run matchers that are explicitly provided
86
- # Use map to ensure all matchers run, then check if all passed
87
82
  results = config.map do |field, matcher_proc|
88
83
  case field
89
84
  when :all
90
- matcher_proc.call(recorded_hash, actual_hash)
85
+ matcher_proc.call(expected_hash, actual_hash)
91
86
  when :stdout, :stderr, :status
92
- matcher_proc.call(recorded_hash[field.to_s], actual_hash[field.to_s])
87
+ matcher_proc.call(expected_hash[field.to_s], actual_hash[field.to_s])
93
88
  else
94
- # This should never happen due to normalize_config validation
95
89
  raise ArgumentError, "Unknown field: #{field}"
96
90
  end
97
91
  end