backspin 0.11.0 → 0.12.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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -0
  3. data/CHANGELOG.md +6 -4
  4. data/Gemfile.lock +3 -1
  5. data/README.md +60 -0
  6. data/Rakefile +27 -0
  7. data/backspin.gemspec +2 -0
  8. data/fixtures/projects/dummy_cli_gem/.rspec +3 -0
  9. data/fixtures/projects/dummy_cli_gem/Gemfile +12 -0
  10. data/fixtures/projects/dummy_cli_gem/Gemfile.lock +55 -0
  11. data/fixtures/projects/dummy_cli_gem/LICENSE.txt +1 -0
  12. data/fixtures/projects/dummy_cli_gem/README.md +7 -0
  13. data/fixtures/projects/dummy_cli_gem/Rakefile +8 -0
  14. data/fixtures/projects/dummy_cli_gem/dummy_cli_gem.gemspec +22 -0
  15. data/fixtures/projects/dummy_cli_gem/exe/dummy_cli_gem +8 -0
  16. data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_echo.yml +18 -0
  17. data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_ls.yml +18 -0
  18. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/cli.rb +35 -0
  19. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/version.rb +5 -0
  20. data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem.rb +7 -0
  21. data/fixtures/projects/dummy_cli_gem/mise.toml +2 -0
  22. data/fixtures/projects/dummy_cli_gem/spec/dummy_cli_gem_backspin_spec.rb +46 -0
  23. data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_echo.yml +18 -0
  24. data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_ls.yml +18 -0
  25. data/fixtures/projects/dummy_cli_gem/spec/fixtures/listing_target/alpha.txt +1 -0
  26. data/fixtures/projects/dummy_cli_gem/spec/spec_helper.rb +22 -0
  27. data/lib/backspin/configuration.rb +13 -0
  28. data/lib/backspin/version.rb +1 -1
  29. data/lib/backspin.rb +22 -2
  30. data/mise.toml +2 -0
  31. metadata +36 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9533baf54aa3ae09b9d483d41149a6ede985f3f974f038e287034928a0ab1e3
4
- data.tar.gz: 75b9cb2018b1f1ca5878f6ce75af74bc9fc4822319ac2f12e8c654f30624f859
3
+ metadata.gz: 6373427307003e8469435ce1c461e6f9bf6ff307c952bce87f07c393470f5fdc
4
+ data.tar.gz: 3e8487ba8345a4fef0b8419487de0e84b756c302ada8b313e2bf0e5cd6c1ec1a
5
5
  SHA512:
6
- metadata.gz: a8d851c2836d8cd0be15b341a7f2af332c356aaa9688766a5b51f65110539a2d500cac8238abd4bc0a668a689effbef45a8f009829a3cd28ff4acd249bb506b4
7
- data.tar.gz: 0151e1de9a47285ca75552e97154b57100de50fe691100869144e1ed17e1b7c421a012599c4462a333f6abd0c4779f110a4fbffd2bbe60d14d600469da459bad
6
+ metadata.gz: d7f1915e4632d8fb4b08ae6a8ff0e6e9846ed3f1cd783c7dcc283ca2d3816afb518173a50cd519b5859eb6d991be7636a81a48e34fb63f0176d14c9a2bdd500a
7
+ data.tar.gz: 7307a43c18730ec430f39dbadecb301eaad177ecc3795906f9682ec3e4781e9d5d9ec2b8c913220f8c5985393ae90e73974e5f2855a40ea17d33f87914b5a527
data/.circleci/config.yml CHANGED
@@ -20,6 +20,9 @@ jobs:
20
20
  - run:
21
21
  name: Run specs
22
22
  command: bundle exec rake spec
23
+ - run:
24
+ name: Run fake gem specs
25
+ command: bundle exec rake spec:fake_gem
23
26
 
24
27
  lint:
25
28
  resource_class: medium
data/CHANGELOG.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+ * Added `BACKSPIN_MODE` environment variable to globally override recording mode (`auto`, `record`, `verify`).
5
+ * Explicit `mode:` kwarg still takes highest precedence, followed by the env var, then auto-detection.
6
+ * Added configurable logger to `Backspin::Configuration` (defaults to WARN level, logfmt-lite format, and can be disabled with `config.logger = nil`).
7
+
3
8
  ## 0.11.0 - 2026-02-11
4
9
  * Added immutable top-level `first_recorded_at` metadata for record files.
