rspec-core 3.0.4 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +2 -1
  5. data/Changelog.md +888 -2
  6. data/{License.txt → LICENSE.md} +6 -5
  7. data/README.md +165 -24
  8. data/lib/rspec/autorun.rb +1 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +19 -20
  10. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  11. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  12. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  13. data/lib/rspec/core/bisect/server.rb +61 -0
  14. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  15. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  16. data/lib/rspec/core/bisect/utilities.rb +69 -0
  17. data/lib/rspec/core/configuration.rb +1287 -246
  18. data/lib/rspec/core/configuration_options.rb +95 -35
  19. data/lib/rspec/core/did_you_mean.rb +46 -0
  20. data/lib/rspec/core/drb.rb +21 -12
  21. data/lib/rspec/core/dsl.rb +10 -6
  22. data/lib/rspec/core/example.rb +305 -113
  23. data/lib/rspec/core/example_group.rb +431 -223
  24. data/lib/rspec/core/example_status_persister.rb +235 -0
  25. data/lib/rspec/core/filter_manager.rb +86 -115
  26. data/lib/rspec/core/flat_map.rb +6 -4
  27. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  28. data/lib/rspec/core/formatters/base_formatter.rb +14 -116
  29. data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
  30. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  31. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  32. data/lib/rspec/core/formatters/console_codes.rb +29 -18
  33. data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
  34. data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
  35. data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
  36. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  37. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  38. data/lib/rspec/core/formatters/helpers.rb +45 -15
  39. data/lib/rspec/core/formatters/html_formatter.rb +33 -28
  40. data/lib/rspec/core/formatters/html_printer.rb +30 -20
  41. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  42. data/lib/rspec/core/formatters/json_formatter.rb +18 -9
  43. data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
  44. data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
  45. data/lib/rspec/core/formatters/protocol.rb +182 -0
  46. data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
  47. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  48. data/lib/rspec/core/formatters.rb +81 -41
  49. data/lib/rspec/core/hooks.rb +314 -244
  50. data/lib/rspec/core/invocations.rb +87 -0
  51. data/lib/rspec/core/memoized_helpers.rb +161 -51
  52. data/lib/rspec/core/metadata.rb +132 -61
  53. data/lib/rspec/core/metadata_filter.rb +224 -64
  54. data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
  55. data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
  56. data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
  57. data/lib/rspec/core/mocking_adapters/null.rb +2 -0
  58. data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
  59. data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
  60. data/lib/rspec/core/notifications.rb +192 -206
  61. data/lib/rspec/core/option_parser.rb +174 -69
  62. data/lib/rspec/core/ordering.rb +48 -35
  63. data/lib/rspec/core/output_wrapper.rb +29 -0
  64. data/lib/rspec/core/pending.rb +25 -33
  65. data/lib/rspec/core/profiler.rb +34 -0
  66. data/lib/rspec/core/project_initializer/.rspec +0 -2
  67. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
  68. data/lib/rspec/core/project_initializer.rb +5 -3
  69. data/lib/rspec/core/rake_task.rb +99 -55
  70. data/lib/rspec/core/reporter.rb +128 -15
  71. data/lib/rspec/core/ruby_project.rb +14 -6
  72. data/lib/rspec/core/runner.rb +96 -45
  73. data/lib/rspec/core/sandbox.rb +37 -0
  74. data/lib/rspec/core/set.rb +54 -0
  75. data/lib/rspec/core/shared_example_group.rb +133 -43
  76. data/lib/rspec/core/shell_escape.rb +49 -0
  77. data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
  78. data/lib/rspec/core/version.rb +1 -1
  79. data/lib/rspec/core/warnings.rb +6 -6
  80. data/lib/rspec/core/world.rb +172 -68
  81. data/lib/rspec/core.rb +66 -21
  82. data.tar.gz.sig +0 -0
  83. metadata +93 -69
  84. metadata.gz.sig +0 -0
  85. data/lib/rspec/core/backport_random.rb +0 -336
