rspec-tap-formatters 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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