5
10
  * Added mutable top-level `recorded_at` metadata that updates on each successful re-record.
6
11
  * Added top-level `record_count`, incremented on each successful record write.
7
- * Record format now writes `format_version: 4.1`; loading remains backward-compatible with 4.0 record files.
8
- * Added acceptance coverage for v4.1 schema and 4.0-to-4.1 upgrade behavior.
9
- * Removed legacy committed `.yml` record fixtures from old schema versions.
12
+ * Bumped record format to 4.1; loading remains backward-compatible with 4.0 record files.
10
13
 
11
14
  ## 0.10.0 - 2026-02-11
12
15
  * Added `filter_on` to `Backspin.run` and `Backspin.capture` (`:both` default, `:record` opt-out).
@@ -19,7 +22,6 @@
19
22
  * Breaking: result convenience accessors (`result.stdout`, `result.stderr`, `result.status`) were removed in favor of snapshot access.
20
23
  * Breaking: record format bumped to 4.0 and now persists a single `snapshot` object (v3 records are rejected).
21
24
  * Simplification: removed legacy `Command`, `CommandResult`, and `RecordResult` layers; matcher/diff now operate directly on snapshots.
22
- * Added focused coverage for the new result contract and capture stream restoration behavior.
23
25
  * Updated project docs to reflect the BackspinResult + Snapshot API surface.
24
26
 
25
27
  ## 0.8.0 - 2026-02-05
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- backspin (0.11.0)
4
+ backspin (0.12.0)
5
+ logger
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
@@ -11,6 +12,7 @@ GEM
11
12
  json (2.16.0)
12
13
  language_server-protocol (3.17.0.5)
13
14
  lint_roller (1.1.0)
15
+ logger (1.7.0)
14
16
  parallel (1.27.0)
15
17
  parser (3.3.10.0)
16
18
  ast (~> 2.4.1)
data/README.md CHANGED
@@ -91,6 +91,26 @@ result = Backspin.run(["echo", "hello"], name: "echo_test", mode: :verify)
91
91
  expect(result.verified?).to be true
92
92
  ```
93
93
 
94
+ ### Environment Variable Mode Override
95
+
96
+ Set `BACKSPIN_MODE` to globally force a recording mode without changing any test code:
97
+
98
+ ```bash
99
+ # Re-record all fixtures
100
+ BACKSPIN_MODE=record bundle exec rspec
101
+
102
+ # Verify-only (CI, no accidental re-records)
103
+ BACKSPIN_MODE=verify bundle exec rspec
104
+ ```
105
+
106
+ Precedence (highest to lowest):
107
+
108
+ 1. Explicit `mode:` kwarg (`:record` or `:verify`)
109
+ 2. `BACKSPIN_MODE` environment variable
110
+ 3. Auto-detection (record if no file exists, verify if it does)
111
+
112
+ Allowed values: `auto`, `record`, `verify` (case-insensitive). Invalid values raise `ArgumentError`.
113
+
94
114
  ### Record Metadata
95
115
 
96
116
  Backspin writes records using `format_version: "4.1"` with top-level metadata:
@@ -243,6 +263,34 @@ result = Backspin.run(["echo", "different"], name: "my_test")
243
263
  Backspin.reset_configuration!
244
264
  ```
245
265
 
266
+ ### Logging
267
+
268
+ Backspin includes a configurable logger for diagnostics. By default it is set to WARN level; but most messages are logged at DEBUG level.
269
+ So if you are looking for more detailed logs, you can set the logger to DEBUG level:
270
+
271
+ ```ruby
272
+ Backspin.configure do |config|
273
+ config.logger = Logger.new($stdout)
274
+ config.logger.level = Logger::DEBUG
275
+ end
276
+ ```
277
+
278
+ To replace the logger entirely:
279
+
280
+ ```ruby
281
+ Backspin.configure do |config|
282
+ config.logger = Logger.new("log/backspin.log")
283
+ end
284
+ ```
285
+
286
+ To disable Backspin logging entirely (for example in tests):
287
+
288
+ ```ruby
289
+ Backspin.configure do |config|
290
+ config.logger = nil
291
+ end
292
+ ```
293
+
246
294
  ### Credential Scrubbing
247
295
 
248
296
  If the CLI interaction you are recording contains sensitive data in stdout/stderr, you should be careful to make sure it is not recorded to YAML.
