ruptr 0.1.3

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.
@@ -0,0 +1,425 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pp'
4
+ begin
5
+ require 'diff/lcs'
6
+ require 'diff/lcs/hunk'
7
+ rescue LoadError
8
+ end
9
+
10
+ require_relative 'formatter'
11
+ require_relative 'suite'
12
+ require_relative 'result'
13
+ require_relative 'tty_colors'
14
+ require_relative 'surrogate_exception'
15
+ require_relative 'stringified'
16
+ require_relative 'assertions'
17
+
18
+ module Ruptr
19
+ class Formatter::Plain < Formatter
20
+ self.formatter_name = :plain
21
+
22
+ include Formatter::Colorizing
23
+ include Formatter::Verbosity
24
+
25
+ private
26
+
27
+ def initialize(output, indent: "\t", heading_width: 80, unicode: false, **opts)
28
+ super(**opts)
29
+ @output = output
30
+ @indent = indent
31
+ @heading_width = heading_width
32
+ @unicode = unicode
33
+ end
34
+
35
+ def write(s) = @output << s
36
+
37
+ def newline = @output << "\n"
38
+
39
+ def line(s) = @output << @line_prefix << s << "\n"
40
+
41
+ def lines(s)
42
+ last_line = nil
43
+ s.each_line do |line|
44
+ @output << @line_prefix << line
45
+ last_line = line
46
+ end
47
+ # NOTE: This can misbehave if there are ANSI terminal codes after the newlines!
48
+ @output << "\n" if last_line && !last_line.end_with?("\n")
49
+ end
50
+
51
+ def indent
52
+ if block_given?
53
+ saved = @line_prefix
54
+ @line_prefix += @indent
55
+ begin
56
+ yield
57
+ ensure
58
+ @line_prefix = saved
59
+ end
60
+ else
61
+ @output << @line_prefix
62
+ end
63
+ end
64
+
65
+ def location_path_relative_from = @location_path_relative_from ||= Dir.pwd
66
+
67
+ def render_backtrace_location(loc)
68
+ if loc =~ /\A([^:]*)(:.*)\z/ && $1.start_with?((base_path = location_path_relative_from + '/'))
69
+ rel_path = $1[base_path.length..]
70
+ colorize(base_path + colorize(rel_path, bright: true) + $2, color: :cyan)
71
+ else
72
+ colorize(loc, color: :cyan)
73
+ end
74
+ end
75
+
76
+ def render_exception(heading_prefix, ex)
77
+ line("#{heading_prefix}: " +
78
+ if ex.is_a?(SurrogateException) && ex.original_class_name
79
+ "#{ex.original_class_name} (#{colorize(ex.class.name, color: :magenta)})"
80
+ else
81
+ colorize(ex.class.name, color: case ex
82
+ when SkippedExceptionMixin,
83
+ PendingSkippedMixin then :yellow
84
+ when StandardError then :red
85
+ else :magenta
86
+ end)
87
+ end)
88
+ indent do
89
+ if (msg = if ex.respond_to?(:detailed_message)
90
+ ex.detailed_message(highlight: @colorizer.is_a?(TTYColors::ANSICodes)) || ex.message
91
+ else
92
+ ex.message
93
+ end)
94
+ line("Message:")
95
+ indent do
96
+ lines(msg)
97
+ end
98
+ end
99
+ case ex
100
+ when Assertions::EquivalenceAssertionError
101
+ render_exception_diff(ex.actual, ex.expected)
102
+ when Assertions::EquivalenceRefutationError
103
+ render_exception_value("Actual", ex.actual)
104
+ end
105
+ if ex.backtrace
106
+ line("Backtrace:")
107
+ indent do
108
+ ex.backtrace.each { |loc| line(render_backtrace_location(loc)) }
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def render_stringified_value(label, stringified)
115
+ extra = []
116
+ extra << stringified.original_class_name
117
+ extra << "<#{stringified.string.encoding.name}>" if stringified.originally_a_string?
118
+ m = stringified.stringification_method
119
+ s = stringified.string_for_io(@output)
120
+ unless m || (s.end_with?("\n") && s.match?(/\A[[:print:]\t\n]*\z/))
121
+ s = s.public_send((m = s.count("\n") > 1 ? :pretty_inspect : :inspect))
122
+ end
123
+ extra << "##{m}" if m
124
+ line("#{label} (#{extra.join(' ')}):")
125
+ indent do
126
+ lines(s)
127
+ end
128
+ end
129
+
130
+ def render_exception_value(label, value)
131
+ render_stringified_value(label, Stringified.from(value))
132
+ end
133
+
134
+ def diff_lcs_available?
135
+ Object.const_defined?(:Diff) && Diff.const_defined?(:LCS) && Diff::LCS.const_defined?(:Hunk)
136
+ end
137
+
138
+ def render_exception_diff(actual, expected)
139
+ actual_stringified = Stringified.from(actual)
140
+ expected_stringified = Stringified.from(expected)
141
+
142
+ can_diff = defined?(Diff::LCS::Hunk) &&
143
+ actual_stringified.compatible_with_io?(@output) &&
144
+ expected_stringified.compatible_with_io?(@output) &&
145
+ begin
146
+ actual_lines = actual_stringified.string.lines
147
+ expected_lines = expected_stringified.string.lines
148
+ actual_lines.size > 1 || expected_lines.size > 1
149
+ end
150
+
151
+ if !can_diff || verbose?(1)
152
+ render_stringified_value("Actual", actual_stringified)
153
+ render_stringified_value("Expected", expected_stringified)
154
+ end
155
+
156
+ return unless can_diff
157
+
158
+ diff_colors = if !TTYColors.seems_to_contain_formatting_codes?(actual_stringified.string) &&
159
+ !TTYColors.seems_to_contain_formatting_codes?(expected_stringified.string)
160
+ { '@' => :cyan, '+' => :green, '-' => :red }
161
+ else
162
+ {}
163
+ end
164
+
165
+ line("Difference:")
166
+ indent do
167
+ render_hunk = lambda do |hunk, last = false|
168
+ hunk.diff(:unified, last).each_line(chomp: true) do |s|
169
+ line(colorize(s, color: diff_colors[s[0]]))
170
+ end
171
+ end
172
+ offset = 0
173
+ last_hunk = nil
174
+ Diff::LCS.diff(expected_lines, actual_lines).each do |piece|
175
+ hunk = Diff::LCS::Hunk.new(expected_lines, actual_lines, piece, 3, offset)
176
+ offset = hunk.file_length_difference
177
+ render_hunk.call(last_hunk) if last_hunk && !hunk.merge(last_hunk)
178
+ last_hunk = hunk
179
+ end
180
+ render_hunk.call(last_hunk, true) if last_hunk
181
+ end
182
+ end
183
+
184
+ STATUS_COLORS = { passed: :green, skipped: :yellow, failed: :red, blocked: :magenta }
185
+ .tap { |h| h.default = :magenta }.freeze
186
+
187
+ def render_element_details(te)
188
+ labels = te.path_labels.compact
189
+ labels.each_with_index do |label, index|
190
+ if @unicode
191
+ s = +''
192
+ s << ' ' * (index - 1) + '└─' unless index.zero?
193
+ s << (index == labels.size - 1 ? "─" : index == 0 ? '┌' : "┬") << '╼'
194
+ s << " " << label
195
+ else
196
+ s = ' ' * index + label
197
+ end
198
+ line(s)
199
+ end
200
+ newline
201
+ end
202
+
203
+ def render_result_details(tr)
204
+ unless quiet?(1)
205
+ line("Status: #{colorize(tr.status.to_s.upcase, color: STATUS_COLORS[tr.status])}")
206
+ a = []
207
+ a << "#{'%0.6f' % tr.user_time} user" if tr.user_time
208
+ a << "#{'%0.6f' % tr.system_time} system" if tr.system_time
209
+ line("Processor time: #{a.join(', ')}") unless a.empty?
210
+ line("Assertion count: #{tr.assertions}") if tr.assertions && !tr.assertions.zero?
211
+ newline
212
+ end
213
+
214
+ space = false
215
+ if tr.exception
216
+ each_exception_cause_innermost_first(tr.exception).with_index do |ex, index|
217
+ render_exception("Exception ##{index + 1}", ex)
218
+ end
219
+ space = true
220
+ elsif tr.failed?
221
+ line("Problem:")
222
+ indent do
223
+ line(colorize("Expected test to fail", color: :red))
224
+ end
225
+ space = true
226
+ end
227
+ newline if space
228
+
229
+ space = false
230
+ if tr.captured_stderr?
231
+ line("Captured stderr:")
232
+ indent do
233
+ tr.captured_stderr.each_line do |s|
234
+ line(colorize(s.chomp, color: :yellow))
235
+ end
236
+ end
237
+ space = true
238
+ end
239
+ if tr.captured_stdout?
240
+ line("Captured stdout:")
241
+ indent do
242
+ tr.captured_stdout.each_line do |s|
243
+ line(colorize(s.chomp, color: :blue))
244
+ end
245
+ end
246
+ space = true
247
+ end
248
+ newline if space
249
+ end
250
+
251
+ def format_heading(title)
252
+ w = @heading_width
253
+ if @unicode
254
+ ["╔#{'═' * (w - 2)}╗", "║#{title.center(w - 2)}║", "╚#{'═' * (w - 2)}╝"]
255
+ else
256
+ b = 4
257
+ ['#' * w, "#{'#' * b}#{title.center(w - b * 2)}#{'#' * b}", '#' * w]
258
+ end
259
+ end
260
+
261
+ def heading(title, **style)
262
+ format_heading(title).each { |s| line(colorize(s, **style)) }
263
+ @heading_count += 1
264
+ newline unless @unicode
265
+ end
266
+
267
+ def render_element(te, tr)
268
+ case
269
+ when tr.passed?
270
+ @total_passed_cases += 1 if te.test_case?
271
+ @total_passed_groups += 1 if te.test_group?
272
+ if tr.captured_stderr?
273
+ @total_passed_warned_cases += 1 if te.test_case?
274
+ @total_passed_warned_groups += 1 if te.test_group?
275
+ return if quiet?(2)
276
+ title = "WARNING ##{@warned_index}"
277
+ @warned_index += 1
278
+ color = :yellow
279
+ else
280
+ return unless verbose?(3)
281
+ title = "PASSED ##{@passed_index}"
282
+ @passed_index += 1
283
+ color = :green
284
+ end
285
+ when tr.skipped?
286
+ @total_skipped_cases += 1 if te.test_case?
287
+ @total_skipped_groups += 1 if te.test_group?
288
+ if tr.exception.is_a?(PendingSkippedMixin)
289
+ @total_skipped_pending_cases += 1 if te.test_case?
290
+ @total_skipped_pending_groups += 1 if te.test_group?
291
+ return unless verbose?(2)
292
+ title = "PENDING ##{@pending_index}"
293
+ @pending_index += 1
294
+ else
295
+ return unless verbose?(2)
296
+ title = "SKIPPED ##{@skipped_index}"
297
+ @skipped_index += 1
298
+ end
299
+ color = :yellow
300
+ when tr.blocked?
301
+ @total_blocked_cases += 1 if te.test_case?
302
+ @total_blocked_groups += 1 if te.test_group?
303
+ return unless verbose?(2)
304
+ title = "BLOCKED ##{@blocked_index}"
305
+ @blocked_index += 1
306
+ color = :magenta
307
+ when tr.failed?
308
+ @total_failed_cases += 1 if te.test_case?
309
+ @total_failed_groups += 1 if te.test_group?
310
+ return if quiet?(2)
311
+ title = "FAILURE ##{@failed_index}"
312
+ @failed_index += 1
313
+ color = :red
314
+ else
315
+ @total_failed_cases += 1 if te.test_case?
316
+ @total_failed_groups += 1 if te.test_group?
317
+ return if quiet?(2)
318
+ title = "UNKNOWN ##{@unknown_index}"
319
+ @unknown_index += 1
320
+ color = :magenta
321
+ end
322
+ heading(title, color:)
323
+ render_element_details(te)
324
+ render_result_details(tr)
325
+ end
326
+
327
+ def render_timing(fields)
328
+ return unless verbose?(1)
329
+ a = [
330
+ *["User time", "System time", "Real time"],
331
+ *%i[test_files_load_user_time test_files_load_system_time test_files_load_real_time
332
+ test_suite_run_user_time test_suite_run_system_time test_suite_run_real_time
333
+ overall_user_time overall_system_time overall_real_time].map { |s| fields[s] || Float::NAN },
334
+ ]
335
+ lines(format(<<~PRINTF, *a))
336
+ %14s %14s %14s
337
+ Loading test files: %14.6f %14.6f %14.6f
338
+ Running test suite: %14.6f %14.6f %14.6f
339
+ Overall: %14.6f %14.6f %14.6f
340
+
341
+ PRINTF
342
+ end
343
+
344
+ def render_summary
345
+ return if quiet?(3)
346
+
347
+ s = +''
348
+ s << "Ran #{@total_test_cases} test cases"
349
+ s << " with #{@total_assertions} assertions" unless @total_assertions.zero?
350
+ s << ": "
351
+
352
+ s << colorize("#{@total_passed_cases} passed",
353
+ color: if @total_failed_cases.zero? &&
354
+ @total_failed_groups.zero? &&
355
+ !@total_passed_cases.zero?
356
+ :green
357
+ end)
358
+ a = []
359
+ a << colorize("#{@total_passed_warned_cases} warned", color: :yellow) \
360
+ unless @total_passed_warned_cases.zero?
361
+ a << colorize("#{@total_passed_warned_groups} warned test groups", color: :yellow) \
362
+ unless @total_passed_warned_groups.zero?
363
+ s << " (#{a.join(', ')})" unless a.empty?
364
+
365
+ s << ", " << colorize("#{@total_skipped_cases} skipped", color: :yellow) \
366
+ unless @total_skipped_cases.zero?
367
+ a = []
368
+ a << colorize("#{@total_skipped_pending_cases} pending", color: :yellow) \
369
+ unless @total_skipped_pending_cases.zero?
370
+ s << " (#{a.join(', ')})" unless a.empty?
371
+
372
+ s << ", " << colorize("#{@total_failed_cases} failed", color: :red) \
373
+ unless @total_failed_cases.zero?
374
+
375
+ a = []
376
+ a << colorize("#{@total_failed_groups} failed test groups", color: :red) \
377
+ unless @total_failed_groups.zero?
378
+ a << colorize("#{@total_skipped_groups} skipped test groups", color: :yellow) \
379
+ unless @total_skipped_groups.zero?
380
+ a << colorize("#{@total_blocked_groups} blocked test groups", color: :magenta) \
381
+ unless @total_blocked_groups.zero?
382
+ unless @total_blocked_cases.zero? && a.empty?
383
+ s << ", " << colorize("#{@total_blocked_cases} blocked", color: :magenta)
384
+ s << " (#{a.join(', ')})" unless a.empty?
385
+ end
386
+
387
+ line(s << ".")
388
+ end
389
+
390
+ public
391
+
392
+ def begin_plan(_)
393
+ @line_prefix = ''
394
+ @heading_count = 0
395
+ @unknown_index = @failed_index = @blocked_index =
396
+ @skipped_index = @pending_index =
397
+ @passed_index = @warned_index = 1
398
+ @total_test_cases = @total_test_groups = 0
399
+ @total_failed_cases = @total_blocked_cases =
400
+ @total_skipped_cases = @total_skipped_pending_cases =
401
+ @total_passed_cases = @total_passed_warned_cases = 0
402
+ @total_failed_groups = @total_blocked_groups =
403
+ @total_skipped_groups = @total_skipped_pending_groups =
404
+ @total_passed_groups = @total_passed_warned_groups = 0
405
+ @total_assertions = 0
406
+ end
407
+
408
+ def finish_plan(fields)
409
+ unless @heading_count.zero?
410
+ newline
411
+ line('=' * @heading_width)
412
+ newline
413
+ end
414
+ render_timing(fields)
415
+ render_summary
416
+ end
417
+
418
+ def finish_element(te, tr)
419
+ @total_test_cases += 1 if te.test_case?
420
+ @total_test_groups += 1 if te.test_group?
421
+ @total_assertions += tr.assertions
422
+ render_element(te, tr)
423
+ end
424
+ end
425
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'suite'
4
+ require_relative 'tty_colors'
5
+
6
+ module Ruptr
7
+ class Progress
8
+ include Sink
9
+
10
+ def initialize(output)
11
+ @output = output
12
+ @colorizer = TTYColors.for(output)
13
+ end
14
+
15
+ def begin_plan(fields)
16
+ @planned_test_case_count = fields[:planned_test_case_count]
17
+ progress_start
18
+ end
19
+
20
+ def finish_plan(_) = progress_end
21
+ def finish_element(te, tr) = progress_result(te, tr)
22
+
23
+ class Dots < self
24
+ def progress_result(_te, tr)
25
+ @output << case
26
+ when tr.passed? then @colorizer.wrap('.', color: :green)
27
+ when tr.skipped? then @colorizer.wrap('_', color: :yellow)
28
+ when tr.failed? then @colorizer.wrap('!', color: :red)
29
+ else @colorizer.wrap('?', color: :magenta)
30
+ end
31
+ end
32
+
33
+ def progress_end
34
+ @output << "\n"
35
+ end
36
+ end
37
+
38
+ class StatusLine < self
39
+ def progress_start
40
+ @processor_time = 0
41
+ @test_cases = @assertions = 0
42
+ @passed = @skipped = @failed = @blocked = 0
43
+ @last_line_width = 0
44
+ @twirls = @last_twirl_processor_time = 0
45
+ end
46
+
47
+ private def colorize(s, **opts) = @colorizer.wrap(s, **opts)
48
+
49
+ CHARS = %w[\\ | / -].freeze
50
+
51
+ def progress_result(te, tr)
52
+ @processor_time += tr.processor_time || 0
53
+ @assertions += tr.assertions || 0
54
+ if te.test_case?
55
+ @test_cases += 1
56
+ @passed += 1 if tr.passed?
57
+ @skipped += 1 if tr.skipped?
58
+ @failed += 1 if tr.failed?
59
+ @blocked += 1 if tr.blocked?
60
+ end
61
+
62
+ line = +"\r"
63
+ line << "Running tests... "
64
+ if (@processor_time - @last_twirl_processor_time) >= 0.25
65
+ @twirls += 1
66
+ @last_twirl_processor_time = @processor_time
67
+ end
68
+ line << CHARS[@twirls % CHARS.size]
69
+ line << " ptime:" << colorize('%.03fs' % @processor_time, color: :cyan)
70
+ line << " cases:" << colorize(@test_cases.to_s, color: :cyan)
71
+ if (n = @planned_test_case_count)
72
+ line << "/#{n}"
73
+ line << " (#{@test_cases * 100 / n}%)" unless n.zero?
74
+ end
75
+ line << " asserts:" << colorize(@assertions.to_s, color: :cyan) unless @assertions.zero?
76
+ line << " passed:" << colorize(@passed.to_s, color: :green) unless @passed.zero?
77
+ line << " skipped:" << colorize(@skipped.to_s, color: :yellow) unless @skipped.zero?
78
+ line << " failed:" << colorize(@failed.to_s, color: :red) unless @failed.zero?
79
+ line << " blocked:" << colorize(@blocked.to_s, color: :magenta) unless @blocked.zero?
80
+ line << ' '
81
+ width = line.length - 1
82
+ if width < @last_line_width
83
+ n = @last_line_width - width + 1
84
+ @last_line_width = width
85
+ line << ' ' * n << "\b" * n
86
+ else
87
+ @last_line_width = width
88
+ end
89
+ @output << line
90
+ end
91
+
92
+ def progress_end
93
+ n = @last_line_width
94
+ @output << ("\b" * n + ' ' * (n + 1) + "\b" * (n + 1))
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/tasklib'
4
+
5
+ require_relative 'main'
6
+
7
+ module Ruptr
8
+ class RakeTask < Rake::TaskLib
9
+ def initialize(name = :ruptr, &)
10
+ super()
11
+ task(name) do
12
+ main = Main.new
13
+ instance_exec(main, &) if block_given?
14
+ main.run
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'suite'
4
+ require_relative 'result'
5
+ require_relative 'sink'
6
+
7
+ module Ruptr
8
+ class Report
9
+ def initialize
10
+ @results = {}
11
+ @failed = false
12
+ @total_assertions = 0
13
+ @total_test_cases = 0
14
+ @total_test_cases_by_status = Hash.new(0)
15
+ @total_test_groups = 0
16
+ @total_test_groups_by_status = Hash.new(0)
17
+ end
18
+
19
+ attr_reader :total_assertions,
20
+ :total_test_cases,
21
+ :total_test_groups
22
+
23
+ def failed? = @failed
24
+ def passed? = !failed?
25
+
26
+ TestResult::VALID_STATUSES.each do |status|
27
+ define_method(:"total_#{status}_test_cases") { @total_test_cases_by_status[status] }
28
+ define_method(:"total_#{status}_test_groups") { @total_test_groups_by_status[status] }
29
+ end
30
+
31
+ def each_test_element_result(klass = TestElement, &)
32
+ return to_enum __method__, klass unless block_given?
33
+ @results.each { |te, tr| yield te, tr if te.is_a?(klass) }
34
+ end
35
+
36
+ def each_test_case_result(&) = each_test_element_result(TestCase, &)
37
+ def each_test_group_result(&) = each_test_element_result(TestGroup, &)
38
+
39
+ def [](k) = @results[k]
40
+
41
+ def []=(k, v)
42
+ raise ArgumentError unless k.is_a?(Symbol)
43
+ @results[k] = v
44
+ end
45
+
46
+ def bump(k, n = 1)
47
+ raise ArgumentError unless k.is_a?(Symbol)
48
+ @results[k] = (v = @results[k]).nil? ? n : v + n
49
+ end
50
+
51
+ def record_result(te, tr)
52
+ raise ArgumentError, "result already recorded" if @results[te]
53
+ case
54
+ when te.test_case?
55
+ @total_test_cases += 1
56
+ @total_test_cases_by_status[tr.status] += 1
57
+ when te.test_group?
58
+ @total_test_groups += 1
59
+ @total_test_groups_by_status[tr.status] += 1
60
+ else
61
+ raise ArgumentError
62
+ end
63
+ @total_assertions += tr.assertions || 0
64
+ @failed ||= tr.failed?
65
+ @results[te] = tr
66
+ end
67
+
68
+ def freeze
69
+ @results.freeze
70
+ @total_test_cases_by_status.freeze
71
+ @total_test_groups_by_status.freeze
72
+ super
73
+ end
74
+
75
+ def emit(sink)
76
+ sink.begin_plan({ planned_test_case_count: @total_test_cases })
77
+ each_test_group_result { |tg, tr| sink.submit_group(tg, tr) }
78
+ each_test_case_result { |tc, tr| sink.submit_case(tc, tr) }
79
+ sink.finish_plan(@results.filter { |k, _v| k.is_a?(Symbol) })
80
+ end
81
+
82
+ class Builder
83
+ include Sink
84
+
85
+ def initialize(report = Report.new)
86
+ @report = report
87
+ end
88
+
89
+ attr_accessor :report
90
+
91
+ def begin_plan(fields)
92
+ fields.each { |k, v| report[k] = v }
93
+ end
94
+
95
+ def finish_plan(fields)
96
+ fields.each { |k, v| report[k] = v }
97
+ end
98
+
99
+ def finish_element(te, tr)
100
+ report.record_result(te, tr)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'suite'
4
+ require_relative 'sink'
5
+
6
+ module Ruptr
7
+ class TestResult
8
+ # NOTE: Objects of this class will get dumped/loaded with Marshal.
9
+
10
+ VALID_STATUSES = %i[passed skipped failed blocked].freeze
11
+
12
+ def initialize(status,
13
+ assertions: 0, user_time: nil, system_time: nil, exception: nil,
14
+ captured_stdout: nil, captured_stderr: nil)
15
+ raise ArgumentError unless VALID_STATUSES.include?(status)
16
+ raise ArgumentError if exception && status == :passed
17
+ @status = status
18
+ @assertions = assertions
19
+ @captured_stdout = captured_stdout
20
+ @captured_stderr = captured_stderr
21
+ @user_time = user_time
22
+ @system_time = system_time
23
+ @exception = exception
24
+ end
25
+
26
+ attr_reader :status, :assertions, :user_time, :system_time, :exception, :captured_stdout, :captured_stderr
27
+
28
+ def passed? = @status == :passed
29
+ def skipped? = @status == :skipped
30
+ def failed? = @status == :failed
31
+ def blocked? = @status == :blocked
32
+
33
+ def processor_time = !@user_time ? @system_time : !@system_time ? @user_time : @user_time + @system_time
34
+
35
+ def captured_stdout? = @captured_stdout && !@captured_stdout.empty?
36
+ def captured_stderr? = @captured_stderr && !@captured_stderr.empty?
37
+ end
38
+ end