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