@@ -274,6 +322,18 @@ Automatic scrubbing includes:
274
322
 
275
323
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
276
324
 
325
+ This repo also includes a decoupled full-stack fixture gem at `fixtures/projects/dummy_cli_gem` that uses Backspin the way downstream projects do. Run it with:
326
+
327
+ ```bash
328
+ bundle exec rake full_stack:dummy_app
329
+ ```
330
+
331
+ To re-record that fixture's committed YAML snapshots:
332
+
333
+ ```bash
334
+ bundle exec rake full_stack:record_dummy_app
335
+ ```
336
+
277
337
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
278
338
 
279
339
  ## Contributing
data/Rakefile CHANGED
@@ -6,6 +6,33 @@ require "standard/rake"
6
6
 
7
7
  RSpec::Core::RakeTask.new(:spec)
8
8
 
9
+ def run_in_fake_gem(command)
10
+ dummy_app_dir = File.expand_path("fixtures/projects/dummy_cli_gem", __dir__)
11
+
12
+ Bundler.with_unbundled_env do
13
+ Dir.chdir(dummy_app_dir) do
14
+ sh "bundle check || bundle install"
15
+ sh command
16
+ end
17
+ end
18
+ end
19
+
20
+ namespace :spec do
21
+ desc "Run the dummy fixture gem specs with Backspin (decoupled from main suite)"
22
+ task :fake_gem do
23
+ run_in_fake_gem("bundle exec rspec")
24
+ end
25
+
26
+ desc "Re-record Backspin YAML fixtures for the dummy fixture gem"
27
+ task :fake_gem_record do
28
+ original_record_mode = ENV["RECORD_MODE"]
29
+ ENV["RECORD_MODE"] = "record"
30
+ run_in_fake_gem("bundle exec rspec")
31
+ ensure
32
+ ENV["RECORD_MODE"] = original_record_mode
33
+ end
34
+ end
35
+
9
36
  task default: %i[spec standard]
10
37
 
11
38
  load "release.rake" if File.exist?("release.rake")
data/backspin.gemspec CHANGED
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.bindir = "exe"
25
25
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
26
  spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "logger"