@@ -1,16 +1,24 @@
1
1
  RSpec::Support.require_rspec_core "formatters/base_text_formatter"
2
+ RSpec::Support.require_rspec_core "formatters/console_codes"
2
3
 
3
4
  module RSpec
4
5
  module Core
5
6
  module Formatters
6
7
  # @private
7
8
  class DocumentationFormatter < BaseTextFormatter
8
- Formatters.register self, :example_group_started, :example_group_finished,
9
- :example_passed, :example_pending, :example_failed
9
+ Formatters.register self, :example_started, :example_group_started, :example_group_finished,
10
+ :example_passed, :example_pending, :example_failed
10
11
 
11
12
  def initialize(output)
12
13
  super
13
14
  @group_level = 0
15
+
16
+ @example_running = false
17
+ @messages = []
18
+ end
19
+
20
+ def example_started(_notification)
21
+ @example_running = true
14
22
  end
15
23
 
16
24
  def example_group_started(notification)
@@ -20,34 +28,64 @@ module RSpec
20
28
  @group_level += 1
21
29
  end
22
30
 
23
- def example_group_finished(notification)
24
- @group_level -= 1
31
+ def example_group_finished(_notification)
32
+ @group_level -= 1 if @group_level > 0
25
33
  end
26
34
 
27
35
  def example_passed(passed)
28
36
  output.puts passed_output(passed.example)
37
+
38
+ flush_messages
39
+ @example_running = false
29
40
  end
30
41
 
31
42
  def example_pending(pending)
32
- output.puts pending_output(pending.example, pending.example.execution_result.pending_message)
43
+ output.puts pending_output(pending.example,
44
+ pending.example.execution_result.pending_message)
45
+
46
+ flush_messages
47
+ @example_running = false
33
48
  end
34
49
 
35
50
  def example_failed(failure)
36
- output.puts failure_output(failure.example, failure.example.execution_result.exception)
51
+ output.puts failure_output(failure.example)
52
+
53
+ flush_messages
54
+ @example_running = false
55
+ end
56
+
57
+ def message(notification)
58
+ if @example_running
59
+ @messages << notification.message
60
+ else
61
+ output.puts "#{current_indentation}#{notification.message}"
62
+ end
37
63
  end
38
64
 
39
65
  private
40
66
 
67
+ def flush_messages
68
+ @messages.each do |message|
69
+ output.puts "#{current_indentation(1)}#{message}"
70
+ end
71
+
72
+ @messages.clear
73
+ end
74
+
41
75
  def passed_output(example)
42
76
  ConsoleCodes.wrap("#{current_indentation}#{example.description.strip}", :success)
43
77
  end
44
78
 
45
79
  def pending_output(example, message)
46
- ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} (PENDING: #{message})", :pending)
80
+ ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} " \
81
+ "(PENDING: #{message})",
82
+ :pending)
47
83
  end
48
84
 
