rspec-core 3.2.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +75 -0
  5. data/README.md +137 -20
  6. data/lib/rspec/autorun.rb +1 -0
  7. data/lib/rspec/core.rb +8 -16
  8. data/lib/rspec/core/backtrace_formatter.rb +1 -3
  9. data/lib/rspec/core/bisect/coordinator.rb +66 -0
  10. data/lib/rspec/core/bisect/example_minimizer.rb +130 -0
  11. data/lib/rspec/core/bisect/runner.rb +139 -0
  12. data/lib/rspec/core/bisect/server.rb +61 -0
  13. data/lib/rspec/core/bisect/subset_enumerator.rb +39 -0
  14. data/lib/rspec/core/configuration.rb +134 -5
  15. data/lib/rspec/core/configuration_options.rb +21 -10
  16. data/lib/rspec/core/example.rb +84 -50
  17. data/lib/rspec/core/example_group.rb +46 -18
  18. data/lib/rspec/core/example_status_persister.rb +235 -0
  19. data/lib/rspec/core/filter_manager.rb +43 -28
  20. data/lib/rspec/core/flat_map.rb +2 -0
  21. data/lib/rspec/core/formatters.rb +30 -20
  22. data/lib/rspec/core/formatters/base_text_formatter.rb +1 -0
  23. data/lib/rspec/core/formatters/bisect_formatter.rb +68 -0
  24. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +115 -0
  25. data/lib/rspec/core/formatters/deprecation_formatter.rb +0 -1
  26. data/lib/rspec/core/formatters/documentation_formatter.rb +0 -4
  27. data/lib/rspec/core/formatters/exception_presenter.rb +389 -0
  28. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  29. data/lib/rspec/core/formatters/helpers.rb +22 -2
  30. data/lib/rspec/core/formatters/html_formatter.rb +1 -4
  31. data/lib/rspec/core/formatters/html_printer.rb +2 -6
  32. data/lib/rspec/core/formatters/json_formatter.rb +6 -4
  33. data/lib/rspec/core/formatters/snippet_extractor.rb +12 -7
  34. data/lib/rspec/core/hooks.rb +8 -2
  35. data/lib/rspec/core/memoized_helpers.rb +77 -17
  36. data/lib/rspec/core/metadata.rb +24 -10
  37. data/lib/rspec/core/metadata_filter.rb +16 -3
  38. data/lib/rspec/core/mutex.rb +63 -0
  39. data/lib/rspec/core/notifications.rb +84 -189
  40. data/lib/rspec/core/option_parser.rb +105 -32
  41. data/lib/rspec/core/ordering.rb +28 -25
  42. data/lib/rspec/core/profiler.rb +32 -0
  43. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +6 -1
  44. data/lib/rspec/core/rake_task.rb +6 -20
  45. data/lib/rspec/core/reentrant_mutex.rb +52 -0
  46. data/lib/rspec/core/reporter.rb +65 -17
  47. data/lib/rspec/core/runner.rb +38 -14
  48. data/lib/rspec/core/set.rb +49 -0
  49. data/lib/rspec/core/shared_example_group.rb +3 -1
  50. data/lib/rspec/core/shell_escape.rb +49 -0
  51. data/lib/rspec/core/version.rb +1 -1
  52. data/lib/rspec/core/world.rb +31 -20
  53. metadata +35 -7
  54. metadata.gz.sig +0 -0
  55. data/lib/rspec/core/backport_random.rb +0 -339
@@ -68,6 +68,7 @@ module RSpec
68
68
 
69
69
  output.puts
70
70
 
71
+ output.flush
71
72
  output.close unless output == $stdout
72
73
  end
73
74
  end
