rspec-tap-formatters 0.1.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.
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/base_formatter'
4
+ require_relative 'printer'
5
+ require_relative 'test_stats'
6
+
7
+ module RSpec
8
+ module TAP
9
+ module Formatters
10
+ # Default TAP formatter
11
+ class Default < RSpec::Core::Formatters::BaseFormatter
12
+ # List of subscribed notifications
13
+ NOTIFICATIONS = %i[
14
+ seed
15
+ start
16
+ start_dump
17
+ example_group_started
18
+ example_group_finished
19
+ example_started
20
+ example_passed
21
+ example_failed
22
+ example_pending
23
+ message
24
+ dump_failures
25
+ dump_pending
26
+ dump_summary
27
+ ].freeze
28
+
29
+ RSpec::Core::Formatters.register(self, *NOTIFICATIONS)
30
+
31
+ # Constructor
32
+ #
33
+ # @param output [StringIO, File] output stream
34
+ def initialize(output)
35
+ super
36
+
37
+ @printer = Printer.new(output)
38
+ @test_stats = TestStats.new
39
+ @seed = nil
40
+ @level = 0
41
+ @example_number = 0
42
+ end
43
+
44
+ # Seed notification
45
+ #
46
+ # @param notification [SeedNotification]
47
+ def seed(notification)
48
+ @seed = notification.seed if notification.seed_used?
49
+ end
50
+
51
+ # Start notification
52
+ #
53
+ # @param notification [StartNotification]
54
+ def start(notification)
55
+ super
56
+
57
+ @printer.start_output
58
+ end
59
+
60
+ # Execution finished notification
61
+ #
62
+ # @param _notification [NullNotification]
63
+ def start_dump(_notification)
64
+ @printer.example_progress_dump
65
+ end
66
+
67
+ # Example group start notification
68
+ #
69
+ # @param notification [GroupNotification]
70
+ def example_group_started(notification)
71
+ @printer.group_start_output(notification, @level)
72
+
73
+ @level += 1
74
+ @example_number = 0
75
+ end
76
+
77
+ # Example group finish notification
78
+ #
79
+ # @param notification [GroupNotification]
80
+ def example_group_finished(notification)
81
+ @printer.group_finished_output(
82
+ @test_stats.data[notification.group.metadata[:line_number]],
83
+ @level
84
+ )
85
+
86
+ @level -= 1 if @level.positive?
87
+ @test_stats = TestStats.new if @level.zero?
88
+ end
89
+
90
+ # Example start notification
91
+ #
92
+ # @param _notification [ExampleNotification]
93
+ def example_started(_notification)
94
+ @example_number += 1
95
+ end
96
+
97
+ # Passing example notification
98
+ #
99
+ # @param notification [ExampleNotification]
100
+ def example_passed(notification)
101
+ @test_stats.populate(notification, 1)
102
+ @printer.example_progress_output(:success)
103
+ @printer.success_output(
104
+ notification.example.description.strip,
105
+ @example_number,
106
+ @level
107
+ )
108
+ end
109
+
110
+ # Failing example notification
111
+ #
112
+ # @param notification [FailedExampleNotification]
113
+ def example_failed(notification)
114
+ @test_stats.populate(notification, 2)
115
+ @printer.example_progress_output(:failure)
116
+ @printer.failure_output(
117
+ notification.example.description.strip,
118
+ @example_number,
119
+ @level
120
+ )
121
+ @printer.failure_reason_output(notification, @level + 1)
122
+ end
123
+
124
+ # Pending example notification
125
+ #
126
+ # @param notification [PendingExampleFailedAsExpectedNotification
127
+ # , SkippedExampleException]
128
+ def example_pending(notification)
129
+ @test_stats.populate(notification, 3)
130
+ @printer.example_progress_output(:pending)
131
+ @printer.pending_output(
132
+ notification,
133
+ notification.example.description.strip,
134
+ @example_number,
135
+ @level
136
+ )
137
+ end
138
+
139
+ # Failure outside of example notification
140
+ #
141
+ # @param notification [MessageNotification]
142
+ def message(notification)
143
+ @printer.message_output(notification)
144
+ end
145
+
146
+ # Failure examples notification
147
+ #
148
+ # @param notification [ExamplesNotification]
149
+ def dump_failures(notification)
150
+ @printer.store_failed_examples_summary(notification)
151
+ end
152
+
153
+ # Pending examples notification
154
+ #
155
+ # @param notification [ExamplesNotification]
156
+ def dump_pending(notification)
157
+ @printer.store_pending_examples_summary(notification)
158
+ end
159
+
160
+ # Examples summary notification
161
+ #
162
+ # @param notification [SummaryNotification]
163
+ def dump_summary(notification)
164
+ @printer.summary_output(notification, @seed)
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/base_formatter'
4
+ require_relative 'printer'
5
+ require_relative 'test_stats'
6
+
7
+ module RSpec
8
+ module TAP
9
+ module Formatters
10
+ # Flat TAP formatter
11
+ class Flat < RSpec::Core::Formatters::BaseFormatter
12
+ # List of subscribed notifications
13
+ NOTIFICATIONS = %i[
14
+ seed
15
+ start
16
+ start_dump
17
+ example_started
18
+ example_passed
19
+ example_failed
20
+ example_pending
21
+ message
22
+ dump_failures
23
+ dump_pending
24
+ dump_summary
25
+ ].freeze
26
+
27
+ RSpec::Core::Formatters.register(self, *NOTIFICATIONS)
28
+
29
+ # Constructor
30
+ #
31
+ # @param output [StringIO, File] output stream
32
+ def initialize(output)
33
+ super
34
+
35
+ @printer = Printer.new(output)
36
+ @seed = nil
37
+ @example_number = 0
38
+ end
39
+
40
+ # Seed notification
41
+ #
42
+ # @param notification [SeedNotification]
43
+ def seed(notification)
44
+ @seed = notification.seed if notification.seed_used?
45
+ end
46
+
47
+ # Start notification
48
+ #
49
+ # @param notification [StartNotification]
50
+ def start(notification)
51
+ super
52
+
53
+ @printer.start_output
54
+ end
55
+
56
+ # Execution finished notification
57
+ #
58
+ # @param _notification [NullNotification]
59
+ def start_dump(_notification)
60
+ @printer.example_progress_dump
61
+ end
62
+
63
+ # Example start notification
64
+ #
65
+ # @param _notification [ExampleNotification]
66
+ def example_started(_notification)
67
+ @example_number += 1
68
+ end
69
+
70
+ # Passing example notification
71
+ #
72
+ # @param notification [ExampleNotification]
73
+ def example_passed(notification)
74
+ @printer.example_progress_output(:success)
75
+ @printer.success_output(
76
+ notification.example.full_description.strip,
77
+ @example_number,
78
+ 0
79
+ )
80
+ end
81
+
82
+ # Failing example notification
83
+ #
84
+ # @param notification [FailedExampleNotification]
85
+ def example_failed(notification)
86
+ @printer.example_progress_output(:failure)
87
+ @printer.failure_output(
88
+ notification.example.full_description.strip,
89
+ @example_number,
90
+ 0
91
+ )
92
+ @printer.failure_reason_output(notification, 1)
93
+ end
94
+
95
+ # Pending example notification
96
+ #
97
+ # @param notification [PendingExampleFailedAsExpectedNotification
98
+ # , SkippedExampleException]
99
+ def example_pending(notification)
100
+ @printer.example_progress_output(:pending)
101
+ @printer.pending_output(
102
+ notification,
103
+ notification.example.full_description.strip,
104
+ @example_number,
105
+ 0
106
+ )
107
+ end
108
+
109
+ # Failure outside of example notification
110
+ #
111
+ # @param notification [MessageNotification]
112
+ def message(notification)
113
+ @printer.message_output(notification)
114
+ end
115
+
116
+ # Failure examples notification
117
+ #
118
+ # @param notification [ExamplesNotification]
119
+ def dump_failures(notification)
120
+ @printer.store_failed_examples_summary(notification)
121
+ end
122
+
123
+ # Pending examples notification
124
+ #
125
+ # @param notification [ExamplesNotification]
126
+ def dump_pending(notification)
127
+ @printer.store_pending_examples_summary(notification)
128
+ end
129
+
130
+ # Examples summary notification
131
+ #
132
+ # @param notification [SummaryNotification]
133
+ def dump_summary(notification)
134
+ @printer.summary_output(notification, @seed)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/base_formatter'
4
+ require_relative 'printer'
5
+ require_relative 'test_stats'
6
+
7
+ module RSpec
8
+ module TAP
9
+ module Formatters
10
+ # Flat compact TAP formatter
11
+ class FlatCompact < RSpec::Core::Formatters::BaseFormatter
12
+ # List of subscribed notifications
13
+ NOTIFICATIONS = %i[
14
+ seed
15
+ start
16
+ start_dump
17
+ example_started
18
+ example_passed
19
+ example_failed
20
+ example_pending
21
+ message
22
+ dump_failures
23
+ dump_pending
24
+ dump_summary
25
+ ].freeze
26
+
27
+ RSpec::Core::Formatters.register(self, *NOTIFICATIONS)
28
+
29
+ # Constructor
30
+ #
31
+ # @param output [StringIO, File] output stream
32
+ def initialize(output)
33
+ super
34
+
35
+ @printer = Printer.new(output)
36
+ @seed = nil
37
+ @example_number = 0
38
+ end
39
+
40
+ # Seed notification
41
+ #
42
+ # @param notification [SeedNotification]
43
+ def seed(notification)
44
+ @seed = notification.seed if notification.seed_used?
45
+ end
46
+
47
+ # Start notification
48
+ #
49
+ # @param notification [StartNotification]
50
+ def start(notification)
51
+ super
52
+
53
+ @printer.start_output
54
+ end
55
+
56
+ # Execution finished notification
57
+ #
58
+ # @param _notification [NullNotification]
59
+ def start_dump(_notification)
60
+ @printer.example_progress_dump
61
+ end
62
+
63
+ # Example start notification
64
+ #
65
+ # @param _notification [ExampleNotification]
66
+ def example_started(_notification)
67
+ @example_number += 1
68
+ end
69
+
70
+ # Passing example notification
71
+ #
72
+ # @param notification [ExampleNotification]
73
+ def example_passed(notification)
74
+ @printer.example_progress_output(:success)
75
+ @printer.success_output(
76
+ notification.example.full_description.strip,
77
+ @example_number,
78
+ 0
79
+ )
80
+ end
81
+
82
+ # Failing example notification
83
+ #
84
+ # @param notification [FailedExampleNotification]
85
+ def example_failed(notification)
86
+ @printer.example_progress_output(:failure)
87
+ @printer.failure_output(
88
+ notification.example.full_description.strip,
89
+ @example_number,
90
+ 0
91
+ )
92
+ end
93
+
94
+ # Pending example notification
95
+ #
96
+ # @param notification [PendingExampleFailedAsExpectedNotification
97
+ # , SkippedExampleException]
98
+ def example_pending(notification)
99
+ @printer.example_progress_output(:pending)
100
+ @printer.pending_output(
101
+ notification,
102
+ notification.example.full_description.strip,
103
+ @example_number,
104
+ 0
105
+ )
106
+ end
107
+
108
+ # Failure outside of example notification
109
+ #
110
+ # @param notification [MessageNotification]
111
+ def message(notification)
112
+ @printer.message_output(notification)
113
+ end
114
+
115
+ # Failure examples notification
116
+ #
117
+ # @param notification [ExamplesNotification]
118
+ def dump_failures(notification)
119
+ @printer.store_failed_examples_summary(notification)
120
+ end
121
+
122
+ # Pending examples notification
123
+ #
124
+ # @param notification [ExamplesNotification]
125
+ def dump_pending(notification)
126
+ @printer.store_pending_examples_summary(notification)
127
+ end
128
+
129
+ # Examples summary notification
130
+ #
131
+ # @param notification [SummaryNotification]
132
+ def dump_summary(notification)
133
+ @printer.summary_output(notification, @seed)
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,445 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/console_codes'
4
+ require_relative 'core_ext/hash'
5
+ require_relative 'core_ext/string'
6
+
7
+ module RSpec
8
+ module TAP
9
+ module Formatters
10
+ # TAP report printer
11
+ class Printer
12
+ # Example status progress report characters
13
+ EXAMPLE_PROGRESS = {
14
+ success: '.',
15
+ failure: 'F',
16
+ pending: '*'
17
+ }.freeze
18
+
19
+ # Constructor
20
+ #
21
+ # @param output [StringIO, File] output stream
22
+ def initialize(output)
23
+ @output = output
24
+ @write_to_file = output.is_a?(File)
25
+ @display_colors = !@write_to_file
26
+ @force_colors = false
27
+
28
+ @bailed_out = false
29
+
30
+ @failed_examples = ''
31
+ @pending_examples = ''
32
+ end
33
+
34
+ # Handler for formatter +start+ notification.
35
+ def start_output
36
+ return if @bailed_out
37
+
38
+ @output.puts('TAP version 13')
39
+ end
40
+
41
+ # Handler for formatter +example_group_started+ notification.
42
+ #
43
+ # @param notification [ExampleNotification] example notification
44
+ # @param padding [Integer] indentation width
45
+ def group_start_output(notification, padding)
46
+ description = notification.group.description.strip
47
+
48
+ line =
49
+ if padding.zero?
50
+ "#{indentation(padding)}# test: #{description} {"
51
+ else
52
+ "#{indentation(padding)}# group: #{description} {"
53
+ end
54
+
55
+ @output.puts(colored_line(line, :detail))
56
+ end
57
+
58
+ # Handler for formatter +example_group_finished+ notification.
59
+ #
60
+ # @param test_stats [Array<Integer>] stats for the example group
61
+ # @param padding [Integer] indentation width
62
+ #
63
+ # @see stats_output
64
+ def group_finished_output(test_stats, padding)
65
+ @output.puts("#{indentation(padding)}1..#{test_stats[0]}")
66
+ stats_output(test_stats, padding)
67
+ @output.puts(colored_line("#{indentation(padding - 1)}}", :detail))
68
+ end
69
+
70
+ # Prints example progress when writing to a file.
71
+ # +.+ for passing, +F+ for failing, and `*` for pending example.
72
+ #
73
+ # @param status [Symbol] example status
74
+ def example_progress_output(status)
75
+ return unless @write_to_file
76
+
77
+ @force_colors = RSpec.configuration.color_enabled?
78
+
79
+ $stdout.print(colored_line(EXAMPLE_PROGRESS[status], status))
80
+
81
+ @force_colors = false
82
+ end
83
+
84
+ # Handler for formatter +start_dump+ notification.
85
+ def example_progress_dump
86
+ $stdout.puts if @write_to_file
87
+ end
88
+
89
+ # Handler for formatter +example_passed+ notification.
90
+ #
91
+ # @param description [String] example description
92
+ # @param example_number [Integer] example number
93
+ # @param padding [Integer] indentation width
94
+ def success_output(description, example_number, padding)
95
+ line = "ok #{example_number} - #{description}"
96
+ line = colored_line("#{indentation(padding)}#{line}", :success)
97
+
98
+ @output.puts(line)
99
+ end
100
+
101
+ # Handler for formatter +example_failed+ notification.
102
+ #
103
+ # @param description [String] example description
104
+ # @param example_number [Integer] example number
105
+ # @param padding [Integer] indentation width
106
+ def failure_output(description, example_number, padding)
107
+ line = "not ok #{example_number} - #{description}"
108
+ line = colored_line("#{indentation(padding)}#{line}", :failure)
109
+
110
+ @output.puts(line)
111
+ end
112
+
113
+ # Prints failure reason YAML block
114
+ # The aggregate failures are not reported for RSpec version
115
+ # before +3.3.0+.
116
+ #
117
+ # @param notification [ExampleNotification] example notification
118
+ # @param padding [Integer] indentation width
119
+ def failure_reason_output(notification, padding)
120
+ rspec_version = Gem::Version.new(RSpec::Core::Version::STRING)
121
+ reason =
122
+ if rspec_version >= Gem::Version.new('3.3.0')
123
+ failure_reason_for_and_post_3_3_0(notification)
124
+ else
125
+ failure_reason_pre_3_3_0(notification)
126
+ end
127
+
128
+ return if reason.empty?
129
+
130
+ failure_diagnostics_output(
131
+ {
132
+ location: notification.example.metadata[:location]
133
+ }.merge(reason).stringify_keys,
134
+ padding
135
+ )
136
+ end
137
+
138
+ # Handler for formatter +example_pending+ notification.
139
+ #
140
+ # @param description [String] example description
141
+ # @param example_number [Integer] example number
142
+ # @param padding [Integer] indentation width
143
+ def pending_output(notification, description, example_number, padding)
144
+ directive = pending_example_directive(notification)
145
+ line = "ok #{example_number} - #{description} # #{directive}"
146
+ line = colored_line("#{indentation(padding)}#{line}", :pending)
147
+
148
+ @output.puts(line)
149
+ end
150
+
151
+ # Handler for formatter +message+ notification.
152
+ #
153
+ # @param notification [ExampleNotification] example notification
154
+ def message_output(notification)
155
+ return if @bailed_out
156
+ return unless RSpec.world.non_example_failure
157
+
158
+ bailed_out_message_output(notification)
159
+
160
+ @bailed_out = true
161
+ end
162
+
163
+ # Handler for formatter +dump_failures+ notification.
164
+ #
165
+ # @param notification [ExampleNotification] example notification
166
+ def store_failed_examples_summary(notification)
167
+ return if notification.failure_notifications.empty?
168
+
169
+ @failed_examples = notification.fully_formatted_failed_examples
170
+ end
171
+
172
+ # Handler for formatter +dump_pending+ notification.
173
+ #
174
+ # @param notification [ExampleNotification] example notification
175
+ def store_pending_examples_summary(notification)
176
+ return if notification.pending_examples.empty?
177
+
178
+ @pending_examples = notification.fully_formatted_pending_examples
179
+ end
180
+
181
+ # Handler for formatter +dump_summary+ notification.
182
+ #
183
+ # @param notification [ExampleNotification] example notification
184
+ # @param seed [Integer] used seed
185
+ def summary_output(notification, seed)
186
+ return if @bailed_out
187
+
188
+ @output.puts("1..#{notification.examples.size}")
189
+
190
+ return if notification.examples.size.zero?
191
+
192
+ execution_stats_output(notification)
193
+
194
+ @output.puts("# seed: #{seed}") if seed
195
+
196
+ dump_failed_examples_summary if @failed_examples.present?
197
+ dump_pending_examples_summary if @pending_examples.present?
198
+ end
199
+
200
+ private
201
+
202
+ # Provides failure reason for RSpec version before +3.3.0+.
203
+ #
204
+ # @param notification [ExampleNotification] example notification
205
+ # @return [Hash<Symbol, String>] +error+ and +backtrace+ for
206
+ # the YAML block
207
+ def failure_reason_pre_3_3_0(notification)
208
+ failure_error_and_backtrace(notification)
209
+ end
210
+
211
+ # Provides failure reason for RSpec version after +3.3.0+.
212
+ #
213
+ # @param notification [ExampleNotification] example notification
214
+ # @return [Hash<Symbol, String>] +error+ and +backtrace+
215
+ # for the YAML block
216
+ def failure_reason_for_and_post_3_3_0(notification)
217
+ case notification.example.execution_result.exception
218
+ when RSpec::Expectations::MultipleExpectationsNotMetError
219
+ multiple_failures_error_and_backtrace(notification)
220
+ else
221
+ failure_error_and_backtrace(notification)
222
+ end
223
+ end
224
+
225
+ # Provides failure error and backtrace.
226
+ #
227
+ # @param notification [ExampleNotification] example notification
228
+ # @return [Hash<Symbol, String>] +error+ and +backtrace+
229
+ # for the YAML block
230
+ def failure_error_and_backtrace(notification)
231
+ {
232
+ error: failure_error(notification),
233
+ backtrace: failure_backtrace(notification)
234
+ }.compact
235
+ end
236
+
237
+ # Provides failure error.
238
+ #
239
+ # @param notification [ExampleNotification] example notification
240
+ # @return [String] failure error
241
+ def failure_error(notification)
242
+ message_lines = notification.message_lines
243
+
244
+ return if message_lines.empty?
245
+
246
+ uncolorize_lines(message_lines)
247
+ .reject(&:blank?)
248
+ .join("\n")
249
+ end
250
+
251
+ # Provides failure backtrace.
252
+ #
253
+ # @param notification [ExampleNotification] example notification
254
+ # @return [String] failure backtrace
255
+ def failure_backtrace(notification)
256
+ formatted_backtrace = notification.formatted_backtrace
257
+
258
+ return if formatted_backtrace.empty?
259
+
260
+ uncolorize_lines(formatted_backtrace)
261
+ .reject(&:blank?)
262
+ .first(10)
263
+ .join("\n")
264
+ end
265
+
266
+ # Provides aggregate failure error.
267
+ #
268
+ # @param notification [ExampleNotification] example notification
269
+ # @return [Hash<Symbol, String>] +error+ for the YAML block
270
+ def multiple_failures_error_and_backtrace(notification)
271
+ {
272
+ error: multiple_failures_error(notification)
273
+ }.compact
274
+ end
275
+
276
+ # Provides aggregate failure error.
277
+ #
278
+ # @param notification [ExampleNotification] example notification
279
+ # @return [String] aggregate failure error
280
+ def multiple_failures_error(notification)
281
+ message = notification.example.execution_result.exception.message
282
+
283
+ return if message.blank?
284
+
285
+ uncolorize_lines(message.split("\n"))
286
+ .reject(&:blank?)
287
+ .join("\n")
288
+ end
289
+
290
+ # Prints failure error YAML block.
291
+ #
292
+ # @param reason [Hash<String, String>] failure reason hash
293
+ # for +location+, +error+, and +backtrace+
294
+ # @param padding [Integer] indentation width
295
+ def failure_diagnostics_output(reason, padding)
296
+ Psych.dump(reason).lines.each do |line|
297
+ @output.print("#{indentation(padding)}#{line}")
298
+ end
299
+
300
+ @output.puts("#{indentation(padding)}...")
301
+ end
302
+
303
+ # Finds the directive for pending example.
304
+ #
305
+ # @param notification [ExampleNotification] example notification
306
+ # @return [String] directive +SKIP+ or +TODO+
307
+ def pending_example_directive(notification)
308
+ execution_result = notification.example.execution_result
309
+ could_be_skipped = execution_result.respond_to?(:example_skipped?)
310
+
311
+ if could_be_skipped && execution_result.example_skipped?
312
+ "SKIP: #{execution_result.pending_message}"
313
+ else
314
+ "TODO: #{execution_result.pending_message}"
315
+ end
316
+ end
317
+
318
+ # Prints failure reason outside of example.
319
+ # It is the only bail out scenario.
320
+ #
321
+ # @param notification [ExampleNotification] example notification
322
+ def bailed_out_message_output(notification)
323
+ bailed_out_report_output
324
+
325
+ uncolorize_lines(notification.message.split("\n")).each do |line|
326
+ next if line.blank?
327
+
328
+ if line.start_with?('#')
329
+ @output.puts("# #{line.chars.drop(1).join.strip}")
330
+ else
331
+ @output.puts("# #{line}")
332
+ end
333
+ end
334
+ end
335
+
336
+ # Prints required TAP lines for bailed out scenario.
337
+ def bailed_out_report_output
338
+ @output.puts('TAP version 13')
339
+ @output.puts('1..0')
340
+ @output.puts('Bail out!')
341
+ end
342
+
343
+ # Prints example stats and duration for the entire execution.
344
+ #
345
+ # @param notification [ExampleNotification] example notification
346
+ def execution_stats_output(notification)
347
+ test_stats = [
348
+ notification.examples.size,
349
+ 0,
350
+ notification.failed_examples.size,
351
+ notification.pending_examples.size
352
+ ]
353
+ test_stats[1] = test_stats[0] - test_stats.drop(1).reduce(:+)
354
+
355
+ stats_output(test_stats, 0)
356
+
357
+ @output.puts("# duration: #{notification.duration} seconds")
358
+ end
359
+
360
+ # Prints example stats.
361
+ #
362
+ # @param test_stats [Array<Integer>] stats for the example group
363
+ # @param padding [Integer] indentation width
364
+ def stats_output(test_stats, padding)
365
+ stats = %i[tests passed failed pending]
366
+ .zip(test_stats)
367
+ .to_h
368
+ .reject { |_, value| value.zero? }
369
+ .map { |key, value| "#{key}: #{value}" }
370
+ .join(', ')
371
+
372
+ @output.puts("#{indentation(padding)}# #{stats}")
373
+ end
374
+
375
+ # Prints failed examples list.
376
+ # It is not included in the TAP report.
377
+ def dump_failed_examples_summary
378
+ if @write_to_file
379
+ $stdout.puts(@failed_examples)
380
+ else
381
+ @output.puts(@failed_examples)
382
+ end
383
+ end
384
+
385
+ # Prints pending examples list.
386
+ # It is not included in the TAP report.
387
+ def dump_pending_examples_summary
388
+ if @write_to_file
389
+ $stdout.puts(@pending_examples)
390
+ else
391
+ @output.puts(@pending_examples)
392
+ end
393
+ end
394
+
395
+ # Converts string to colored string.
396
+ #
397
+ # @param line [String] line to print
398
+ # @param status [Symbol] status for color
399
+ # (:detail, :success, :failure, and :pending)
400
+ # @return [String] colored string
401
+ def colored_line(line, status)
402
+ if @force_colors || @display_colors
403
+ RSpec::Core::Formatters::ConsoleCodes.wrap(line, status)
404
+ else
405
+ line
406
+ end
407
+ end
408
+
409
+ # Converts array of colored strings to uncolored string
410
+ #
411
+ # @param lines [Array<String>] lines to uncolor
412
+ # @return [Array<String>] uncolored lines
413
+ def uncolorize_lines(lines)
414
+ lines.map { |line| uncolorize_line(line) }
415
+ end
416
+
417
+ # Converts string to uncolored line.
418
+ # It strips ANSI escape sequences +\033[XXXm+.
419
+ #
420
+ # @example
421
+ # uncolorize_line('\033[0;31mcolored line\033[0m')
422
+ # #=> 'colored line'
423
+ #
424
+ # @param line [String] line to print
425
+ # @return [String] uncolored line
426
+ def uncolorize_line(line)
427
+ return line if line.blank?
428
+
429
+ line.gsub(/\e\[(\d+)(;\d+)*m/, '')
430
+ end
431
+
432
+ # Computes the indentation width by padding.
433
+ # The default indentation width is +2+.
434
+ #
435
+ # @param padding [Integer] indentation width
436
+ # @return [String] string of whitespaces
437
+ def indentation(padding)
438
+ return unless padding.positive?
439
+
440
+ ' ' * padding
441
+ end
442
+ end
443
+ end
444
+ end
445
+ end