49
- def failure_output(example, exception)
50
- ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} (FAILED - #{next_failure_index})", :failure)
85
+ def failure_output(example)
86
+ ConsoleCodes.wrap("#{current_indentation}#{example.description.strip} " \
87
+ "(FAILED - #{next_failure_index})",
88
+ :failure)
51
89
  end
52
90
 
53
91
  def next_failure_index
@@ -55,14 +93,9 @@ module RSpec
55
93
  @next_failure_index += 1
56
94
  end
57
95
 
58
- def current_indentation
59
- ' ' * @group_level
96
+ def current_indentation(offset=0)
97
+ ' ' * (@group_level + offset)
60
98
  end
61
-
62
- def example_group_chain
63
- example_group.parent_groups.reverse
64
- end
65
-
66
99
  end
67
100
  end
68
101
  end
@@ -0,0 +1,525 @@
1
+ # encoding: utf-8
2
+ RSpec::Support.require_rspec_core "formatters/console_codes"
3
+ RSpec::Support.require_rspec_core "formatters/snippet_extractor"
4
+ RSpec::Support.require_rspec_core 'formatters/syntax_highlighter'
5
+ RSpec::Support.require_rspec_support "encoded_string"
6
+
7
+ module RSpec
8
+ module Core
9
+ module Formatters
10
+ # @private
11
+ class ExceptionPresenter
12
+ attr_reader :exception, :example, :description, :message_color,
13
+ :detail_formatter, :extra_detail_formatter, :backtrace_formatter
14
+ private :message_color, :detail_formatter, :extra_detail_formatter, :backtrace_formatter
15
+
16
+ def initialize(exception, example, options={})
17
+ @exception = exception
18
+ @example = example
19
+ @message_color = options.fetch(:message_color) { RSpec.configuration.failure_color }
20
+ @description = options.fetch(:description) { example.full_description }
21
+ @detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
22
+ @extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
23
+ @backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
24
+ @indentation = options.fetch(:indentation, 2)
25
+ @skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false)
26
+ @failure_lines = options[:failure_lines]
27
+ end
28
+
29
+ def message_lines
30
+ add_shared_group_lines(failure_lines, Notifications::NullColorizer)
31
+ end
32
+
33
+ def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
34
+ add_shared_group_lines(failure_lines, colorizer).map do |line|
35
+ colorizer.wrap line, message_color
36
+ end
37
+ end
38
+
39
+ def formatted_backtrace(exception=@exception)
40
+ backtrace_formatter.format_backtrace(exception.backtrace, example.metadata) +
41
+ formatted_cause(exception)
42
+ end
43
+
44
+ if RSpec::Support::RubyFeatures.supports_exception_cause?
45
+ def formatted_cause(exception)
46
+ last_cause = final_exception(exception, [exception])
47
+ cause = []
48
+
49
+ if exception.cause
50
+ cause << '------------------'
51
+ cause << '--- Caused by: ---'
52
+ cause << "#{exception_class_name(last_cause)}:" unless exception_class_name(last_cause) =~ /RSpec/
53
+
54
+ encoded_string(exception_message_string(last_cause)).split("\n").each do |line|
55
+ cause << " #{line}"
56
+ end
57
+
58
+ unless last_cause.backtrace.nil? || last_cause.backtrace.empty?
59
+ cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}")
60
+ end
61
+ end
62
+
63
+ cause
64
+ end
65
+ else
66
+ # :nocov:
67
+ def formatted_cause(_)
68
+ []
69
+ end
70
+ # :nocov:
71
+ end
72
+
73
+ def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
74
+ formatted_backtrace.map do |backtrace_info|
75
+ colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color
76
+ end
77
+ end
78
+
79
+ def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
80
+ lines = fully_formatted_lines(failure_number, colorizer)
81
+ lines.join("\n") << "\n"
82
+ end
83
+
84
+ def fully_formatted_lines(failure_number, colorizer)
85
+ lines = [
86
+ encoded_description(description),
87
+ detail_formatter.call(example, colorizer),
88
+ formatted_message_and_backtrace(colorizer),
89
+ extra_detail_formatter.call(failure_number, colorizer),
90
+ ].compact.flatten
91
+
92
+ lines = indent_lines(lines, failure_number)
93
+ lines.unshift("")
94
+ lines
95
+ end
96
+
97
+ private
98
+
99
+ def final_exception(exception, previous=[])
100
+ cause = exception.cause
101
+
102
+ if cause && Exception === cause && !previous.include?(cause)
103
+ previous << cause
104
+ final_exception(cause, previous)
105
+ else
106
+ exception
107
+ end
108
+ end
109
+
110
+ if String.method_defined?(:encoding)
111
+ def encoding_of(string)
112
+ string.encoding
113
+ end
114
+
115
+ def encoded_string(string)
116
+ RSpec::Support::EncodedString.new(string, Encoding.default_external)
117
+ end
118
+ else # for 1.8.7
119
+ # :nocov:
120
+ def encoding_of(_string)
121
+ end
122
+
123
+ def encoded_string(string)
124
+ RSpec::Support::EncodedString.new(string)
125
+ end
126
+ # :nocov:
127
+ end
128
+
129
+ def indent_lines(lines, failure_number)
130
+ alignment_basis = ' ' * @indentation
131
+ alignment_basis << "#{failure_number}) " if failure_number
132
+ indentation = ' ' * alignment_basis.length
133
+
134
+ lines.each_with_index.map do |line, index|
135
+ if index == 0
136
+ "#{alignment_basis}#{line}"
137
+ elsif line.empty?
138
+ line
139
+ else
140
+ "#{indentation}#{line}"
141
+ end
142
+ end
143
+ end
144
+
145
+ def exception_class_name(exception=@exception)
146
+ name = exception.class.name.to_s
147
+ name = "(anonymous error class)" if name == ''
148
+ name
149
+ end
150
+
151
+ def failure_lines
152
+ @failure_lines ||= [].tap do |lines|
153
+ lines.concat(failure_slash_error_lines)
154
+
155
+ sections = [failure_slash_error_lines, exception_lines]
156
+ if sections.any? { |section| section.size > 1 } && !exception_lines.first.empty?
157
+ lines << ''
158
+ end
159
+
160
+ lines.concat(exception_lines)
161
+ lines.concat(extra_failure_lines)
162
+ end
163
+ end
164
+
165
+ def failure_slash_error_lines
166
+ lines = read_failed_lines
167
+ if lines.count == 1
168
+ lines[0] = "Failure/Error: #{lines[0].strip}"
169
+ else
170
+ least_indentation = SnippetExtractor.least_indentation_from(lines)
171
+ lines = lines.map { |line| line.sub(/^#{least_indentation}/, ' ') }
172
+ lines.unshift('Failure/Error:')
173
+ end
174
+ lines
175
+ end
176
+
177
+ # rubocop:disable Lint/RescueException
178
+ def exception_message_string(exception)
179
+ exception.message.to_s
180
+ rescue Exception => other
181
+ "A #{exception.class} for which `exception.message.to_s` raises #{other.class}."
182
+ end
183
+ # rubocop:enable Lint/RescueException
184
+
185
+ def exception_lines
186
+ @exception_lines ||= begin
187
+ lines = []
188
+ lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/
189
+ encoded_string(exception_message_string(exception)).split("\n").each do |line|
190
+ lines << (line.empty? ? line : " #{line}")
191
+ end
192
+ lines
193
+ end
194
+ end
195
+
196
+ def extra_failure_lines
197
+ @extra_failure_lines ||= begin
198
+ lines = Array(example.metadata[:extra_failure_lines])
199
+ unless lines.empty?
200
+ lines.unshift('') unless lines.first == ''
201
+ lines.push('') unless lines.last == ''
202
+ end
203
+ lines
204
+ end
205
+ end
206
+
207
+ def add_shared_group_lines(lines, colorizer)
208
+ return lines if @skip_shared_group_trace
209
+
210
+ example.metadata[:shared_group_inclusion_backtrace].each do |frame|
211
+ lines << colorizer.wrap(frame.description, RSpec.configuration.default_color)
212
+ end
213
+
214
+ lines
215
+ end
216
+
217
+ def read_failed_lines
218
+ matching_line = find_failed_line
219
+ unless matching_line
220
+ return ["Unable to find matching line from backtrace"]
221
+ end
222
+
223
+ file_and_line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)
224
+
225
+ unless file_and_line_number
226
+ return ["Unable to infer file and line number from backtrace"]
227
+ end
228
+
229
+ file_path, line_number = file_and_line_number[1..2]
230
+ max_line_count = RSpec.configuration.max_displayed_failure_line_count
231
+ lines = SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count)
232
+ RSpec.world.syntax_highlighter.highlight(lines)
233
+ rescue SnippetExtractor::NoSuchFileError
234
+ ["Unable to find #{file_path} to read failed line"]
235
+ rescue SnippetExtractor::NoSuchLineError
236
+ ["Unable to find matching line in #{file_path}"]
237
+ rescue SecurityError
238
+ ["Unable to read failed line"]
239
+ end
240
+
241
+ def find_failed_line
242
+ line_regex = RSpec.configuration.in_project_source_dir_regex
243
+ loaded_spec_files = RSpec.configuration.loaded_spec_files
244
+
245
+ exception_backtrace.reject! do |line|
246
+ line.start_with?("<internal:")
247
+ end
248
+
249
+ exception_backtrace.find do |line|
250
+ next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1])
251
+ path = File.expand_path(line_path)
252
+ loaded_spec_files.include?(path) || path =~ line_regex
253
+ end || exception_backtrace.first
254
+ end
255
+
256
+ def formatted_message_and_backtrace(colorizer)
257
+ lines = colorized_message_lines(colorizer) + colorized_formatted_backtrace(colorizer)
258
+ encoding = encoding_of("")
259
+ lines.map do |line|
260
+ RSpec::Support::EncodedString.new(line, encoding)
261
+ end
262
+ end
263
+
264
+ if String.method_defined?(:encoding)
265
+ def encoded_description(description)
266
+ return if description.nil?
267
+ encoded_string(description)
268
+ end
269
+ else # for 1.8.7
270
+ def encoded_description(description)
271
+ description
272
+ end
273
+ end
274
+
275
+ def exception_backtrace
276
+ exception.backtrace || []
277
+ end
278
+
279
+ # @private
280
+ # Configuring the `ExceptionPresenter` with the right set of options to handle
281
+ # pending vs failed vs skipped and aggregated (or not) failures is not simple.
282
+ # This class takes care of building an appropriate `ExceptionPresenter` for the
283
+ # provided example.
284
+ class Factory
285
+ def build
286
+ ExceptionPresenter.new(@exception, @example, options)
287
+ end
288
+
289
+ private
290
+
291
+ def initialize(example)
292
+ @example = example
293
+ @execution_result = example.execution_result
294
+ @exception = if @execution_result.status == :pending
295
+ @execution_result.pending_exception
296
+ else
297
+ @execution_result.exception
298
+ end
299
+ end
300
+
301
+ def options
302
+ with_multiple_error_options_as_needed(@exception, pending_options || {})
303
+ end
304
+
305
+ def pending_options
306
+ if @execution_result.pending_fixed?
307
+ {
308
+ :description => "#{@example.full_description} FIXED",
309
+ :message_color => RSpec.configuration.fixed_color,
310
+ :failure_lines => [
311
+ "Expected pending '#{@execution_result.pending_message}' to fail. No error was raised."
312
+ ]
313
+ }
314
+ elsif @execution_result.status == :pending
315
+ {
316
+ :message_color => RSpec.configuration.pending_color,
317
+ :detail_formatter => PENDING_DETAIL_FORMATTER
318
+ }
319
+ end
320
+ end
321
+
322
+ def with_multiple_error_options_as_needed(exception, options)
323
+ return options unless multiple_exceptions_error?(exception)
324
+
325
+ options = options.merge(
326
+ :failure_lines => [],
327
+ :extra_detail_formatter => sub_failure_list_formatter(exception, options[:message_color]),
328
+ :detail_formatter => multiple_exception_summarizer(exception,
329
+ options[:detail_formatter],
330
+ options[:message_color])
331
+ )
332
+
333
+ return options unless exception.aggregation_metadata[:hide_backtrace]
334
+ options[:backtrace_formatter] = EmptyBacktraceFormatter
335
+ options
336
+ end
337
+
338
+ def multiple_exceptions_error?(exception)
339
+ MultipleExceptionError::InterfaceTag === exception
340
+ end
341
+
342
+ def multiple_exception_summarizer(exception, prior_detail_formatter, color)
343
+ lambda do |example, colorizer|
344
+ summary = if exception.aggregation_metadata[:hide_backtrace]
345
+ # Since the backtrace is hidden, the subfailures will come
346
+ # immediately after this, and using `:` will read well.
347
+ "Got #{exception.exception_count_description}:"
348
+ else
349
+ # The backtrace comes after this, so using a `:` doesn't make sense
350
+ # since the failures may be many lines below.
351
+ "#{exception.summary}."
352
+ end
353
+
354
+ summary = colorizer.wrap(summary, color || RSpec.configuration.failure_color)
355
+ return summary unless prior_detail_formatter
356
+ [
357
+ prior_detail_formatter.call(example, colorizer),
358
+ summary
359
+ ]
360
+ end
361
+ end
362
+
363
+ def sub_failure_list_formatter(exception, message_color)
364
+ common_backtrace_truncater = CommonBacktraceTruncater.new(exception)
365
+
366
+ lambda do |failure_number, colorizer|
367
+ FlatMap.flat_map(exception.all_exceptions.each_with_index) do |failure, index|
368
+ options = with_multiple_error_options_as_needed(
369
+ failure,
370
+ :description => nil,
371
+ :indentation => 0,
372
+ :message_color => message_color || RSpec.configuration.failure_color,
373
+ :skip_shared_group_trace => true
374
+ )
375
+
376
+ failure = common_backtrace_truncater.with_truncated_backtrace(failure)
377
+ presenter = ExceptionPresenter.new(failure, @example, options)
378
+ presenter.fully_formatted_lines(
379
+ "#{failure_number ? "#{failure_number}." : ''}#{index + 1}",
380
+ colorizer
381
+ )
382
+ end
383
+ end
384
+ end
385
+
386
+ # @private
387
+ # Used to prevent a confusing backtrace from showing up from the `aggregate_failures`
388
+ # block declared for `:aggregate_failures` metadata.
389
+ module EmptyBacktraceFormatter
390
+ def self.format_backtrace(*)
391
+ []
392
+ end
393
+ end
394
+
395
+ # @private
396
+ class CommonBacktraceTruncater
397
+ def initialize(parent)
398
+ @parent = parent
399
+ end
400
+
401
+ def with_truncated_backtrace(child)
402
+ child_bt = child.backtrace
403
+ parent_bt = @parent.backtrace
404
+ return child if child_bt.nil? || child_bt.empty? || parent_bt.nil?
405
+
406
+ index_before_first_common_frame = -1.downto(-child_bt.size).find do |index|
407
+ parent_bt[index] != child_bt[index]
408
+ end
409
+
410
+ return child if index_before_first_common_frame.nil?
411
+ return child if index_before_first_common_frame == -1
412
+
413
+ child = child.dup
414
+ child.set_backtrace(child_bt[0..index_before_first_common_frame])
415
+ child
416
+ end
417
+ end
418
+ end
419
+
420
+ # @private
421
+ PENDING_DETAIL_FORMATTER = Proc.new do |example, colorizer|
422
+ colorizer.wrap("# #{example.execution_result.pending_message}", :detail)
423
+ end
424
+ end
425
+ end
426
+
427
+ # Provides a single exception instance that provides access to
428
+ # multiple sub-exceptions. This is used in situations where a single
429
+ # individual spec has multiple exceptions, such as one in the `it` block
430
+ # and one in an `after` block.
431
+ class MultipleExceptionError < StandardError
432
+ # @private
433
+ # Used so there is a common module in the ancestor chain of this class
434
+ # and `RSpec::Expectations::MultipleExpectationsNotMetError`, which allows
435
+ # code to detect exceptions that are instances of either, without first
436
+ # checking to see if rspec-expectations is loaded.
437
+ module InterfaceTag
438
+ # Appends the provided exception to the list.
439
+ # @param exception [Exception] Exception to append to the list.
440
+ # @private
441
+ def add(exception)
442
+ # `PendingExampleFixedError` can be assigned to an example that initially has no
443
+ # failures, but when the `aggregate_failures` around hook completes, it notifies of
444
+ # a failure. If we do not ignore `PendingExampleFixedError` it would be surfaced to
445
+ # the user as part of a multiple exception error, which is undesirable. While it's
446
+ # pretty weird we handle this here, it's the best solution I've been able to come
447
+ # up with, and `PendingExampleFixedError` always represents the _lack_ of any exception
448
+ # so clearly when we are transitioning to a `MultipleExceptionError`, it makes sense to
449
+ # ignore it.
450
+ return if Pending::PendingExampleFixedError === exception
451
+
452
+ return if exception == self
453
+
454
+ all_exceptions << exception
455
+
456
+ if exception.class.name =~ /RSpec/
457
+ failures << exception
458
+ else
459
+ other_errors << exception
460
+ end
461
+ end
462
+
463
+ # Provides a way to force `ex` to be something that satisfies the multiple
464
+ # exception error interface. If it already satisfies it, it will be returned;
465
+ # otherwise it will wrap it in a `MultipleExceptionError`.
466
+ # @private
467
+ def self.for(ex)
468
+ return ex if self === ex
469
+ MultipleExceptionError.new(ex)
470
+ end
471
+ end
472
+
473
+ include InterfaceTag
474
+
475
+ # @return [Array<Exception>] The list of failures.
476
+ attr_reader :failures
477
+
478
+ # @return [Array<Exception>] The list of other errors.
479
+ attr_reader :other_errors
480
+
481
+ # @return [Array<Exception>] The list of failures and other exceptions, combined.
482
+ attr_reader :all_exceptions
483
+
484
+ # @return [Hash] Metadata used by RSpec for formatting purposes.
485
+ attr_reader :aggregation_metadata
486
+
487
+ # @return [nil] Provided only for interface compatibility with
488
+ # `RSpec::Expectations::MultipleExpectationsNotMetError`.
489
+ attr_reader :aggregation_block_label
490
+
491
+ # @param exceptions [Array<Exception>] The initial list of exceptions.
492
+ def initialize(*exceptions)
493
+ super()
494
+
495
+ @failures = []
496
+ @other_errors = []
497
+ @all_exceptions = []
498
+ @aggregation_metadata = { :hide_backtrace => true }
499
+ @aggregation_block_label = nil
500
+
501
+ exceptions.each { |e| add e }
502
+ end
503
+
504
+ # @return [String] Combines all the exception messages into a single string.
505
+ # @note RSpec does not actually use this -- instead it formats each exception
506
+ # individually.
507
+ def message
508
+ all_exceptions.map(&:message).join("\n\n")
509
+ end
510
+
511
+ # @return [String] A summary of the failure, including the block label and a count of failures.
512
+ def summary
513
+ "Got #{exception_count_description}"
514
+ end
515
+
516
+ # return [String] A description of the failure/error counts.
517
+ def exception_count_description
518
+ failure_count = Formatters::Helpers.pluralize(failures.size, "failure")
519
+ return failure_count if other_errors.empty?
520
+ error_count = Formatters::Helpers.pluralize(other_errors.size, "other error")
521
+ "#{failure_count} and #{error_count}"
522
+ end
523
+ end
524
+ end
525
+ end
@@ -0,0 +1,23 @@
1
+ RSpec::Support.require_rspec_core "formatters/base_formatter"
2
+
3
+ module RSpec
4
+ module Core
5
+ module Formatters
6
+ # @private
7
+ class FailureListFormatter < BaseFormatter
8
+ Formatters.register self, :example_failed, :dump_profile, :message
9
+
10
+ def example_failed(failure)
11
+ output.puts "#{failure.example.location}:#{failure.example.description}"
12
+ end
13
+
14
+ # Discard profile and messages
15
+ #
16
+ # These outputs are not really relevant in the context of this failure
17
+ # list formatter.
18
+ def dump_profile(_profile); end
19
+ def message(_message); end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module RSpec
2
+ module Core
3
+ module Formatters
4
+ # @api private
5
+ # Formatter for providing message output as a fallback when no other
6
+ # profiler implements #message
7
+ class FallbackMessageFormatter
8
+ Formatters.register self, :message
9
+
10
+ def initialize(output)
11
+ @output = output
12
+ end
13
+
14
+ # @private
15
+ attr_reader :output
16
+
17
+ # @api public
18
+ #
19
+ # Used by the reporter to send messages to the output stream.
20
+ #
21
+ # @param notification [MessageNotification] containing message
22
+ def message(notification)
23
+ output.puts notification.message
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end