backspin 0.4.0 → 0.4.2
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/.gem_release.yml +13 -0
- data/CHANGELOG.md +5 -7
- data/CLAUDE.md +7 -3
- data/CONTRIBUTING.md +2 -2
- data/Gemfile +3 -1
- data/Gemfile.lock +3 -1
- data/MATCH_ON_USAGE.md +110 -0
- data/README.md +7 -2
- data/Rakefile +5 -1
- data/backspin.gemspec +6 -3
- data/examples/match_on_example.rb +116 -0
- data/fixtures/backspin/all_and_fields.yml +15 -0
- data/fixtures/backspin/all_bypass_equality.yml +14 -0
- data/fixtures/backspin/all_checks_equality.yml +17 -0
- data/fixtures/backspin/all_for_logging.yml +13 -0
- data/fixtures/backspin/all_matcher_basic.yml +14 -0
- data/fixtures/backspin/all_matcher_custom.yml +17 -0
- data/fixtures/backspin/all_matcher_demo.yml +14 -0
- data/fixtures/backspin/all_matcher_test.yml +14 -0
- data/fixtures/backspin/all_mode_filter.yml +14 -0
- data/fixtures/backspin/all_no_short_circuit.yml +14 -0
- data/fixtures/backspin/all_pass_field_fail.yml +14 -0
- data/fixtures/backspin/all_short_circuit.yml +14 -0
- data/fixtures/backspin/all_skips_equality.yml +17 -0
- data/fixtures/backspin/all_with_equality.yml +17 -0
- data/fixtures/backspin/all_with_fields.yml +17 -0
- data/fixtures/backspin/combined_fail_demo.yml +14 -0
- data/fixtures/backspin/combined_matcher_demo.yml +14 -0
- data/fixtures/backspin/credential_filter.yml +18 -0
- data/fixtures/backspin/echo_hello.yml +14 -0
- data/fixtures/backspin/echo_verify.yml +14 -0
- data/fixtures/backspin/episodes_filter.yml +26 -0
- data/fixtures/backspin/failure_test.yml +14 -0
- data/fixtures/backspin/field_matcher_demo.yml +17 -0
- data/fixtures/backspin/field_matcher_values.yml +14 -0
- data/fixtures/backspin/full_data_filter.yml +17 -0
- data/fixtures/backspin/key_confusion_test.yml +14 -0
- data/fixtures/backspin/match_on_any_fail.yml +21 -0
- data/fixtures/backspin/match_on_bad_format.yml +14 -0
- data/fixtures/backspin/match_on_fail.yml +15 -0
- data/fixtures/backspin/match_on_invalid.yml +14 -0
- data/fixtures/backspin/match_on_multiple.yml +28 -0
- data/fixtures/backspin/match_on_nil.yml +14 -0
- data/fixtures/backspin/match_on_other_fields.yml +23 -0
- data/fixtures/backspin/match_on_run_bang.yml +16 -0
- data/fixtures/backspin/match_on_run_bang_fail.yml +15 -0
- data/fixtures/backspin/match_on_single.yml +17 -0
- data/fixtures/backspin/mixed_calls.yml +24 -0
- data/fixtures/backspin/multi_command.yml +34 -0
- data/fixtures/backspin/multi_command_filter.yml +26 -0
- data/fixtures/backspin/multi_field_filter.yml +13 -0
- data/fixtures/backspin/multi_system.yml +20 -0
- data/fixtures/backspin/nil_filter.yml +14 -0
- data/fixtures/backspin/none_mode_test.yml +14 -0
- data/fixtures/backspin/path_test.yml +17 -0
- data/fixtures/backspin/playback_system.yml +12 -0
- data/fixtures/backspin/playback_test.yml +14 -0
- data/fixtures/backspin/stderr_test.yml +19 -0
- data/fixtures/backspin/string_symbol_test.yml +14 -0
- data/fixtures/backspin/system_echo.yml +12 -0
- data/fixtures/backspin/system_false.yml +18 -0
- data/fixtures/backspin/timestamp_test.yml +18 -0
- data/fixtures/backspin/use_record_filter.yml +15 -0
- data/fixtures/backspin/verify_system.yml +12 -0
- data/fixtures/backspin/verify_system_diff.yml +11 -0
- data/fixtures/backspin/version_test.yml +14 -0
- data/lib/backspin/command.rb +1 -5
- data/lib/backspin/command_diff.rb +98 -16
- data/lib/backspin/command_result.rb +2 -4
- data/lib/backspin/record.rb +31 -10
- data/lib/backspin/record_result.rb +20 -14
- data/lib/backspin/recorder.rb +100 -55
- data/lib/backspin/version.rb +3 -1
- data/lib/backspin.rb +36 -175
- data/release.rake +97 -0
- data/script/lint +6 -0
- metadata +79 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 055d320685da2ca9e280c3aa8bcb15edb101d788c4d8c4df8ff203d4c2c34dac
|
4
|
+
data.tar.gz: cf21c90a588ff5590844ae2a9c50a78707a5c71d44b43ad0d3a571fed0bbe4b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c3a0941042e885d5370c4287fdd88323bc1b95d15a7c795afde0d08aa5efe050f7e54838a5774de967d4d633b9e30df82b5700d17c431fa8ea1cce79777bee9
|
7
|
+
data.tar.gz: 86584a95060fda843934065c2d9a3ca59acd12a1e21f70dcf3d9bd648f4cd1cec97ff21a57f98f8aa7a133865ccbb38438bdb05402f19e8292bed02c56d51bfe
|
data/.gem_release.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## 0.4.0 - 2025-06-06
|
4
4
|
|
5
5
|
Simpler, unified API: `Backspin.run` and `Backspin run!` methods that automatically record on first use and verify on subsequent runs. `run!` will raise an error if results differ, whereas `run` will return the result for the caller to decide what to do with
|
6
6
|
|
7
|
-
##
|
7
|
+
## 0.3.0 - 2025-06-05
|
8
8
|
- Scrub credentials from command arguments
|
9
9
|
|
10
|
-
##
|
10
|
+
## 0.2.0 - 2025-06-05
|
11
11
|
- First public release of Backspin, extracteed from `name-TBD` CLI tool
|
12
12
|
|
13
|
-
##
|
13
|
+
## 0.2.1 - 2025-06-04
|
14
14
|
- major refactoring, add support for `system` calls
|
15
15
|
|
16
|
-
##
|
17
|
-
|
18
|
-
### Added
|
16
|
+
## 0.1.0 - 2025-06-02
|
19
17
|
- Initial (internal) release of Backspin
|
20
18
|
- `record` method to capture CLI command outputs
|
21
19
|
- `verify` and `verify!` methods for output verification
|
data/CLAUDE.md
CHANGED
@@ -29,9 +29,13 @@ bin/rake release # Release to RubyGems (updates version, tags, pushes)
|
|
29
29
|
|
30
30
|
### Code Quality
|
31
31
|
```bash
|
32
|
-
|
32
|
+
script/lint # Run Standard Ruby linter
|
33
|
+
script/lint --fix # Auto-fix linting issues
|
34
|
+
bin/rake standard # Alternative: Run via Rake task
|
33
35
|
```
|
34
36
|
|
37
|
+
**Important**: Always use `standardrb` for linting, never use `rubocop` directly. The project uses Standard Ruby for consistent code style.
|
38
|
+
|
35
39
|
## Architecture
|
36
40
|
|
37
41
|
### Core Components
|
@@ -62,7 +66,7 @@ bin/rake standard # Run Standard Ruby linter
|
|
62
66
|
### Testing Approach
|
63
67
|
|
64
68
|
- Integration-focused tests that exercise the full stack
|
65
|
-
- Default record directory is `
|
69
|
+
- Default record directory is `fixtures/backspin` (can be configured)
|
66
70
|
- Tests use real shell commands (`echo`, `date`, etc.)
|
67
71
|
- Configuration is reset between tests to avoid side effects
|
68
72
|
- **Important**: Backspin specs MUST be as local and un-DRY as possible. Each spec should be self-contained with its own setup, expectations, and cleanup if needed. Avoid shared contexts or helpers that hide important test details.
|
@@ -76,7 +80,7 @@ bin/rake standard # Run Standard Ruby linter
|
|
76
80
|
4. Run tests with `rake spec`
|
77
81
|
|
78
82
|
### Debugging Tests
|
79
|
-
- Records are saved to `
|
83
|
+
- Records are saved to `fixtures/backspin/` by default
|
80
84
|
- Check YAML files to see recorded command outputs
|
81
85
|
|
82
86
|
### Updating Credential Patterns
|
data/CONTRIBUTING.md
CHANGED
@@ -99,7 +99,7 @@ Backspin is a Ruby gem for characterization testing of command-line interfaces.
|
|
99
99
|
|
100
100
|
#### Debugging Tests
|
101
101
|
|
102
|
-
- Records are saved to `
|
102
|
+
- Records are saved to `fixtures/backspin/` by default
|
103
103
|
- Check YAML files to see recorded command outputs
|
104
104
|
|
105
105
|
## Testing
|
@@ -125,7 +125,7 @@ Backspin uses integration-focused tests that exercise the full stack. When writi
|
|
125
125
|
- Avoid shared contexts or helpers that hide important test details
|
126
126
|
- Use real shell commands (`echo`, `date`, etc.) for testing
|
127
127
|
- Ensure configuration is reset between tests to avoid side effects
|
128
|
-
- Verify new or updated test records in `
|
128
|
+
- Verify new or updated test records in `fixtures/backspin/`
|
129
129
|
|
130
130
|
Example test structure:
|
131
131
|
|
data/Gemfile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source "https://rubygems.org"
|
2
4
|
|
3
5
|
# Specify your gem's dependencies in backspin.gemspec
|
@@ -6,6 +8,6 @@ gemspec
|
|
6
8
|
group :development do
|
7
9
|
gem "rake", "~> 13.0"
|
8
10
|
gem "rspec", "~> 3.0"
|
9
|
-
gem "timecop", "~> 0.9"
|
10
11
|
gem "standard"
|
12
|
+
gem "timecop", "~> 0.9"
|
11
13
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
backspin (0.4.
|
4
|
+
backspin (0.4.2)
|
5
5
|
ostruct (~> 0.5.0)
|
6
6
|
rspec-mocks (~> 3.0)
|
7
7
|
|
@@ -10,6 +10,7 @@ GEM
|
|
10
10
|
specs:
|
11
11
|
ast (2.4.3)
|
12
12
|
diff-lcs (1.6.2)
|
13
|
+
gem-release (2.2.4)
|
13
14
|
json (2.12.2)
|
14
15
|
language_server-protocol (3.17.0.5)
|
15
16
|
lint_roller (1.1.0)
|
@@ -78,6 +79,7 @@ PLATFORMS
|
|
78
79
|
|
79
80
|
DEPENDENCIES
|
80
81
|
backspin!
|
82
|
+
gem-release (~> 2.2)
|
81
83
|
rake (~> 13.0)
|
82
84
|
rspec (~> 3.0)
|
83
85
|
standard
|
data/MATCH_ON_USAGE.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Using match_on for Field-Specific Verification
|
2
|
+
|
3
|
+
The `match_on` option allows you to use custom matchers for specific fields while maintaining exact equality checks for all other fields. This is useful when dealing with dynamic content like timestamps, process IDs, or version numbers.
|
4
|
+
|
5
|
+
## Basic Usage
|
6
|
+
|
7
|
+
### Single Field Matcher
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Record a command with a timestamp
|
11
|
+
Backspin.run("timestamp_test") do
|
12
|
+
Open3.capture3("date")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Verify with a custom matcher for stdout
|
16
|
+
result = Backspin.run("timestamp_test",
|
17
|
+
match_on: [:stdout, ->(recorded, actual) {
|
18
|
+
# Both should contain a day of the week
|
19
|
+
recorded.match?(/Mon|Tue|Wed|Thu|Fri|Sat|Sun/) &&
|
20
|
+
actual.match?(/Mon|Tue|Wed|Thu|Fri|Sat|Sun/)
|
21
|
+
}]) do
|
22
|
+
Open3.capture3("date")
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
### Multiple Field Matchers
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# Match different fields with different rules
|
30
|
+
result = Backspin.run("multi_field_test",
|
31
|
+
match_on: [
|
32
|
+
[:stdout, ->(recorded, actual) {
|
33
|
+
# Match process ID format
|
34
|
+
recorded.match?(/PID: \d+/) && actual.match?(/PID: \d+/)
|
35
|
+
}],
|
36
|
+
[:stderr, ->(recorded, actual) {
|
37
|
+
# Match error type, ignore details
|
38
|
+
recorded.include?("Error:") && actual.include?("Error:")
|
39
|
+
}]
|
40
|
+
]) do
|
41
|
+
Open3.capture3("./my_script.sh")
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Matcher Format
|
46
|
+
|
47
|
+
The `match_on` option accepts two formats:
|
48
|
+
|
49
|
+
1. **Single field**: `[:field_name, matcher_proc]`
|
50
|
+
2. **Multiple fields**: `[[:field1, matcher1], [:field2, matcher2], ...]`
|
51
|
+
|
52
|
+
Valid field names are:
|
53
|
+
- `:stdout` - Standard output
|
54
|
+
- `:stderr` - Standard error
|
55
|
+
- `:status` - Exit status code
|
56
|
+
|
57
|
+
## Matcher Proc
|
58
|
+
|
59
|
+
The matcher proc receives two arguments:
|
60
|
+
- `recorded_value` - The value from the saved recording
|
61
|
+
- `actual_value` - The value from the current execution
|
62
|
+
|
63
|
+
It should return `true` if the values match according to your criteria, `false` otherwise.
|
64
|
+
|
65
|
+
## Examples
|
66
|
+
|
67
|
+
### Matching Version Numbers
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# Match major version only
|
71
|
+
match_on: [:stdout, ->(recorded, actual) {
|
72
|
+
recorded.match(/Version: (\d+)\./) &&
|
73
|
+
actual.match(/Version: (\d+)\./) &&
|
74
|
+
$1 == $1 # Major versions match
|
75
|
+
}]
|
76
|
+
```
|
77
|
+
|
78
|
+
### Ignoring Timestamps
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# Match log format but ignore timestamp
|
82
|
+
match_on: [:stdout, ->(recorded, actual) {
|
83
|
+
# Remove timestamps before comparing
|
84
|
+
recorded.gsub(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/, '') ==
|
85
|
+
actual.gsub(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/, '')
|
86
|
+
}]
|
87
|
+
```
|
88
|
+
|
89
|
+
### Handling Dynamic IDs
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# Match API response structure, ignore dynamic IDs
|
93
|
+
match_on: [:stdout, ->(recorded, actual) {
|
94
|
+
recorded_json = JSON.parse(recorded)
|
95
|
+
actual_json = JSON.parse(actual)
|
96
|
+
|
97
|
+
# Compare structure, not values
|
98
|
+
recorded_json.keys.sort == actual_json.keys.sort
|
99
|
+
}]
|
100
|
+
```
|
101
|
+
|
102
|
+
## Important Notes
|
103
|
+
|
104
|
+
1. **Other fields must match exactly**: When using `match_on`, all fields not specified in the matcher list must match exactly. If stdout has a custom matcher but stderr doesn't, stderr must be identical to pass verification.
|
105
|
+
|
106
|
+
2. **Precedence**: If both `matcher` and `match_on` options are provided, `matcher` takes precedence (for backward compatibility).
|
107
|
+
|
108
|
+
3. **Error messages**: When verification fails with `match_on`, the error will indicate which fields failed and whether they failed exact matching or custom matching.
|
109
|
+
|
110
|
+
4. **Works with run!**: The `match_on` option works with both `run` and `run!` methods.
|
data/README.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
# Backspin
|
1
|
+
# Backspin
|
2
|
+
|
3
|
+
[](https://www.ruby-lang.org/)
|
4
|
+
[](https://rubygems.org/gems/backspin)
|
5
|
+
[](https://circleci.com/gh/rsanheim/backspin)
|
6
|
+
[](https://github.com/rsanheim/backspin/commits/main)
|
2
7
|
|
3
8
|
Backspin records and replays CLI interactions in Ruby for easy snapshot testing of command-line interfaces. Currently supports `Open3.capture3` and `system` and requires `rspec-mocks`. More system calls and test integrations are welcome - send a PR!
|
4
9
|
|
@@ -62,7 +67,7 @@ end
|
|
62
67
|
result = Backspin.run("echo_test", mode: :record) do
|
63
68
|
Open3.capture3("echo hello")
|
64
69
|
end
|
65
|
-
# This will save the output to `
|
70
|
+
# This will save the output to `fixtures/backspin/echo_test.yml`.
|
66
71
|
|
67
72
|
# Explicit verify mode: Always verify against existing recording
|
68
73
|
result = Backspin.run("echo_test", mode: :verify) do
|
data/Rakefile
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
4
|
require "rspec/core/rake_task"
|
3
5
|
require "standard/rake"
|
4
6
|
|
5
7
|
RSpec::Core::RakeTask.new(:spec)
|
6
8
|
|
7
|
-
task default: [
|
9
|
+
task default: %i[spec standard]
|
10
|
+
|
11
|
+
load "release.rake" if File.exist?("release.rake")
|
data/backspin.gemspec
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "lib/backspin/version"
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
@@ -16,14 +18,15 @@ Gem::Specification.new do |spec|
|
|
16
18
|
spec.metadata["source_code_uri"] = spec.homepage
|
17
19
|
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
18
20
|
|
19
|
-
|
20
|
-
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
21
22
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
23
|
end
|
23
24
|
spec.bindir = "exe"
|
24
25
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
25
26
|
spec.require_paths = ["lib"]
|
26
27
|
|
27
|
-
spec.add_dependency "rspec-mocks", "~> 3.0"
|
28
28
|
spec.add_dependency "ostruct", "~> 0.5.0"
|
29
|
+
spec.add_dependency "rspec-mocks", "~> 3.0"
|
30
|
+
|
31
|
+
spec.add_development_dependency "gem-release", "~> 2.2"
|
29
32
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/inline"
|
5
|
+
require "open3"
|
6
|
+
|
7
|
+
gemfile do
|
8
|
+
source "https://rubygems.org"
|
9
|
+
gem "backspin", path: ".."
|
10
|
+
end
|
11
|
+
|
12
|
+
# Example 1: Single field matcher
|
13
|
+
puts "Example 1: Matching timestamps with custom matcher"
|
14
|
+
puts "-" * 50
|
15
|
+
|
16
|
+
# First run: Record the output
|
17
|
+
result = Backspin.run("timestamp_example") do
|
18
|
+
Open3.capture3("date '+%Y-%m-%d %H:%M:%S'")
|
19
|
+
end
|
20
|
+
puts "Recorded: #{result.stdout.chomp}"
|
21
|
+
|
22
|
+
# Sleep to ensure different timestamp
|
23
|
+
sleep 1
|
24
|
+
|
25
|
+
# Second run: Verify with custom matcher
|
26
|
+
result = Backspin.run("timestamp_example",
|
27
|
+
match_on: [:stdout, lambda { |recorded, actual|
|
28
|
+
# Both should have the same date format
|
29
|
+
recorded.match?(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/) &&
|
30
|
+
actual.match?(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)
|
31
|
+
}]) do
|
32
|
+
Open3.capture3("date '+%Y-%m-%d %H:%M:%S'")
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "Current: #{result.stdout.chomp}"
|
36
|
+
puts "Verified: #{result.verified?}"
|
37
|
+
puts
|
38
|
+
|
39
|
+
# Example 2: Multiple field matchers
|
40
|
+
puts "Example 2: Matching multiple fields with different patterns"
|
41
|
+
puts "-" * 50
|
42
|
+
|
43
|
+
# Record a command with dynamic content
|
44
|
+
Backspin.run("multi_field_example") do
|
45
|
+
script = <<~BASH
|
46
|
+
echo "PID: $$"
|
47
|
+
echo "Error: Timeout at $(date '+%H:%M:%S')" >&2
|
48
|
+
exit 1
|
49
|
+
BASH
|
50
|
+
Open3.capture3("bash", "-c", script)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Verify with different PID and timestamp
|
54
|
+
result = Backspin.run("multi_field_example",
|
55
|
+
match_on: [
|
56
|
+
[:stdout, lambda { |recorded, actual|
|
57
|
+
# Both should have PID format
|
58
|
+
recorded.match?(/PID: \d+/) && actual.match?(/PID: \d+/)
|
59
|
+
}],
|
60
|
+
[:stderr, lambda { |recorded, actual|
|
61
|
+
# Both should have timeout error, ignore timestamp
|
62
|
+
recorded.match?(/Error: Timeout at/) && actual.match?(/Error: Timeout at/)
|
63
|
+
}]
|
64
|
+
]) do
|
65
|
+
script = <<~BASH
|
66
|
+
echo "PID: $$"
|
67
|
+
echo "Error: Timeout at $(date '+%H:%M:%S')" >&2
|
68
|
+
exit 1
|
69
|
+
BASH
|
70
|
+
Open3.capture3("bash", "-c", script)
|
71
|
+
end
|
72
|
+
|
73
|
+
puts "Stdout: #{result.stdout.chomp}"
|
74
|
+
puts "Stderr: #{result.stderr.chomp}"
|
75
|
+
puts "Status: #{result.status}"
|
76
|
+
puts "Verified: #{result.verified?}"
|
77
|
+
puts
|
78
|
+
|
79
|
+
# Example 3: Mixed matching - some fields exact, some custom
|
80
|
+
puts "Example 3: Mixed field matching"
|
81
|
+
puts "-" * 50
|
82
|
+
|
83
|
+
# Record with specific values
|
84
|
+
Backspin.run("mixed_matching") do
|
85
|
+
script = <<~BASH
|
86
|
+
echo "Version: 1.2.3"
|
87
|
+
echo "Build: $(date +%s)"
|
88
|
+
echo "Status: OK"
|
89
|
+
BASH
|
90
|
+
Open3.capture3("bash", "-c", script)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Verify - stdout uses custom matcher, stderr must match exactly
|
94
|
+
result = Backspin.run("mixed_matching",
|
95
|
+
match_on: [:stdout, lambda { |recorded, actual|
|
96
|
+
# Version and Status must match, Build can differ
|
97
|
+
recorded_lines = recorded.lines
|
98
|
+
actual_lines = actual.lines
|
99
|
+
|
100
|
+
recorded_lines[0] == actual_lines[0] && # Version line must match
|
101
|
+
recorded_lines[1].start_with?("Build:") && actual_lines[1].start_with?("Build:") && # Build line format
|
102
|
+
recorded_lines[2] == actual_lines[2] # Status line must match
|
103
|
+
}]) do
|
104
|
+
script = <<~BASH
|
105
|
+
echo "Version: 1.2.3"
|
106
|
+
echo "Build: $(date +%s)"
|
107
|
+
echo "Status: OK"
|
108
|
+
BASH
|
109
|
+
Open3.capture3("bash", "-c", script)
|
110
|
+
end
|
111
|
+
|
112
|
+
puts "Output:\n#{result.stdout}"
|
113
|
+
puts "Verified: #{result.verified?}"
|
114
|
+
|
115
|
+
# Cleanup
|
116
|
+
FileUtils.rm_rf("fixtures/backspin")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-06-10T11:27:40-05:00'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- sh
|
8
|
+
- "-c"
|
9
|
+
- echo 'valid'; exit 0
|
10
|
+
stdout: 'valid
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: ''
|
14
|
+
status: 0
|
15
|
+
recorded_at: '2025-06-10T11:27:40-05:00'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-06-10T11:02:03-05:00'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- echo
|
8
|
+
- first output
|
9
|
+
stdout: 'first output
|
10
|
+
|
11
|
+
'
|
12
|
+
stderr: ''
|
13
|
+
status: 0
|
14
|
+
recorded_at: '2025-06-10T11:02:03-05:00'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-05-01T12:00:00Z'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- sh
|
8
|
+
- "-c"
|
9
|
+
- echo 'original'; echo 'original error' >&2
|
10
|
+
stdout: 'original
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: 'original error
|
14
|
+
|
15
|
+
'
|
16
|
+
status: 0
|
17
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-05-01T12:00:00Z'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- sh
|
8
|
+
- "-c"
|
9
|
+
- 'echo ''PASS: test 1''; echo ''WARNING: minor issue'' >&2'
|
10
|
+
stdout: 'PASS: test 1
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: 'WARNING: minor issue
|
14
|
+
|
15
|
+
'
|
16
|
+
status: 0
|
17
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-05-01T12:00:00Z'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- sh
|
8
|
+
- "-c"
|
9
|
+
- echo 'original'; echo 'original error' >&2
|
10
|
+
stdout: 'original
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: 'original error
|
14
|
+
|
15
|
+
'
|
16
|
+
status: 0
|
17
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-06-10T11:27:40-05:00'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- sh
|
8
|
+
- "-c"
|
9
|
+
- echo 'output1'; echo 'error1' >&2
|
10
|
+
stdout: 'output1
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: 'error1
|
14
|
+
|
15
|
+
'
|
16
|
+
status: 0
|
17
|
+
recorded_at: '2025-06-10T11:27:40-05:00'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-05-01T12:00:00Z'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- sh
|
8
|
+
- "-c"
|
9
|
+
- echo 'output'; echo 'error' >&2
|
10
|
+
stdout: 'output
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: 'error
|
14
|
+
|
15
|
+
'
|
16
|
+
status: 0
|
17
|
+
recorded_at: '2025-05-01T12:00:00Z'
|