Module: Backspin

Defined in:
lib/backspin/snapshot.rb,
lib/backspin.rb,
lib/backspin/record.rb,
lib/backspin/matcher.rb,
lib/backspin/version.rb,
lib/backspin/recorder.rb,
lib/backspin/snapshot.rb,
lib/backspin/command_diff.rb,
lib/backspin/configuration.rb,
lib/backspin/backspin_result.rb

Overview

Define the Backspin::Capturer class for identification.

Defined Under Namespace

Classes: BackspinResult, Capturer, CommandDiff, Configuration, Matcher, Record, RecordFormatError, RecordNotFoundError, Recorder, ReferenceCommandError, Snapshot, VerificationError

Constant Summary collapse

VALID_MODES =
%i[auto record verify].freeze
VERSION =
"0.13.0"

Class Method Summary collapse

Class Method Details

.capture(record_name, mode: :auto, matcher: nil, filter: nil, filter_on: :both, &block) ⇒ BackspinResult

Captures all stdout/stderr output from a block

Parameters:

  • record_name (String)

    Name for the record file

  • mode (Symbol) (defaults to: :auto)

    Recording mode - :auto, :record, :verify

  • matcher (Proc, Hash) (defaults to: nil)

    Custom matcher for verification

  • filter (Proc) (defaults to: nil)

    Custom filter for recorded data/canonicalization

  • filter_on (Symbol) (defaults to: :both)

    Filter application mode - :both (default), :record

Returns:

Raises:

  • (ArgumentError)


115
116
117
118
119
120
121
# File 'lib/backspin.rb', line 115

def capture(record_name, mode: :auto, matcher: nil, filter: nil, filter_on: :both, &block)
  raise ArgumentError, "record_name is required" if record_name.nil? || record_name.empty?
  raise ArgumentError, "block is required" unless block_given?
  validate_filter_on!(filter_on)

  perform_capture(record_name, mode: mode, matcher: matcher, filter: filter, filter_on: filter_on, &block)
end

.compare(reference:, actual:, env: nil, matcher: nil, filter: nil) ⇒ BackspinResult

Differential testing - runs a reference command and a command under test and compares their filtered output. Nothing is recorded to disk.

Only stdout, stderr, and status are compared, so the two commands may differ in argv and env without any normalization.

Parameters:

  • reference (String, Array)

    Command that defines the correct output

  • actual (String, Array)

    Command under test

  • env (Hash) (defaults to: nil)

    Environment variables passed to both commands

  • matcher (Proc, Hash) (defaults to: nil)

    Custom matcher for verification

  • filter (Proc) (defaults to: nil)

    Custom filter applied to both sides before comparing

Returns:

  • (BackspinResult)

    expected = reference, actual = command under test



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/backspin.rb', line 135

def compare(reference:, actual:, env: nil, matcher: nil, filter: nil)
  normalized_env = normalize_env(env)

  expected_snapshot, = capture_command_snapshot(reference, normalized_env)
  if expected_snapshot.stdout.empty? && expected_snapshot.stderr.empty?
    raise ReferenceCommandError,
      "Reference command produced no output (exit status #{expected_snapshot.status}): #{reference.inspect}"
  end

  actual_snapshot, = capture_command_snapshot(actual, normalized_env)

  command_diff = CommandDiff.new(
    expected: expected_snapshot,
    actual: actual_snapshot,
    matcher: matcher,
    filter: filter
  )
  result = BackspinResult.new(
    mode: :verify,
    record_path: nil,
    actual: actual_snapshot,
    expected: expected_snapshot,
    verified: command_diff.verified?,
    command_diff: command_diff
  )

  raise_on_verification_failure!(result)

  result
end

.configurationObject



48
49
50
51
52
# File 'lib/backspin.rb', line 48

def configuration
  return @configuration if @configuration

  @configuration = Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields:



54
55
56
# File 'lib/backspin.rb', line 54

def configure
  yield(configuration)
end

.reset_configuration!Object



58
59
60
# File 'lib/backspin.rb', line 58

def reset_configuration!
  @configuration = Configuration.new
end

.run(command = nil, name:, env: nil, mode: :auto, matcher: nil, filter: nil, filter_on: :both, &block) ⇒ BackspinResult

Primary API - records on first run, verifies on subsequent runs

Parameters:

  • command (String, Array) (defaults to: nil)

    Command to execute via Open3.capture3

  • name (String)

    Name for the record file

  • env (Hash) (defaults to: nil)

    Environment variables to pass to Open3.capture3

  • mode (Symbol) (defaults to: :auto)

    Recording mode - :auto, :record, :verify

  • matcher (Proc, Hash) (defaults to: nil)

    Custom matcher for verification

  • filter (Proc) (defaults to: nil)

    Custom filter for recorded data/canonicalization

  • filter_on (Symbol) (defaults to: :both)

    Filter application mode - :both (default), :record

Returns:

Raises:

  • (ArgumentError)


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/backspin.rb', line 84

def run(command = nil, name:, env: nil, mode: :auto, matcher: nil, filter: nil, filter_on: :both, &block)
  validate_filter_on!(filter_on)

  if block_given?
    raise ArgumentError, "command must be omitted when using a block" unless command.nil?
    raise ArgumentError, "env is not supported when using a block" unless env.nil?

    return perform_capture(name, mode: mode, matcher: matcher, filter: filter, filter_on: filter_on, &block)
  end

  raise ArgumentError, "command is required" if command.nil?

  perform_command_run(
    command,
    name: name,
    env: env,
    mode: mode,
    matcher: matcher,
    filter: filter,
    filter_on: filter_on
  )
end

.scrub_text(text) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
# File 'lib/backspin.rb', line 62

def scrub_text(text)
  return text unless configuration.scrub_credentials && text

  scrubbed = text.dup
  configuration.credential_patterns.each do |pattern|
    scrubbed.gsub!(pattern) do |match|
      "*" * match.length
    end
  end
  scrubbed
end