backspin 0.4.1 → 0.4.5
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 +8 -7
- data/CLAUDE.md +5 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +3 -1
- data/MATCH_ON_USAGE.md +110 -0
- 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_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/field_matcher_demo.yml +17 -0
- data/fixtures/backspin/field_matcher_values.yml +14 -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/string_symbol_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 +34 -173
- data/release.rake +104 -0
- data/script/lint +6 -0
- metadata +54 -5
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-06-10T11:00:27-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:00:27-05:00'
|
@@ -0,0 +1,21 @@
|
|
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"
|
10
|
+
- good;
|
11
|
+
- echo
|
12
|
+
- bad
|
13
|
+
- ">&2'"
|
14
|
+
stdout: 'good
|
15
|
+
|
16
|
+
'
|
17
|
+
stderr: 'bad
|
18
|
+
|
19
|
+
'
|
20
|
+
status: 0
|
21
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,15 @@
|
|
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
|
+
- echo
|
8
|
+
- "'Version:"
|
9
|
+
- 1.2.3'
|
10
|
+
stdout: 'Version: 1.2.3
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: ''
|
14
|
+
status: 0
|
15
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,28 @@
|
|
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"
|
10
|
+
- "\"User:"
|
11
|
+
- alice@example.com";
|
12
|
+
- echo
|
13
|
+
- "\"Error:"
|
14
|
+
- Connection
|
15
|
+
- timeout
|
16
|
+
- at
|
17
|
+
- 10:30:00"
|
18
|
+
- ">&2;"
|
19
|
+
- exit
|
20
|
+
- 1'
|
21
|
+
stdout: 'User: alice@example.com
|
22
|
+
|
23
|
+
'
|
24
|
+
stderr: 'Error: Connection timeout at 10:30:00
|
25
|
+
|
26
|
+
'
|
27
|
+
status: 1
|
28
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,23 @@
|
|
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"
|
10
|
+
- output;
|
11
|
+
- echo
|
12
|
+
- error
|
13
|
+
- ">&2;"
|
14
|
+
- exit
|
15
|
+
- 1'
|
16
|
+
stdout: 'output
|
17
|
+
|
18
|
+
'
|
19
|
+
stderr: 'error
|
20
|
+
|
21
|
+
'
|
22
|
+
status: 1
|
23
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,16 @@
|
|
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
|
+
- echo
|
8
|
+
- "'Process"
|
9
|
+
- 'ID:'
|
10
|
+
- 12345'
|
11
|
+
stdout: 'Process ID: 12345
|
12
|
+
|
13
|
+
'
|
14
|
+
stderr: ''
|
15
|
+
status: 0
|
16
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,15 @@
|
|
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
|
+
- echo
|
8
|
+
- "'Status:"
|
9
|
+
- OK'
|
10
|
+
stdout: 'Status: OK
|
11
|
+
|
12
|
+
'
|
13
|
+
stderr: ''
|
14
|
+
status: 0
|
15
|
+
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
|
+
- echo
|
8
|
+
- "'Current"
|
9
|
+
- 'time:'
|
10
|
+
- '2025-01-06'
|
11
|
+
- 10:00:00'
|
12
|
+
stdout: 'Current time: 2025-01-06 10:00:00
|
13
|
+
|
14
|
+
'
|
15
|
+
stderr: ''
|
16
|
+
status: 0
|
17
|
+
recorded_at: '2025-05-01T12:00:00Z'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
first_recorded_at: '2025-06-10T11:00:57-05:00'
|
3
|
+
format_version: '2.0'
|
4
|
+
commands:
|
5
|
+
- command_type: Open3::Capture3
|
6
|
+
args:
|
7
|
+
- echo
|
8
|
+
- test output
|
9
|
+
stdout: 'test output
|
10
|
+
|
11
|
+
'
|
12
|
+
stderr: ''
|
13
|
+
status: 0
|
14
|
+
recorded_at: '2025-06-10T11:00:57-05:00'
|
data/lib/backspin/command.rb
CHANGED
@@ -12,11 +12,7 @@ module Backspin
|
|
12
12
|
@recorded_at = recorded_at
|
13
13
|
|
14
14
|
# Accept either a CommandResult or individual stdout/stderr/status
|
15
|
-
@result = result || CommandResult.new(
|
16
|
-
stdout: stdout || "",
|
17
|
-
stderr: stderr || "",
|
18
|
-
status: status || 0
|
19
|
-
)
|
15
|
+
@result = result || CommandResult.new(stdout: stdout || "", stderr: stderr || "", status: status || 0)
|
20
16
|
end
|
21
17
|
|
22
18
|
def stdout
|
@@ -4,20 +4,27 @@ module Backspin
|
|
4
4
|
# Represents the difference between a recorded command and actual execution
|
5
5
|
# Handles verification and diff generation for a single command
|
6
6
|
class CommandDiff
|
7
|
-
attr_reader :recorded_command, :
|
7
|
+
attr_reader :recorded_command, :actual_command, :matcher
|
8
8
|
|
9
|
-
def initialize(recorded_command:,
|
9
|
+
def initialize(recorded_command:, actual_command:, matcher: nil)
|
10
10
|
@recorded_command = recorded_command
|
11
|
-
@
|
12
|
-
@matcher = matcher
|
11
|
+
@actual_command = actual_command
|
12
|
+
@matcher = normalize_matcher(matcher)
|
13
13
|
end
|
14
14
|
|
15
15
|
# @return [Boolean] true if the command output matches
|
16
16
|
def verified?
|
17
|
-
if
|
18
|
-
|
17
|
+
# First check if method classes match
|
18
|
+
return false unless method_classes_match?
|
19
|
+
|
20
|
+
if matcher.nil?
|
21
|
+
recorded_command.result == actual_command.result
|
22
|
+
elsif matcher.is_a?(Proc) # basic all matcher: lambda { |recorded, actual| ...}
|
23
|
+
matcher.call(recorded_command.to_h, actual_command.to_h)
|
24
|
+
elsif matcher.is_a?(Hash) # matcher: {all: lambda { |recorded, actual| ...}, stdout: lambda { |recorded, actual| ...}}
|
25
|
+
verify_with_hash_matcher
|
19
26
|
else
|
20
|
-
|
27
|
+
raise ArgumentError, "Invalid matcher type: #{matcher.class}"
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
@@ -27,12 +34,17 @@ module Backspin
|
|
27
34
|
|
28
35
|
parts = []
|
29
36
|
|
30
|
-
|
37
|
+
# Check method class mismatch first
|
38
|
+
unless method_classes_match?
|
39
|
+
parts << "Command type mismatch: expected #{recorded_command.method_class.name}, got #{actual_command.method_class.name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
parts << stdout_diff if recorded_command.stdout != actual_command.stdout
|
31
43
|
|
32
|
-
parts << stderr_diff if recorded_command.stderr !=
|
44
|
+
parts << stderr_diff if recorded_command.stderr != actual_command.stderr
|
33
45
|
|
34
|
-
if recorded_command.status !=
|
35
|
-
parts << "Exit status: expected #{recorded_command.status}, got #{
|
46
|
+
if recorded_command.status != actual_command.status
|
47
|
+
parts << "Exit status: expected #{recorded_command.status}, got #{actual_command.status}"
|
36
48
|
end
|
37
49
|
|
38
50
|
parts.join("\n\n")
|
@@ -49,20 +61,90 @@ module Backspin
|
|
49
61
|
|
50
62
|
private
|
51
63
|
|
64
|
+
def method_classes_match?
|
65
|
+
recorded_command.method_class == actual_command.method_class
|
66
|
+
end
|
67
|
+
|
68
|
+
def normalize_matcher(matcher)
|
69
|
+
return nil if matcher.nil?
|
70
|
+
return matcher if matcher.is_a?(Proc)
|
71
|
+
|
72
|
+
raise ArgumentError, "Matcher must be a Proc or Hash, got #{matcher.class}" unless matcher.is_a?(Hash)
|
73
|
+
|
74
|
+
# Validate hash keys and values
|
75
|
+
matcher.each do |key, value|
|
76
|
+
unless %i[all stdout stderr status].include?(key)
|
77
|
+
raise ArgumentError, "Invalid matcher key: #{key}. Must be one of: :all, :stdout, :stderr, :status"
|
78
|
+
end
|
79
|
+
raise ArgumentError, "Matcher for #{key} must be callable (Proc/Lambda)" unless value.respond_to?(:call)
|
80
|
+
end
|
81
|
+
matcher
|
82
|
+
end
|
83
|
+
|
84
|
+
def verify_with_hash_matcher
|
85
|
+
recorded_hash = recorded_command.to_h
|
86
|
+
actual_hash = actual_command.to_h
|
87
|
+
|
88
|
+
all_passed = matcher[:all].nil? || matcher[:all].call(recorded_hash, actual_hash)
|
89
|
+
|
90
|
+
fields_passed = %w[stdout stderr status].all? do |field|
|
91
|
+
field_sym = field.to_sym
|
92
|
+
if matcher[field_sym]
|
93
|
+
matcher[field_sym].call(recorded_hash[field], actual_hash[field])
|
94
|
+
else
|
95
|
+
recorded_hash[field] == actual_hash[field]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
all_passed && fields_passed
|
100
|
+
end
|
101
|
+
|
52
102
|
def failure_reason
|
53
103
|
reasons = []
|
54
|
-
|
55
|
-
|
56
|
-
|
104
|
+
|
105
|
+
# Check method class first
|
106
|
+
unless method_classes_match?
|
107
|
+
reasons << "command type mismatch"
|
108
|
+
return reasons.join(", ")
|
109
|
+
end
|
110
|
+
|
111
|
+
if matcher.nil?
|
112
|
+
reasons << "stdout differs" if recorded_command.stdout != actual_command.stdout
|
113
|
+
reasons << "stderr differs" if recorded_command.stderr != actual_command.stderr
|
114
|
+
reasons << "exit status differs" if recorded_command.status != actual_command.status
|
115
|
+
elsif matcher.is_a?(Hash)
|
116
|
+
recorded_hash = recorded_command.to_h
|
117
|
+
actual_hash = actual_command.to_h
|
118
|
+
|
119
|
+
# Check :all matcher first
|
120
|
+
reasons << ":all matcher failed" if matcher[:all] && !matcher[:all].call(recorded_hash, actual_hash)
|
121
|
+
|
122
|
+
# Check field-specific matchers
|
123
|
+
%w[stdout stderr status].each do |field|
|
124
|
+
field_sym = field.to_sym
|
125
|
+
if matcher[field_sym]
|
126
|
+
unless matcher[field_sym].call(recorded_hash[field], actual_hash[field])
|
127
|
+
reasons << "#{field} custom matcher failed"
|
128
|
+
end
|
129
|
+
elsif recorded_hash[field] != actual_hash[field]
|
130
|
+
# Always check exact equality for fields without matchers
|
131
|
+
reasons << "#{field} differs"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
else
|
135
|
+
# Proc matcher
|
136
|
+
reasons << "custom matcher failed"
|
137
|
+
end
|
138
|
+
|
57
139
|
reasons.join(", ")
|
58
140
|
end
|
59
141
|
|
60
142
|
def stdout_diff
|
61
|
-
"stdout diff:\n#{generate_line_diff(recorded_command.stdout,
|
143
|
+
"stdout diff:\n#{generate_line_diff(recorded_command.stdout, actual_command.stdout)}"
|
62
144
|
end
|
63
145
|
|
64
146
|
def stderr_diff
|
65
|
-
"stderr diff:\n#{generate_line_diff(recorded_command.stderr,
|
147
|
+
"stderr diff:\n#{generate_line_diff(recorded_command.stderr, actual_command.stderr)}"
|
66
148
|
end
|
67
149
|
|
68
150
|
def generate_line_diff(expected, actual)
|
@@ -35,13 +35,11 @@ module Backspin
|
|
35
35
|
def ==(other)
|
36
36
|
return false unless other.is_a?(CommandResult)
|
37
37
|
|
38
|
-
stdout == other.stdout &&
|
39
|
-
stderr == other.stderr &&
|
40
|
-
status == other.status
|
38
|
+
stdout == other.stdout && stderr == other.stderr && status == other.status
|
41
39
|
end
|
42
40
|
|
43
41
|
def inspect
|
44
|
-
"#<Backspin::CommandResult status=#{status} stdout=#{stdout
|
42
|
+
"#<Backspin::CommandResult status=#{status} stdout=#{stdout} stderr=#{stderr}>"
|
45
43
|
end
|
46
44
|
|
47
45
|
private
|
data/lib/backspin/record.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Backspin
|
2
4
|
class RecordFormatError < StandardError; end
|
3
5
|
|
@@ -7,12 +9,37 @@ module Backspin
|
|
7
9
|
FORMAT_VERSION = "2.0"
|
8
10
|
attr_reader :path, :commands, :first_recorded_at
|
9
11
|
|
12
|
+
def self.load_or_create(path)
|
13
|
+
record = new(path)
|
14
|
+
record.load_from_file if File.exist?(path)
|
15
|
+
record
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.load_from_file(path)
|
19
|
+
raise Backspin::RecordNotFoundError unless File.exist?(path)
|
20
|
+
|
21
|
+
record = new(path)
|
22
|
+
record.load_from_file
|
23
|
+
record
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.build_record_path(name)
|
27
|
+
backspin_dir = Backspin.configuration.backspin_dir
|
28
|
+
backspin_dir.mkpath
|
29
|
+
|
30
|
+
File.join(backspin_dir, "#{name}.yml")
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.create(name)
|
34
|
+
path = build_record_path(name)
|
35
|
+
new(path)
|
36
|
+
end
|
37
|
+
|
10
38
|
def initialize(path)
|
11
39
|
@path = path
|
12
40
|
@commands = []
|
13
41
|
@first_recorded_at = nil
|
14
42
|
@playback_index = 0
|
15
|
-
load_from_file if File.exist?(@path)
|
16
43
|
end
|
17
44
|
|
18
45
|
def add_command(command)
|
@@ -35,7 +62,7 @@ module Backspin
|
|
35
62
|
@commands = []
|
36
63
|
@playback_index = 0
|
37
64
|
load_from_file if File.exist?(@path)
|
38
|
-
@playback_index = 0
|
65
|
+
@playback_index = 0 # Reset again after loading to ensure it's at 0
|
39
66
|
end
|
40
67
|
|
41
68
|
def exists?
|
@@ -51,9 +78,7 @@ module Backspin
|
|
51
78
|
end
|
52
79
|
|
53
80
|
def next_command
|
54
|
-
if @playback_index >= @commands.size
|
55
|
-
raise NoMoreRecordingsError, "No more recordings available for replay"
|
56
|
-
end
|
81
|
+
raise NoMoreRecordingsError, "No more recordings available for replay" if @playback_index >= @commands.size
|
57
82
|
|
58
83
|
command = @commands[@playback_index]
|
59
84
|
@playback_index += 1
|
@@ -65,11 +90,7 @@ module Backspin
|
|
65
90
|
@playback_index = 0
|
66
91
|
end
|
67
92
|
|
68
|
-
|
69
|
-
new(path)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
93
|
+
# private
|
73
94
|
|
74
95
|
def load_from_file
|
75
96
|
data = YAML.load_file(@path.to_s)
|
@@ -4,13 +4,14 @@ module Backspin
|
|
4
4
|
# Result object for all Backspin record operations
|
5
5
|
# Provides a consistent interface whether recording, verifying, or playing back
|
6
6
|
class RecordResult
|
7
|
-
attr_reader :output, :
|
7
|
+
attr_reader :output, :commands, :mode, :command_diffs
|
8
|
+
attr_reader :record
|
8
9
|
|
9
|
-
def initialize(output:, mode:,
|
10
|
+
def initialize(output:, mode:, record:, verified: nil, command_diffs: nil)
|
10
11
|
@output = output
|
11
12
|
@mode = mode
|
12
|
-
@
|
13
|
-
@commands = commands
|
13
|
+
@record = record
|
14
|
+
@commands = record.commands
|
14
15
|
@verified = verified
|
15
16
|
@command_diffs = command_diffs || []
|
16
17
|
end
|
@@ -20,8 +21,16 @@ module Backspin
|
|
20
21
|
mode == :record
|
21
22
|
end
|
22
23
|
|
24
|
+
def record_path
|
25
|
+
record.path
|
26
|
+
end
|
27
|
+
|
23
28
|
# @return [Boolean, nil] true/false for verification results, nil for recording
|
24
29
|
def verified?
|
30
|
+
return @verified unless mode == :verify
|
31
|
+
|
32
|
+
return false if command_diffs.size < commands.size
|
33
|
+
|
25
34
|
@verified
|
26
35
|
end
|
27
36
|
|
@@ -33,6 +42,12 @@ module Backspin
|
|
33
42
|
# @return [String, nil] Human-readable error message if verification failed
|
34
43
|
def error_message
|
35
44
|
return nil unless verified? == false
|
45
|
+
|
46
|
+
# Check for command count mismatch first
|
47
|
+
if command_diffs.size < commands.size
|
48
|
+
return "Expected #{commands.size} commands but only #{command_diffs.size} were executed"
|
49
|
+
end
|
50
|
+
|
36
51
|
return "No commands to verify" if command_diffs.empty?
|
37
52
|
|
38
53
|
failed_diffs = command_diffs.reject(&:verified?)
|
@@ -130,24 +145,15 @@ module Backspin
|
|
130
145
|
playback: playback?,
|
131
146
|
stdout: stdout,
|
132
147
|
stderr: stderr,
|
133
|
-
status: status
|
134
|
-
record_path: record_path.to_s
|
148
|
+
status: status
|
135
149
|
}
|
136
150
|
|
137
|
-
# Only include verified if it's not nil
|
138
151
|
hash[:verified] = verified? unless verified?.nil?
|
139
|
-
|
140
|
-
# Only include diff if present
|
141
152
|
hash[:diff] = diff if diff
|
142
|
-
|
143
153
|
# Include number of failed commands if in verify mode
|
144
154
|
hash[:failed_commands] = command_diffs.count { |d| !d.verified? } if mode == :verify && command_diffs.any?
|
145
155
|
|
146
156
|
hash
|
147
157
|
end
|
148
|
-
|
149
|
-
def inspect
|
150
|
-
"#<Backspin::RecordResult mode=#{mode} verified=#{verified?.inspect} status=#{status}>"
|
151
|
-
end
|
152
158
|
end
|
153
159
|
end
|