27
29
  end
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "backspin", path: "../../.."
8
+
9
+ group :development, :test do
10
+ gem "rake", "~> 13"
11
+ gem "rspec", "~> 3"
12
+ end
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: ../../..
3
+ specs:
4
+ backspin (0.11.0)
5
+ logger
6
+
7
+ PATH
8
+ remote: .
9
+ specs:
10
+ dummy_cli_gem (0.1.0)
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ diff-lcs (1.6.2)
16
+ logger (1.7.0)
17
+ rake (13.3.1)
18
+ rspec (3.13.2)
19
+ rspec-core (~> 3.13.0)
20
+ rspec-expectations (~> 3.13.0)
21
+ rspec-mocks (~> 3.13.0)
22
+ rspec-core (3.13.6)
23
+ rspec-support (~> 3.13.0)
24
+ rspec-expectations (3.13.5)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.13.0)
27
+ rspec-mocks (3.13.7)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.13.0)
30
+ rspec-support (3.13.7)
31
+
32
+ PLATFORMS
33
+ aarch64-linux
34
+ ruby
35
+
36
+ DEPENDENCIES
37
+ backspin!
38
+ dummy_cli_gem!
39
+ rake (~> 13)
40
+ rspec (~> 3)
41
+
42
+ CHECKSUMS
43
+ backspin (0.11.0)
44
+ diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
45
+ dummy_cli_gem (0.1.0)
46
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
47
+ rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
48
+ rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
49
+ rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
50
+ rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
51
+ rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c
52
+ rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
53
+
54
+ BUNDLED WITH
55
+ 4.0.5
@@ -0,0 +1 @@
1
+ MIT License
@@ -0,0 +1,7 @@
1
+ # DummyCliGem Fixture
2
+
3
+ This is a minimal fixture gem used by Backspin's full-stack integration checks.
4
+
5
+ - It shells out to standard unix utilities (`echo`, `ls`).
6
+ - Its RSpec suite verifies command output through `Backspin.run`.
7
+ - Snapshot YAML records are stored in-repo under `spec/fixtures/backspin`.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/dummy_cli_gem/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "dummy_cli_gem"
7
+ spec.version = DummyCliGem::VERSION
8
+ spec.authors = ["Backspin"]
9
+ spec.email = ["noreply@example.com"]
10
+ spec.summary = "Dummy CLI gem fixture for Backspin full-stack verification"
11
+ spec.description = "Fixture gem that shells out to unix utilities and is tested via Backspin snapshots."
12
+ spec.homepage = "https://example.com/dummy_cli_gem"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0")
15
+
16
+ spec.files = Dir.chdir(__dir__) do
17
+ Dir["lib/**/*.rb", "exe/*", "spec/**/*", "script/**/*", "README.md", "LICENSE.txt"]
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = ["dummy_cli_gem"]
21
+ spec.require_paths = ["lib"]
22
+ end
@@ -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
  [
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Backspin
4
- VERSION = "0.11.0"
4
+ VERSION = "0.12.0"
5
5
  end
data/lib/backspin.rb CHANGED
@@ -14,6 +14,8 @@ require "backspin/backspin_result"
14
14
  require "backspin/recorder"
15
15
 
16
16
  module Backspin
17
+ VALID_MODES = %i[auto record verify].freeze
18
+
17
19
  class RecordNotFoundError < StandardError; end
18
20
 
19
21
  class VerificationError < StandardError
@@ -242,8 +244,26 @@ module Backspin
242
244
  def determine_mode(mode_option, record_path)
243
245
  return mode_option if mode_option && mode_option != :auto
244
246
 
245
- # Auto mode: record if file doesn't exist, verify if it does
246
- File.exist?(record_path) ? :verify : :record
247
+ env_mode = mode_from_env
248
+ if env_mode && env_mode != :auto
249
+ configuration.logger&.debug { "event=mode_resolved mode=#{env_mode} source=env record=#{record_path}" }
250
+ return env_mode
251
+ end
252
+
253
+ resolved = File.exist?(record_path) ? :verify : :record
254
+ configuration.logger&.debug { "event=mode_resolved mode=#{resolved} source=auto record=#{record_path}" }
255
+ resolved
256
+ end
257
+
258
+ def mode_from_env
259
+ raw = ENV["BACKSPIN_MODE"]
260
+ return if raw.nil? || raw.strip.empty?
261
+
262
+ mode = raw.strip.downcase.to_sym
263
+ return mode if VALID_MODES.include?(mode)
264
+
265
+ raise ArgumentError,
266
+ "Invalid BACKSPIN_MODE value: #{raw.inspect}. Allowed values: auto, record, verify"
247
267
  end
248
268
 
249
269
  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.12.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.
@@ -40,6 +54,25 @@ files:
40
54
  - docs/backspin-result-api-sketch.md
41
55
  - examples/match_on_example.rb
42
56
  - fixtures/backspin/.gitkeep
57
+ - fixtures/projects/dummy_cli_gem/.rspec
58
+ - fixtures/projects/dummy_cli_gem/Gemfile
59
+ - fixtures/projects/dummy_cli_gem/Gemfile.lock
60
+ - fixtures/projects/dummy_cli_gem/LICENSE.txt
61
+ - fixtures/projects/dummy_cli_gem/README.md
62
+ - fixtures/projects/dummy_cli_gem/Rakefile
63
+ - fixtures/projects/dummy_cli_gem/dummy_cli_gem.gemspec
64
+ - fixtures/projects/dummy_cli_gem/exe/dummy_cli_gem
65
+ - fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_echo.yml
66
+ - fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_ls.yml
67
+ - fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem.rb
68
+ - fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/cli.rb
69
+ - fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/version.rb
70
+ - fixtures/projects/dummy_cli_gem/mise.toml
71
+ - fixtures/projects/dummy_cli_gem/spec/dummy_cli_gem_backspin_spec.rb
72
+ - fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_echo.yml
73
+ - fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_ls.yml
74
+ - fixtures/projects/dummy_cli_gem/spec/fixtures/listing_target/alpha.txt
75
+ - fixtures/projects/dummy_cli_gem/spec/spec_helper.rb
43
76
  - lib/backspin.rb
44
77
  - lib/backspin/backspin_result.rb
45
78
  - lib/backspin/command_diff.rb
@@ -49,6 +82,7 @@ files:
49
82
  - lib/backspin/recorder.rb
50
83
  - lib/backspin/snapshot.rb
51
84
  - lib/backspin/version.rb
85
+ - mise.toml
52
86
  - release.rake
53
87
  - script/lint
54
88
  - script/run_affected_tests