backspin 0.2.1 → 0.4.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/.circleci/config.yml +31 -0
- data/.gitignore +1 -4
- data/CHANGELOG.md +6 -0
- data/CLAUDE.md +4 -5
- data/CONTRIBUTING.md +222 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +87 -0
- data/README.md +108 -34
- data/backspin.gemspec +4 -4
- data/bin/rake +27 -0
- data/bin/rspec +27 -0
- data/bin/setup +1 -3
- data/lib/backspin/command.rb +49 -12
- data/lib/backspin/command_diff.rb +88 -0
- data/lib/backspin/command_result.rb +60 -0
- data/lib/backspin/record.rb +2 -2
- data/lib/backspin/record_result.rb +153 -0
- data/lib/backspin/recorder.rb +4 -23
- data/lib/backspin/version.rb +1 -1
- data/lib/backspin.rb +170 -285
- metadata +17 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59bc6e711951434fc84dd2c2694f69aa5b00ef3bf878b81c5b4e97beeb430ad6
|
4
|
+
data.tar.gz: f9784962ef86ad73e0eb1c8388217700e477bc60ce0528b5dd16f08bc5c2f637
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4151f4a28909e1a80706a30520b268f5114124230df9869a3603d5502194f4885ca8dff19e15a9a8ffe5f559b14695614f9f113c66e82ccc105de5b7ae17006
|
7
|
+
data.tar.gz: a66f71d1a4b2e9404ca39178920d8b88686f5e8cec2fe62f282b1cda9de031c1041cdfa1007ee9dc725c24cfb1c4dad5445213091a20c0faf23165705a8d264e
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# See: https://circleci.com/docs/configuration-reference
|
2
|
+
version: 2.1
|
3
|
+
|
4
|
+
orbs:
|
5
|
+
# See: https://circleci.com/developer/orbs/orb/circleci/ruby
|
6
|
+
ruby: circleci/ruby@2.5.3
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
build:
|
10
|
+
resource_class: medium
|
11
|
+
parameters:
|
12
|
+
ruby-version:
|
13
|
+
type: string
|
14
|
+
docker:
|
15
|
+
- image: cimg/ruby:<< parameters.ruby-version >>
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- checkout
|
19
|
+
- ruby/install-deps:
|
20
|
+
key: gems-v1-ruby<< parameters.ruby-version >>
|
21
|
+
- run:
|
22
|
+
name: Run specs and lint
|
23
|
+
command: bundle exec rake
|
24
|
+
|
25
|
+
workflows:
|
26
|
+
build:
|
27
|
+
jobs:
|
28
|
+
- build:
|
29
|
+
matrix:
|
30
|
+
parameters:
|
31
|
+
ruby-version: ["3.2", "3.3", "3.4"]
|
data/.gitignore
CHANGED
@@ -2,18 +2,15 @@
|
|
2
2
|
/.yardoc
|
3
3
|
/_yardoc/
|
4
4
|
/coverage/
|
5
|
-
/doc/
|
6
5
|
/pkg/
|
7
6
|
/spec/reports/
|
8
7
|
/tmp/
|
9
8
|
/.claude
|
10
9
|
/.cursor
|
11
10
|
|
11
|
+
/backspin*.gem
|
12
12
|
# rspec failure tracking
|
13
13
|
.rspec_status
|
14
14
|
|
15
15
|
# Backspin cassettes
|
16
16
|
/tmp/backspin/
|
17
|
-
|
18
|
-
# Bundler
|
19
|
-
Gemfile.lock
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [0.4.0] - 2025-06-06
|
4
|
+
|
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
|
+
|
7
|
+
## [0.3.0] - 2025-06-05
|
8
|
+
- Scrub credentials from command arguments
|
3
9
|
|
4
10
|
## [0.2.0] - 2025-06-05
|
5
11
|
- First public release of Backspin, extracteed from `name-TBD` CLI tool
|
data/CLAUDE.md
CHANGED
@@ -17,14 +17,14 @@ bin/setup
|
|
17
17
|
### Testing
|
18
18
|
```bash
|
19
19
|
bin/rake spec # Run all tests
|
20
|
-
rspec spec/[file] # Run specific test file
|
21
|
-
rspec spec/[file]:[line] # Run specific test
|
20
|
+
bin/rspec spec/[file] # Run specific test file
|
21
|
+
bin/rspec spec/[file]:[line] # Run specific test
|
22
22
|
```
|
23
23
|
|
24
24
|
### Building and Releasing
|
25
25
|
```bash
|
26
|
-
|
27
|
-
|
26
|
+
bin/rake install # Install gem locally for testing
|
27
|
+
bin/rake release # Release to RubyGems (updates version, tags, pushes)
|
28
28
|
```
|
29
29
|
|
30
30
|
### Code Quality
|
@@ -78,7 +78,6 @@ bin/rake standard # Run Standard Ruby linter
|
|
78
78
|
### Debugging Tests
|
79
79
|
- Records are saved to `spec/backspin_data/` by default
|
80
80
|
- Check YAML files to see recorded command outputs
|
81
|
-
- Use `VERBOSE=1` for additional output during tests
|
82
81
|
|
83
82
|
### Updating Credential Patterns
|
84
83
|
- Add patterns to `DEFAULT_CREDENTIAL_PATTERNS` in `lib/backspin.rb`
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
# Contributing to Backspin
|
2
|
+
|
3
|
+
Thank you for your interest in contributing to Backspin! This guide will help you get started with development and walk you through the contribution process.
|
4
|
+
|
5
|
+
Note that Backspin is in early development and the API _will_ change before stabilizing at 1.0.
|
6
|
+
|
7
|
+
## Table of Contents
|
8
|
+
|
9
|
+
- [Getting Started](#getting-started)
|
10
|
+
- [Development Setup](#development-setup)
|
11
|
+
- [Making Changes](#making-changes)
|
12
|
+
- [Testing](#testing)
|
13
|
+
- [Submitting Changes](#submitting-changes)
|
14
|
+
- [Code Style](#code-style)
|
15
|
+
- [Reporting Issues](#reporting-issues)
|
16
|
+
- [Feature Requests](#feature-requests)
|
17
|
+
|
18
|
+
## Getting Started
|
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.
|
21
|
+
|
22
|
+
### Prerequisites
|
23
|
+
|
24
|
+
- Ruby 3.2, 3.3, or 3.4
|
25
|
+
- Bundler
|
26
|
+
- Git
|
27
|
+
|
28
|
+
## Development Setup
|
29
|
+
|
30
|
+
1. Fork and clone the repository:
|
31
|
+
```bash
|
32
|
+
git clone https://github.com/rsanheim/backspin.git
|
33
|
+
cd backspin
|
34
|
+
```
|
35
|
+
|
36
|
+
2. Run the setup script:
|
37
|
+
```bash
|
38
|
+
bin/setup
|
39
|
+
```
|
40
|
+
|
41
|
+
3. Run the full build (specs & standardrb linting) to ensure everything is working:
|
42
|
+
```bash
|
43
|
+
bin/rake spec
|
44
|
+
```
|
45
|
+
|
46
|
+
## Making Changes
|
47
|
+
|
48
|
+
### Development Workflow
|
49
|
+
|
50
|
+
1. Create a feature branch:
|
51
|
+
```bash
|
52
|
+
git checkout -b feature/your-feature-name
|
53
|
+
```
|
54
|
+
|
55
|
+
2. Make your changes following the project conventions
|
56
|
+
|
57
|
+
3. Run tests frequently:
|
58
|
+
```bash
|
59
|
+
bin/rake spec
|
60
|
+
```
|
61
|
+
|
62
|
+
4. Run the linter:
|
63
|
+
```bash
|
64
|
+
bin/rake standard
|
65
|
+
```
|
66
|
+
|
67
|
+
5. Commit your changes with clear, descriptive messages
|
68
|
+
|
69
|
+
### Architecture Overview
|
70
|
+
|
71
|
+
**Core Components:**
|
72
|
+
|
73
|
+
- **Backspin Module** (`lib/backspin.rb`)
|
74
|
+
- Main API: `call`, `verify`, `verify!`, `use_record`
|
75
|
+
- Credential scrubbing logic
|
76
|
+
- Configuration management
|
77
|
+
|
78
|
+
- **Command Class** (`lib/backspin/command.rb`)
|
79
|
+
- Represents a single CLI execution
|
80
|
+
- Stores: args, stdout, stderr, status, recorded_at, etc
|
81
|
+
|
82
|
+
- **Record Class** (`lib/backspin/record.rb`)
|
83
|
+
- Manages YAML record files
|
84
|
+
- Handles recording/playback sequencing
|
85
|
+
|
86
|
+
### Common Development Tasks
|
87
|
+
|
88
|
+
#### Adding New Features
|
89
|
+
|
90
|
+
1. Write integration tests in `spec/backspin/`
|
91
|
+
2. Implement in appropriate module (usually `lib/backspin.rb`)
|
92
|
+
3. Update README.md if adding public API
|
93
|
+
4. Run the the full build
|
94
|
+
|
95
|
+
#### Updating Credential Patterns
|
96
|
+
|
97
|
+
- Add patterns to `DEFAULT_CREDENTIAL_PATTERNS` in `lib/backspin.rb`
|
98
|
+
- Test with appropriate fixtures in specs
|
99
|
+
|
100
|
+
#### Debugging Tests
|
101
|
+
|
102
|
+
- Records are saved to `spec/backspin_data/` by default
|
103
|
+
- Check YAML files to see recorded command outputs
|
104
|
+
|
105
|
+
## Testing
|
106
|
+
|
107
|
+
### Running Tests
|
108
|
+
|
109
|
+
```bash
|
110
|
+
# Run all tests
|
111
|
+
bin/rake spec
|
112
|
+
|
113
|
+
# Run specific test file
|
114
|
+
bundle exec rspec spec/backspin/record_spec.rb
|
115
|
+
|
116
|
+
# Run specific test
|
117
|
+
bundle exec rspec spec/backspin/record_spec.rb:42
|
118
|
+
```
|
119
|
+
|
120
|
+
### Writing Tests
|
121
|
+
|
122
|
+
Backspin uses integration-focused tests that exercise the full stack. When writing tests:
|
123
|
+
|
124
|
+
- Keep specs self-contained with their own setup, expectations, and cleanup
|
125
|
+
- Avoid shared contexts or helpers that hide important test details
|
126
|
+
- Use real shell commands (`echo`, `date`, etc.) for testing
|
127
|
+
- Ensure configuration is reset between tests to avoid side effects
|
128
|
+
- Verify new or updated test records in `spec/backspin_data/`
|
129
|
+
|
130
|
+
Example test structure:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
RSpec.describe "Feature name" do
|
134
|
+
it "does something specific" do
|
135
|
+
# Setup
|
136
|
+
record_name = "my_test_record"
|
137
|
+
|
138
|
+
# Exercise
|
139
|
+
result = Backspin.call(record_name) do
|
140
|
+
Open3.capture3("echo", "hello")
|
141
|
+
end
|
142
|
+
|
143
|
+
# Verify
|
144
|
+
expect(result.stdout).to eq("hello\n")
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
## Submitting Changes
|
150
|
+
|
151
|
+
### Pull Request Process
|
152
|
+
|
153
|
+
1. Ensure all tests pass
|
154
|
+
2. Update documentation if needed
|
155
|
+
3. Add entries to CHANGELOG.md following the existing format
|
156
|
+
4. Push your branch and create a pull request
|
157
|
+
5. Provide a clear description of your changes
|
158
|
+
6. Link any related issues
|
159
|
+
|
160
|
+
### Pull Request Guidelines
|
161
|
+
|
162
|
+
- Keep changes focused and atomic
|
163
|
+
- Include tests for new functionality
|
164
|
+
- Update examples in README.md if changing public APIs
|
165
|
+
- Ensure CI passes (tests against Ruby 3.2, 3.3, and 3.4)
|
166
|
+
|
167
|
+
## Code Style
|
168
|
+
|
169
|
+
Backspin uses [Standard Ruby](https://github.com/standardrb/standard) for code formatting. Run the linter before committing:
|
170
|
+
|
171
|
+
```bash
|
172
|
+
bin/rake standard
|
173
|
+
```
|
174
|
+
|
175
|
+
To automatically fix issues:
|
176
|
+
|
177
|
+
```bash
|
178
|
+
bin/rake standard:fix
|
179
|
+
```
|
180
|
+
|
181
|
+
## Reporting Issues
|
182
|
+
|
183
|
+
### Bug Reports
|
184
|
+
|
185
|
+
When reporting bugs, please include *full logs* including:
|
186
|
+
|
187
|
+
1. Ruby version (`ruby -v`) and ruby version manager info
|
188
|
+
2. Backspin version
|
189
|
+
3. Minimal reproduction code
|
190
|
+
4. Expected behavior vs actual behavior
|
191
|
+
5. Full error messages and stack traces
|
192
|
+
6. Relevant YAML record files (sanitized of sensitive data)
|
193
|
+
|
194
|
+
### Security Issues
|
195
|
+
|
196
|
+
For security vulnerabilities, please email the maintainers directly rather than opening a public issue.
|
197
|
+
|
198
|
+
## Feature Requests
|
199
|
+
|
200
|
+
We welcome feature requests! When proposing new features:
|
201
|
+
|
202
|
+
1. Check existing issues to avoid duplicates
|
203
|
+
2. Describe the use case and motivation
|
204
|
+
3. Provide code examples of how the feature would work
|
205
|
+
4. Be open to discussion and alternative approaches
|
206
|
+
|
207
|
+
## Additional Resources
|
208
|
+
|
209
|
+
- [Project README](README.md)
|
210
|
+
- [CLAUDE.md](CLAUDE.md) - AI assistant guidance
|
211
|
+
- [RSpec Documentation](https://rspec.info/)
|
212
|
+
- [Standardrb linting](https://github.com/standardrb/standard)
|
213
|
+
|
214
|
+
## Questions?
|
215
|
+
|
216
|
+
If you have questions about contributing, feel free to:
|
217
|
+
|
218
|
+
- Open an issue for discussion
|
219
|
+
- Check existing issues and pull requests
|
220
|
+
- Review the test suite for examples
|
221
|
+
|
222
|
+
Thank you for contributing to Backspin!
|
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
backspin (0.4.0)
|
5
|
+
ostruct (~> 0.5.0)
|
6
|
+
rspec-mocks (~> 3.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.4.3)
|
12
|
+
diff-lcs (1.6.2)
|
13
|
+
json (2.12.2)
|
14
|
+
language_server-protocol (3.17.0.5)
|
15
|
+
lint_roller (1.1.0)
|
16
|
+
ostruct (0.5.5)
|
17
|
+
parallel (1.27.0)
|
18
|
+
parser (3.3.8.0)
|
19
|
+
ast (~> 2.4.1)
|
20
|
+
racc
|
21
|
+
prism (1.4.0)
|
22
|
+
racc (1.8.1)
|
23
|
+
rainbow (3.1.1)
|
24
|
+
rake (13.3.0)
|
25
|
+
regexp_parser (2.10.0)
|
26
|
+
rspec (3.13.1)
|
27
|
+
rspec-core (~> 3.13.0)
|
28
|
+
rspec-expectations (~> 3.13.0)
|
29
|
+
rspec-mocks (~> 3.13.0)
|
30
|
+
rspec-core (3.13.4)
|
31
|
+
rspec-support (~> 3.13.0)
|
32
|
+
rspec-expectations (3.13.5)
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
+
rspec-support (~> 3.13.0)
|
35
|
+
rspec-mocks (3.13.5)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.13.0)
|
38
|
+
rspec-support (3.13.4)
|
39
|
+
rubocop (1.75.8)
|
40
|
+
json (~> 2.3)
|
41
|
+
language_server-protocol (~> 3.17.0.2)
|
42
|
+
lint_roller (~> 1.1.0)
|
43
|
+
parallel (~> 1.10)
|
44
|
+
parser (>= 3.3.0.2)
|
45
|
+
rainbow (>= 2.2.2, < 4.0)
|
46
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
47
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
48
|
+
ruby-progressbar (~> 1.7)
|
49
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
50
|
+
rubocop-ast (1.44.1)
|
51
|
+
parser (>= 3.3.7.2)
|
52
|
+
prism (~> 1.4)
|
53
|
+
rubocop-performance (1.25.0)
|
54
|
+
lint_roller (~> 1.1)
|
55
|
+
rubocop (>= 1.75.0, < 2.0)
|
56
|
+
rubocop-ast (>= 1.38.0, < 2.0)
|
57
|
+
ruby-progressbar (1.13.0)
|
58
|
+
standard (1.50.0)
|
59
|
+
language_server-protocol (~> 3.17.0.2)
|
60
|
+
lint_roller (~> 1.0)
|
61
|
+
rubocop (~> 1.75.5)
|
62
|
+
standard-custom (~> 1.0.0)
|
63
|
+
standard-performance (~> 1.8)
|
64
|
+
standard-custom (1.0.2)
|
65
|
+
lint_roller (~> 1.0)
|
66
|
+
rubocop (~> 1.50)
|
67
|
+
standard-performance (1.8.0)
|
68
|
+
lint_roller (~> 1.1)
|
69
|
+
rubocop-performance (~> 1.25.0)
|
70
|
+
timecop (0.9.10)
|
71
|
+
unicode-display_width (3.1.4)
|
72
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
73
|
+
unicode-emoji (4.0.4)
|
74
|
+
|
75
|
+
PLATFORMS
|
76
|
+
arm64-darwin-24
|
77
|
+
ruby
|
78
|
+
|
79
|
+
DEPENDENCIES
|
80
|
+
backspin!
|
81
|
+
rake (~> 13.0)
|
82
|
+
rspec (~> 3.0)
|
83
|
+
standard
|
84
|
+
timecop (~> 0.9)
|
85
|
+
|
86
|
+
BUNDLED WITH
|
87
|
+
2.6.9
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Backspin
|
1
|
+
# Backspin [](https://badge.fury.io/rb/backspin) [](https://dl.circleci.com/status-badge/redirect/gh/rsanheim/backspin/tree/main)
|
2
2
|
|
3
|
-
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
|
3
|
+
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
4
|
|
5
|
-
**NOTE:** Backspin
|
5
|
+
**NOTE:** Backspin should be considered alpha quality software while pre v1.0. It is in heavy development, and you can expect the API to change. It is being developed in conjunction with production CLI apps, so the API will be refined and improved as we get to 1.0.
|
6
6
|
|
7
7
|
Inspired by [VCR](https://github.com/vcr/vcr) and other [golden master](https://en.wikipedia.org/wiki/Golden_master_(software_development)) libraries.
|
8
8
|
|
@@ -24,73 +24,147 @@ And then run `bundle install`.
|
|
24
24
|
|
25
25
|
## Usage
|
26
26
|
|
27
|
-
###
|
27
|
+
### Quick Start
|
28
|
+
|
29
|
+
The simplest way to use Backspin is with the `run` method, which automatically records on the first execution and verifies on subsequent runs:
|
28
30
|
|
29
31
|
```ruby
|
30
|
-
require "backspin"
|
32
|
+
require "backspin"
|
33
|
+
|
34
|
+
# First run: records the output
|
35
|
+
result = Backspin.run("my_command") do
|
36
|
+
Open3.capture3("echo hello world")
|
37
|
+
end
|
31
38
|
|
32
|
-
#
|
33
|
-
result = Backspin.
|
34
|
-
|
35
|
-
# This will save the output to `spec/backspin_data/echo_hello.yaml`.
|
39
|
+
# Subsequent runs: verifies the output matches
|
40
|
+
result = Backspin.run("my_command") do
|
41
|
+
Open3.capture3("echo hello world")
|
36
42
|
end
|
37
43
|
|
44
|
+
# Use run! to automatically fail tests on mismatch
|
45
|
+
Backspin.run!("my_command") do
|
46
|
+
Open3.capture3("echo hello mars")
|
47
|
+
end
|
48
|
+
# Raises an error because stdout will not match the recorded output
|
38
49
|
```
|
39
50
|
|
40
|
-
###
|
51
|
+
### Recording Modes
|
52
|
+
|
53
|
+
Backspin supports different modes for controlling how commands are recorded and verified:
|
41
54
|
|
42
55
|
```ruby
|
43
|
-
#
|
44
|
-
result = Backspin.
|
56
|
+
# Auto mode (default): Record on first run, verify on subsequent runs
|
57
|
+
result = Backspin.run("my_command") do
|
58
|
+
Open3.capture3("echo hello")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Explicit record mode: Always record, overwriting existing recordings
|
62
|
+
result = Backspin.run("echo_test", mode: :record) do
|
45
63
|
Open3.capture3("echo hello")
|
46
64
|
end
|
65
|
+
# This will save the output to `spec/backspin_data/echo_test.yml`.
|
47
66
|
|
67
|
+
# Explicit verify mode: Always verify against existing recording
|
68
|
+
result = Backspin.run("echo_test", mode: :verify) do
|
69
|
+
Open3.capture3("echo hello")
|
70
|
+
end
|
48
71
|
expect(result.verified?).to be true
|
72
|
+
|
73
|
+
# Playback mode: Return recorded output without running the command
|
74
|
+
result = Backspin.run("slow_command", mode: :playback) do
|
75
|
+
Open3.capture3("slow_command") # Not executed - returns recorded output
|
76
|
+
end
|
49
77
|
```
|
50
78
|
|
51
|
-
### Using
|
79
|
+
### Using run! for automatic test failures
|
80
|
+
|
81
|
+
The `run!` method works exactly like `run` but automatically fails the test if verification fails:
|
52
82
|
|
53
83
|
```ruby
|
54
84
|
# Automatically fail the test if output doesn't match
|
55
|
-
Backspin.
|
85
|
+
Backspin.run!("echo_test") do
|
56
86
|
Open3.capture3("echo hello")
|
57
87
|
end
|
88
|
+
# Raises an error with detailed diff if verification fails from recorded data in "echo_test.yml"
|
58
89
|
```
|
59
90
|
|
60
|
-
###
|
91
|
+
### Custom matchers
|
92
|
+
|
93
|
+
For cases where exact matching isn't suitable, you can provide custom verification logic:
|
61
94
|
|
62
95
|
```ruby
|
63
|
-
#
|
64
|
-
result = Backspin.
|
65
|
-
|
96
|
+
# Use custom logic to verify output
|
97
|
+
result = Backspin.run("version_check",
|
98
|
+
matcher: ->(recorded, actual) {
|
99
|
+
# Just check that both start with "ruby"
|
100
|
+
recorded["stdout"].start_with?("ruby") &&
|
101
|
+
actual["stdout"].start_with?("ruby")
|
102
|
+
}) do
|
103
|
+
Open3.capture3("ruby --version")
|
66
104
|
end
|
67
105
|
```
|
68
106
|
|
69
|
-
###
|
107
|
+
### Working with the Result Object
|
108
|
+
|
109
|
+
The API returns a `RecordResult` object with helpful methods:
|
70
110
|
|
71
111
|
```ruby
|
72
|
-
|
73
|
-
|
74
|
-
matcher: ->(recorded, actual) {
|
75
|
-
# Just check that both start with "ruby"
|
76
|
-
recorded["stdout"].start_with?("ruby") &&
|
77
|
-
actual["stdout"].start_with?("ruby")
|
78
|
-
}) do
|
79
|
-
Open3.capture3("ruby --version")
|
112
|
+
result = Backspin.run("my_test") do
|
113
|
+
Open3.capture3("echo out; echo err >&2; exit 42")
|
80
114
|
end
|
115
|
+
|
116
|
+
# Check the mode
|
117
|
+
result.recorded? # true on first run
|
118
|
+
result.verified? # true/false on subsequent runs, nil when recording
|
119
|
+
result.playback? # true in playback mode
|
120
|
+
|
121
|
+
# Access output (first command for single commands)
|
122
|
+
result.stdout # "out\n"
|
123
|
+
result.stderr # "err\n"
|
124
|
+
result.status # 42
|
125
|
+
result.success? # false (non-zero exit)
|
126
|
+
result.output # The raw return value from the block
|
127
|
+
|
128
|
+
# Debug information
|
129
|
+
result.record_path # Path to the YAML file
|
130
|
+
result.error_message # Human-readable error if verification failed
|
131
|
+
result.diff # Diff between expected and actual output
|
81
132
|
```
|
82
133
|
|
83
|
-
###
|
134
|
+
### Multiple Commands
|
84
135
|
|
85
|
-
|
136
|
+
Backspin automatically records and verifies all commands executed in a block:
|
86
137
|
|
87
138
|
```ruby
|
88
|
-
|
89
|
-
|
90
|
-
Open3.capture3("
|
139
|
+
result = Backspin.run("multi_command_test") do
|
140
|
+
# All of these commands will be recorded
|
141
|
+
version, = Open3.capture3("ruby --version")
|
142
|
+
files, = Open3.capture3("ls -la")
|
143
|
+
system("echo 'Processing...'") # Note: system doesn't capture output
|
144
|
+
data, stderr, = Open3.capture3("curl https://api.example.com/data")
|
145
|
+
|
146
|
+
# Return whatever you need
|
147
|
+
{ version: version.strip, file_count: files.lines.count, data: data }
|
91
148
|
end
|
149
|
+
|
150
|
+
# Access individual command results
|
151
|
+
result.commands.size # 4
|
152
|
+
result.multiple_commands? # true
|
153
|
+
|
154
|
+
# For multiple commands, use these accessors
|
155
|
+
result.all_stdout # Array of stdout from each command
|
156
|
+
result.all_stderr # Array of stderr from each command
|
157
|
+
result.all_status # Array of exit statuses
|
158
|
+
|
159
|
+
# Or access specific commands
|
160
|
+
result.commands[0].stdout # Ruby version output
|
161
|
+
result.commands[1].stdout # ls output
|
162
|
+
result.commands[2].status # system call exit status (stdout is empty)
|
163
|
+
result.commands[3].stderr # curl errors if any
|
92
164
|
```
|
93
165
|
|
166
|
+
When verifying multiple commands, Backspin ensures all commands match in the exact order they were recorded. If any command differs, you'll get a detailed error showing which commands failed.
|
167
|
+
|
94
168
|
### Credential Scrubbing
|
95
169
|
|
96
170
|
If the CLI interaction you are recording contains sensitive data in stdout or stderr, you should be careful to make sure it is not recorded to yaml!
|
@@ -102,7 +176,7 @@ A tool like [trufflehog](https://github.com/trufflesecurity/trufflehog) or [gitl
|
|
102
176
|
|
103
177
|
```ruby
|
104
178
|
# This will automatically scrub AWS keys, API tokens, passwords, etc.
|
105
|
-
Backspin.
|
179
|
+
Backspin.run("aws_command") do
|
106
180
|
Open3.capture3("aws s3 ls")
|
107
181
|
end
|
108
182
|
|
@@ -146,4 +220,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/rsanhe
|
|
146
220
|
|
147
221
|
## License
|
148
222
|
|
149
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
223
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/backspin.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = "Backspin is a Ruby library for characterization testing of command-line interfaces. Inspired by VCR's cassette-based approach, it records and replays CLI interactions to make testing faster and more deterministic."
|
11
11
|
spec.homepage = "https://github.com/rsanheim/backspin"
|
12
12
|
spec.license = "MIT"
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new(">=
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
|
14
14
|
|
15
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
16
16
|
spec.metadata["source_code_uri"] = spec.homepage
|
@@ -20,10 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
21
21
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
22
|
end
|
23
|
-
spec.bindir = "
|
24
|
-
spec.executables = spec.files.grep(%r{
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
|
27
27
|
spec.add_dependency "rspec-mocks", "~> 3.0"
|
28
|
-
spec.add_dependency "ostruct"
|
28
|
+
spec.add_dependency "ostruct", "~> 0.5.0"
|
29
29
|
end
|
data/bin/rake
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
14
|
+
|
15
|
+
if File.file?(bundle_binstub)
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
17
|
+
load(bundle_binstub)
|
18
|
+
else
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require "bundler/setup"
|
26
|
+
|
27
|
+
load Gem.bin_path("rake", "rake")
|