@@ -0,0 +1,68 @@
1
+ require 'drb/drb'
2
+
3
+ module RSpec
4
+ module Core
5
+ module Formatters
6
+ # Used by `--bisect`. When it shells out and runs a portion of the suite, it uses
7
+ # this formatter as a means to have the status reported back to it, via DRb.
8
+ #
9
+ # Note that since DRb calls carry considerable overhead compared to normal
10
+ # method calls, we try to minimize the number of DRb calls for perf reasons,
11
+ # opting to communicate only at the start and the end of the run, rather than
12
+ # after each example.
13
+ # @private
14
+ class BisectFormatter
15
+ Formatters.register self, :start, :start_dump, :example_started,
16
+ :example_failed, :example_passed, :example_pending
17
+
18
+ def initialize(_output)
19
+ port = RSpec.configuration.drb_port
20
+ drb_uri = "druby://localhost:#{port}"
21
+ @all_example_ids = []
22
+ @failed_example_ids = []
23
+ @bisect_server = DRbObject.new_with_uri(drb_uri)
24
+ @remaining_failures = []
25
+ end
26
+
27
+ def start(_notification)
28
+ @remaining_failures = Set.new(@bisect_server.expected_failures)
29
+ end
30
+
31
+ def example_started(notification)
32
+ @all_example_ids << notification.example.id
33
+ end
34
+
35
+ def example_failed(notification)
36
+ @failed_example_ids << notification.example.id
37
+ example_finished(notification, :failed)
38
+ end
39
+
40
+ def example_passed(notification)
41
+ example_finished(notification, :passed)
42
+ end
43
+
44
+ def example_pending(notification)
45
+ example_finished(notification, :pending)
46
+ end
47
+
48
+ def start_dump(_notification)
49
+ @bisect_server.latest_run_results = RunResults.new(
50
+ @all_example_ids, @failed_example_ids
51
+ )
52
+ end
53
+
54
+ RunResults = Struct.new(:all_example_ids, :failed_example_ids)
55
+
56
+ private
57
+
58
+ def example_finished(notification, status)
59
+ return unless @remaining_failures.include?(notification.example.id)
60
+ @remaining_failures.delete(notification.example.id)
61
+
62
+ return if status == :failed && !@remaining_failures.empty?
63
+ RSpec.world.wants_to_quit = true
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,115 @@
1
+ RSpec::Support.require_rspec_core "formatters/base_text_formatter"
2
+
3
+ module RSpec
4
+ module Core
5
+ module Formatters
6
+ # @private
7
+ # Produces progress output while bisecting.
8
+ class BisectProgressFormatter < BaseTextFormatter
9
+ # We've named all events with a `bisect_` prefix to prevent naming collisions.
10
+ Formatters.register self, :bisect_starting, :bisect_original_run_complete,
11
+ :bisect_round_started, :bisect_individual_run_complete,
12
+ :bisect_round_finished, :bisect_complete, :bisect_repro_command,
13
+ :bisect_failed, :bisect_aborted
14
+
15
+ def bisect_starting(notification)
16
+ options = notification.original_cli_args.join(' ')
17
+ output.puts "Bisect started using options: #{options.inspect}"
18
+ output.print "Running suite to find failures..."
19
+ end
20
+
21
+ def bisect_original_run_complete(notification)
22
+ failures = Helpers.pluralize(notification.failed_example_ids.size, "failing example")
23
+ non_failures = Helpers.pluralize(notification.non_failing_example_ids.size, "non-failing example")
24
+
25
+ output.puts " (#{Helpers.format_duration(notification.duration)})"
26
+ output.puts "Starting bisect with #{failures} and #{non_failures}."
27
+ end
28
+
29
+ def bisect_round_started(notification, include_trailing_space=true)
30
+ search_desc = Helpers.pluralize(
31
+ notification.subset_size, "non-failing example"
32
+ )
33
+
34
+ output.print "\nRound #{notification.round}: searching for #{search_desc}" \
35
+ " (of #{notification.remaining_count}) to ignore:"
36
+ output.print " " if include_trailing_space
37
+ end
38
+
39
+ def bisect_round_finished(notification)
40
+ output.print " (#{Helpers.format_duration(notification.duration)})"
41
+ end
42
+
43
+ def bisect_individual_run_complete(_)
44
+ output.print '.'
45
+ end
46
+
47
+ def bisect_complete(notification)
48
+ output.puts "\nBisect complete! Reduced necessary non-failing examples " \
49
+ "from #{notification.original_non_failing_count} to " \
50
+ "#{notification.remaining_count} in " \
51
+ "#{Helpers.format_duration(notification.duration)}."
52
+ end
53
+
54
+ def bisect_repro_command(notification)
55
+ output.puts "\nThe minimal reproduction command is:\n #{notification.repro}"
56
+ end
57
+
58
+ def bisect_failed(notification)
59
+ output.puts "\nBisect failed! #{notification.failure_explanation}"
60
+ end
61
+
62
+ def bisect_aborted(notification)
63
+ output.puts "\n\nBisect aborted!"
64
+ output.puts "\nThe most minimal reproduction command discovered so far is:\n #{notification.repro}"
65
+ end
66
+ end
67
+
68
+ # @private
69
+ # Produces detailed debug output while bisecting. Used when
70
+ # bisect is performed while the `DEBUG_RSPEC_BISECT` ENV var is used.
71
+ # Designed to provide details for us when we need to troubleshoot bisect bugs.
72
+ class BisectDebugFormatter < BisectProgressFormatter
73
+ Formatters.register self, :bisect_original_run_complete, :bisect_individual_run_start,
74
+ :bisect_individual_run_complete, :bisect_round_finished, :bisect_ignoring_ids
75
+
76
+ def bisect_original_run_complete(notification)
77
+ output.puts " (#{Helpers.format_duration(notification.duration)})"
78
+
79
+ output.puts " - #{describe_ids 'Failing examples', notification.failed_example_ids}"
80
+ output.puts " - #{describe_ids 'Non-failing examples', notification.non_failing_example_ids}"
81
+ end
82
+
83
+ def bisect_individual_run_start(notification)
84
+ output.print "\n - Running: #{notification.command}"
85
+ end
86
+
87
+ def bisect_individual_run_complete(notification)
88
+ output.print " (#{Helpers.format_duration(notification.duration)})"
89
+ end
90
+
91
+ def bisect_round_started(notification)
92
+ super(notification, false)
93
+ end
94
+
95
+ def bisect_round_finished(notification)
96
+ output.print "\n - Round finished"
97
+ super
98
+ end
99
+
100
+ def bisect_ignoring_ids(notification)
101
+ output.print "\n - #{describe_ids 'Examples we can safely ignore', notification.ids_to_ignore}"
102
+ output.print "\n - #{describe_ids 'Remaining non-failing examples', notification.remaining_ids}"
103
+ end
104
+
105
+ private
106
+
107
+ def describe_ids(description, ids)
108
+ organized_ids = Formatters::Helpers.organize_ids(ids)
109
+ formatted_ids = organized_ids.map { |id| " - #{id}" }.join("\n")
110
+ "#{description} (#{ids.size}):\n#{formatted_ids}"
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,5 +1,4 @@
1
1
  RSpec::Support.require_rspec_core "formatters/helpers"
2
- require 'set'
3
2
 
4
3
  module RSpec
5
4
  module Core
@@ -64,10 +64,6 @@ module RSpec
64
64
  def current_indentation
65
65
  ' ' * @group_level
66
66
  end
67
-
68
- def example_group_chain
69
- example_group.parent_groups.reverse
70
- end
71
67
  end
72
68
  end
73
69
  end
@@ -0,0 +1,389 @@
1
+ module RSpec
2
+ module Core
3
+ module Formatters
4
+ # @private
5
+ class ExceptionPresenter
6
+ attr_reader :exception, :example, :description, :message_color,
7
+ :detail_formatter, :extra_detail_formatter, :backtrace_formatter
8
+ private :message_color, :detail_formatter, :extra_detail_formatter, :backtrace_formatter
9
+
10
+ def initialize(exception, example, options={})
11
+ @exception = exception
12
+ @example = example
13
+ @message_color = options.fetch(:message_color) { RSpec.configuration.failure_color }
14
+ @description = options.fetch(:description_formatter) { Proc.new { example.full_description } }.call(self)
15
+ @detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
16
+ @extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
17
+ @backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
18
+ @indentation = options.fetch(:indentation, 2)
19
+ @skip_shared_group_trace = options.fetch(:skip_shared_group_trace, false)
20
+ @failure_lines = options[:failure_lines]
21
+ end
22
+
23
+ def message_lines
24
+ add_shared_group_lines(failure_lines, Notifications::NullColorizer)
25
+ end
26
+
27
+ def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
28
+ add_shared_group_lines(failure_lines, colorizer).map do |line|
29
+ colorizer.wrap line, message_color
30
+ end
31
+ end
32
+
33
+ def formatted_backtrace
34
+ backtrace_formatter.format_backtrace(exception.backtrace, example.metadata)
35
+ end
36
+
37
+ def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
38
+ formatted_backtrace.map do |backtrace_info|
39
+ colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color
40
+ end
41
+ end
42
+
43
+ def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
44
+ alignment_basis = "#{' ' * @indentation}#{failure_number}) "
45
+ indentation = ' ' * alignment_basis.length
46
+
47
+ "\n#{alignment_basis}#{description_and_detail(colorizer, indentation)}" \
48
+ "\n#{formatted_message_and_backtrace(colorizer, indentation)}" \
49
+ "#{extra_detail_formatter.call(failure_number, colorizer, indentation)}"
50
+ end
51
+
52
+ def failure_slash_error_line
53
+ @failure_slash_error_line ||= "Failure/Error: #{read_failed_line.strip}"
54
+ end
55
+
56
+ private
57
+
58
+ def description_and_detail(colorizer, indentation)
59
+ detail = detail_formatter.call(example, colorizer, indentation)
60
+ return (description || detail) unless description && detail
61
+ "#{description}\n#{indentation}#{detail}"
62
+ end
63
+
64
+ if String.method_defined?(:encoding)
65
+ def encoding_of(string)
66
+ string.encoding
67
+ end
68
+
69
+ def encoded_string(string)
70
+ RSpec::Support::EncodedString.new(string, Encoding.default_external)
71
+ end
72
+ else # for 1.8.7
73
+ # :nocov:
74
+ def encoding_of(_string)
75
+ end
76
+
77
+ def encoded_string(string)
78
+ RSpec::Support::EncodedString.new(string)
79
+ end
80
+ # :nocov:
81
+ end
82
+
83
+ def exception_class_name
84
+ name = exception.class.name.to_s
85
+ name = "(anonymous error class)" if name == ''
86
+ name
87
+ end
88
+
89
+ def failure_lines
90
+ @failure_lines ||=
91
+ begin
92
+ lines = []
93
+ lines << failure_slash_error_line unless (description == failure_slash_error_line)
94
+ lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/
95
+ encoded_string(exception.message.to_s).split("\n").each do |line|
96
+ lines << " #{line}"
97
+ end
98
+ lines
99
+ end
100
+ end
101
+
102
+ def add_shared_group_lines(lines, colorizer)
103
+ return lines if @skip_shared_group_trace
104
+
105
+ example.metadata[:shared_group_inclusion_backtrace].each do |frame|
106
+ lines << colorizer.wrap(frame.description, RSpec.configuration.default_color)
107
+ end
108
+
109
+ lines
110
+ end
111
+
112
+ def read_failed_line
113
+ matching_line = find_failed_line
114
+ unless matching_line
115
+ return "Unable to find matching line from backtrace"
116
+ end
117
+
118
+ file_path, line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)[1..2]
119
+
120
+ if File.exist?(file_path)
121
+ File.readlines(file_path)[line_number.to_i - 1] ||
122
+ "Unable to find matching line in #{file_path}"
123
+ else
124
+ "Unable to find #{file_path} to read failed line"
125
+ end
126
+ rescue SecurityError
127
+ "Unable to read failed line"
128
+ end
129
+
130
+ def find_failed_line
131
+ example_path = example.metadata[:absolute_file_path].downcase
132
+ exception.backtrace.find do |line|
133
+ next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1])
134
+ File.expand_path(line_path).downcase == example_path
135
+ end
136
+ end
137
+
138
+ def formatted_message_and_backtrace(colorizer, indentation)
139
+ lines = colorized_message_lines(colorizer) + colorized_formatted_backtrace(colorizer)
140
+
141
+ formatted = ""
142
+
143
+ lines.each do |line|
144
+ formatted << RSpec::Support::EncodedString.new("#{indentation}#{line}\n", encoding_of(formatted))
145
+ end
146
+
147
+ formatted
148
+ end
149
+
150
+ # @private
151
+ # Configuring the `ExceptionPresenter` with the right set of options to handle
152
+ # pending vs failed vs skipped and aggregated (or not) failures is not simple.
153
+ # This class takes care of building an appropriate `ExceptionPresenter` for the
154
+ # provided example.
155
+ class Factory
156
+ def build
157
+ ExceptionPresenter.new(@exception, @example, options)
158
+ end
159
+
160
+ private
161
+
162
+ def initialize(example)
163
+ @example = example
164
+ @execution_result = example.execution_result
165
+ @exception = if @execution_result.status == :pending
166
+ @execution_result.pending_exception
167
+ else
168
+ @execution_result.exception
169
+ end
170
+ end
171
+
172
+ def options
173
+ with_multiple_error_options_as_needed(@exception, pending_options || {})
174
+ end
175
+
176
+ def pending_options
177
+ if @execution_result.pending_fixed?
178
+ {
179
+ :description_formatter => Proc.new { "#{@example.full_description} FIXED" },
180
+ :message_color => RSpec.configuration.fixed_color,
181
+ :failure_lines => [
182
+ "Expected pending '#{@execution_result.pending_message}' to fail. No Error was raised."
183
+ ]
184
+ }
185
+ elsif @execution_result.status == :pending
186
+ {
187
+ :message_color => RSpec.configuration.pending_color,
188
+ :detail_formatter => PENDING_DETAIL_FORMATTER
189
+ }
190
+ end
191
+ end
192
+
193
+ def with_multiple_error_options_as_needed(exception, options)
194
+ return options unless multiple_exceptions_error?(exception)
195
+
196
+ options = options.merge(
197
+ :failure_lines => [],
198
+ :extra_detail_formatter => sub_failure_list_formatter(exception, options[:message_color]),
199
+ :detail_formatter => multiple_exception_summarizer(exception,
200
+ options[:detail_formatter],
201
+ options[:message_color])
202
+ )
203
+
204
+ options[:description_formatter] &&= Proc.new {}
205
+
206
+ return options unless exception.aggregation_metadata[:hide_backtrace]
207
+ options[:backtrace_formatter] = EmptyBacktraceFormatter
208
+ options
209
+ end
210
+
211
+ def multiple_exceptions_error?(exception)
212
+ MultipleExceptionError::InterfaceTag === exception
213
+ end
214
+
215
+ def multiple_exception_summarizer(exception, prior_detail_formatter, color)
216
+ lambda do |example, colorizer, indentation|
217
+ summary = if exception.aggregation_metadata[:hide_backtrace]
218
+ # Since the backtrace is hidden, the subfailures will come
219
+ # immediately after this, and using `:` will read well.
220
+ "Got #{exception.exception_count_description}:"
221
+ else
222
+ # The backtrace comes after this, so using a `:` doesn't make sense
223
+ # since the failures may be many lines below.
224
+ "#{exception.summary}."
225
+ end
226
+
227
+ summary = colorizer.wrap(summary, color || RSpec.configuration.failure_color)
228
+ return summary unless prior_detail_formatter
229
+ "#{prior_detail_formatter.call(example, colorizer, indentation)}\n#{indentation}#{summary}"
230
+ end
231
+ end
232
+
233
+ def sub_failure_list_formatter(exception, message_color)
234
+ common_backtrace_truncater = CommonBacktraceTruncater.new(exception)
235
+
236
+ lambda do |failure_number, colorizer, indentation|
237
+ exception.all_exceptions.each_with_index.map do |failure, index|
238
+ options = with_multiple_error_options_as_needed(
239
+ failure,
240
+ :description_formatter => :failure_slash_error_line.to_proc,
241
+ :indentation => indentation.length,
242
+ :message_color => message_color || RSpec.configuration.failure_color,
243
+ :skip_shared_group_trace => true
244
+ )
245
+
246
+ failure = common_backtrace_truncater.with_truncated_backtrace(failure)
247
+ presenter = ExceptionPresenter.new(failure, @example, options)
248
+ presenter.fully_formatted("#{failure_number}.#{index + 1}", colorizer)
249
+ end.join
250
+ end
251
+ end
252
+
253
+ # @private
254
+ # Used to prevent a confusing backtrace from showing up from the `aggregate_failures`
255
+ # block declared for `:aggregate_failures` metadata.
256
+ module EmptyBacktraceFormatter
257
+ def self.format_backtrace(*)
258
+ []
259
+ end
260
+ end
261
+
262
+ # @private
263
+ class CommonBacktraceTruncater
264
+ def initialize(parent)
265
+ @parent = parent
266
+ end
267
+
268
+ def with_truncated_backtrace(child)
269
+ child_bt = child.backtrace
270
+ parent_bt = @parent.backtrace
271
+ return child if child_bt.nil? || child_bt.empty? || parent_bt.nil?
272
+
273
+ index_before_first_common_frame = -1.downto(-child_bt.size).find do |index|
274
+ parent_bt[index] != child_bt[index]
275
+ end
276
+
277
+ return child if index_before_first_common_frame == -1
278
+
279
+ child = child.dup
280
+ child.set_backtrace(child_bt[0..index_before_first_common_frame])
281
+ child
282
+ end
283
+ end
284
+ end
285
+
286
+ # @private
287
+ PENDING_DETAIL_FORMATTER = Proc.new do |example, colorizer|
288
+ colorizer.wrap("# #{example.execution_result.pending_message}", :detail)
289
+ end
290
+ end
291
+ end
292
+
293
+ # Provides a single exception instance that provides access to
294
+ # multiple sub-exceptions. This is used in situations where a single
295
+ # individual spec has multiple exceptions, such as one in the `it` block
296
+ # and one in an `after` block.
297
+ class MultipleExceptionError < StandardError
298
+ # @private
299
+ # Used so there is a common module in the ancestor chain of this class
300
+ # and `RSpec::Expectations::MultipleExpectationsNotMetError`, which allows
301
+ # code to detect exceptions that are instances of either, without first
302
+ # checking to see if rspec-expectations is loaded.
303
+ module InterfaceTag
304
+ # Appends the provided exception to the list.
305
+ # @param exception [Exception] Exception to append to the list.
306
+ # @private
307
+ def add(exception)
308
+ # `PendingExampleFixedError` can be assigned to an example that initially has no
309
+ # failures, but when the `aggregate_failures` around hook completes, it notifies of
310
+ # a failure. If we do not ignore `PendingExampleFixedError` it would be surfaced to
311
+ # the user as part of a multiple exception error, which is undesirable. While it's
312
+ # pretty weird we handle this here, it's the best solution I've been able to come
313
+ # up with, and `PendingExampleFixedError` always represents the _lack_ of any exception
314
+ # so clearly when we are transitioning to a `MultipleExceptionError`, it makes sense to
315
+ # ignore it.
316
+ return if Pending::PendingExampleFixedError === exception
317
+
318
+ all_exceptions << exception
319
+
320
+ if exception.class.name =~ /RSpec/
321
+ failures << exception
322
+ else
323
+ other_errors << exception
324
+ end
325
+ end
326
+
327
+ # Provides a way to force `ex` to be something that satisfies the multiple
328
+ # exception error interface. If it already satisfies it, it will be returned;
329
+ # otherwise it will wrap it in a `MultipleExceptionError`.
330
+ # @private
331
+ def self.for(ex)
332
+ return ex if self === ex
333
+ MultipleExceptionError.new(ex)
334
+ end
335
+ end
336
+
337
+ include InterfaceTag
338
+
339
+ # @return [Array<Exception>] The list of failures.
340
+ attr_reader :failures
341
+
342
+ # @return [Array<Exception>] The list of other errors.
343
+ attr_reader :other_errors
344
+
345
+ # @return [Array<Exception>] The list of failures and other exceptions, combined.
346
+ attr_reader :all_exceptions
347
+
348
+ # @return [Hash] Metadata used by RSpec for formatting purposes.
349
+ attr_reader :aggregation_metadata
350
+
351
+ # @return [nil] Provided only for interface compatibility with
352
+ # `RSpec::Expectations::MultipleExpectationsNotMetError`.
353
+ attr_reader :aggregation_block_label
354
+
355
+ # @param exceptions [Array<Exception>] The initial list of exceptions.
356
+ def initialize(*exceptions)
357
+ super()
358
+
359
+ @failures = []
360
+ @other_errors = []
361
+ @all_exceptions = []
362
+ @aggregation_metadata = { :hide_backtrace => true }
363
+ @aggregation_block_label = nil
364
+
365
+ exceptions.each { |e| add e }
366
+ end
367
+
368
+ # @return [String] Combines all the exception messages into a single string.
369
+ # @note RSpec does not actually use this -- instead it formats each exception
370
+ # individually.
371
+ def message
372
+ all_exceptions.map(&:message).join("\n\n")
373
+ end
374
+
375
+ # @return [String] A summary of the failure, including the block label and a count of failures.
376
+ def summary
377
+ "Got #{exception_count_description}"
378
+ end
379
+
380
+ # return [String] A description of the failure/error counts.
381
+ def exception_count_description
382
+ failure_count = Formatters::Helpers.pluralize(failures.size, "failure")
383
+ return failure_count if other_errors.empty?
384
+ error_count = Formatters::Helpers.pluralize(other_errors.size, "other error")
385
+ "#{failure_count} and #{error_count}"
386
+ end
387
+ end
388
+ end
389
+ end