rspec-core 3.3.2 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +69 -0
- data/{License.txt → LICENSE.md} +6 -5
- data/README.md +18 -3
- data/lib/rspec/core/bisect/example_minimizer.rb +78 -39
- data/lib/rspec/core/configuration.rb +87 -25
- data/lib/rspec/core/configuration_options.rb +1 -1
- data/lib/rspec/core/example.rb +54 -7
- data/lib/rspec/core/example_group.rb +28 -8
- data/lib/rspec/core/example_status_persister.rb +16 -16
- data/lib/rspec/core/formatters.rb +1 -0
- data/lib/rspec/core/formatters/bisect_progress_formatter.rb +44 -15
- data/lib/rspec/core/formatters/exception_presenter.rb +146 -59
- data/lib/rspec/core/formatters/helpers.rb +1 -1
- data/lib/rspec/core/formatters/html_formatter.rb +2 -2
- data/lib/rspec/core/formatters/html_printer.rb +2 -3
- data/lib/rspec/core/formatters/html_snippet_extractor.rb +116 -0
- data/lib/rspec/core/formatters/protocol.rb +9 -0
- data/lib/rspec/core/formatters/snippet_extractor.rb +124 -97
- data/lib/rspec/core/hooks.rb +2 -2
- data/lib/rspec/core/memoized_helpers.rb +2 -2
- data/lib/rspec/core/metadata.rb +3 -2
- data/lib/rspec/core/metadata_filter.rb +11 -6
- data/lib/rspec/core/notifications.rb +3 -2
- data/lib/rspec/core/option_parser.rb +22 -4
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +2 -2
- data/lib/rspec/core/rake_task.rb +12 -3
- data/lib/rspec/core/reporter.rb +18 -2
- data/lib/rspec/core/ruby_project.rb +1 -1
- data/lib/rspec/core/shared_example_group.rb +2 -0
- data/lib/rspec/core/source.rb +76 -0
- data/lib/rspec/core/source/location.rb +13 -0
- data/lib/rspec/core/source/node.rb +93 -0
- data/lib/rspec/core/source/syntax_highlighter.rb +71 -0
- data/lib/rspec/core/source/token.rb +43 -0
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/world.rb +25 -6
- metadata +12 -9
- metadata.gz.sig +0 -0
- data/lib/rspec/core/bisect/subset_enumerator.rb +0 -39
- data/lib/rspec/core/mutex.rb +0 -63
- data/lib/rspec/core/reentrant_mutex.rb +0 -52
data/lib/rspec/core/example.rb
CHANGED
@@ -118,6 +118,30 @@ module RSpec
|
|
118
118
|
@id ||= Metadata.id_from(metadata)
|
119
119
|
end
|
120
120
|
|
121
|
+
# @private
|
122
|
+
def self.parse_id(id)
|
123
|
+
# http://rubular.com/r/OMZSAPcAfn
|
124
|
+
id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures
|
125
|
+
end
|
126
|
+
|
127
|
+
# Duplicates the example and overrides metadata with the provided
|
128
|
+
# hash.
|
129
|
+
#
|
130
|
+
# @param metadata_overrides [Hash] the hash to override the example metadata
|
131
|
+
# @return [Example] a duplicate of the example with modified metadata
|
132
|
+
def duplicate_with(metadata_overrides={})
|
133
|
+
new_metadata = metadata.clone.merge(metadata_overrides)
|
134
|
+
|
135
|
+
RSpec::Core::Metadata::RESERVED_KEYS.each do |reserved_key|
|
136
|
+
new_metadata.delete reserved_key
|
137
|
+
end
|
138
|
+
|
139
|
+
# don't clone the example group because the new example
|
140
|
+
# must belong to the same example group (not a clone).
|
141
|
+
Example.new(example_group, description.clone,
|
142
|
+
new_metadata, new_metadata[:block])
|
143
|
+
end
|
144
|
+
|
121
145
|
# @attr_reader
|
122
146
|
#
|
123
147
|
# Returns the first exception raised in the context of running this
|
@@ -170,6 +194,12 @@ module RSpec
|
|
170
194
|
@reporter = RSpec::Core::NullReporter
|
171
195
|
end
|
172
196
|
|
197
|
+
# Provide a human-readable representation of this class
|
198
|
+
def inspect
|
199
|
+
"#<#{self.class.name} #{description.inspect}>"
|
200
|
+
end
|
201
|
+
alias to_s inspect
|
202
|
+
|
173
203
|
# @return [RSpec::Core::Reporter] the current reporter for the example
|
174
204
|
attr_reader :reporter
|
175
205
|
|
@@ -215,14 +245,14 @@ module RSpec
|
|
215
245
|
rescue Pending::SkipDeclaredInExample
|
216
246
|
# no-op, required metadata has already been set by the `skip`
|
217
247
|
# method.
|
218
|
-
rescue
|
248
|
+
rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
|
219
249
|
set_exception(e)
|
220
250
|
ensure
|
221
251
|
run_after_example
|
222
252
|
end
|
223
253
|
end
|
224
254
|
end
|
225
|
-
rescue
|
255
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
226
256
|
set_exception(e)
|
227
257
|
ensure
|
228
258
|
@example_group_instance = nil # if you love something... let it go
|
@@ -233,6 +263,20 @@ module RSpec
|
|
233
263
|
RSpec.current_example = nil
|
234
264
|
end
|
235
265
|
|
266
|
+
if RSpec::Support::Ruby.jruby? || RUBY_VERSION.to_f < 1.9
|
267
|
+
# :nocov:
|
268
|
+
# For some reason, rescuing `Support::AllExceptionsExceptOnesWeMustNotRescue`
|
269
|
+
# in place of `Exception` above can cause the exit status to be the wrong
|
270
|
+
# thing. I have no idea why. See:
|
271
|
+
# https://github.com/rspec/rspec-core/pull/2063#discussion_r38284978
|
272
|
+
# @private
|
273
|
+
AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Exception
|
274
|
+
# :nocov:
|
275
|
+
else
|
276
|
+
# @private
|
277
|
+
AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt = Support::AllExceptionsExceptOnesWeMustNotRescue
|
278
|
+
end
|
279
|
+
|
236
280
|
# Wraps both a `Proc` and an {Example} for use in {Hooks#around
|
237
281
|
# around} hooks. In around hooks we need to yield this special
|
238
282
|
# kind of object (rather than the raw {Example}) because when
|
@@ -256,13 +300,15 @@ module RSpec
|
|
256
300
|
attr_reader :example
|
257
301
|
|
258
302
|
Example.public_instance_methods(false).each do |name|
|
259
|
-
|
303
|
+
name_sym = name.to_sym
|
304
|
+
next if name_sym == :run || name_sym == :inspect || name_sym == :to_s
|
260
305
|
|
261
306
|
define_method(name) { |*a, &b| @example.__send__(name, *a, &b) }
|
262
307
|
end
|
263
308
|
|
264
309
|
Proc.public_instance_methods(false).each do |name|
|
265
|
-
|
310
|
+
name_sym = name.to_sym
|
311
|
+
next if name_sym == :call || name_sym == :inspect || name_sym == :to_s || name_sym == :to_proc
|
266
312
|
|
267
313
|
define_method(name) { |*a, &b| @proc.__send__(name, *a, &b) }
|
268
314
|
end
|
@@ -386,7 +432,7 @@ module RSpec
|
|
386
432
|
|
387
433
|
def with_around_example_hooks
|
388
434
|
hooks.run(:around, :example, self) { yield }
|
389
|
-
rescue
|
435
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
390
436
|
set_exception(e)
|
391
437
|
end
|
392
438
|
|
@@ -398,6 +444,7 @@ module RSpec
|
|
398
444
|
def finish(reporter)
|
399
445
|
pending_message = execution_result.pending_message
|
400
446
|
|
447
|
+
reporter.example_finished(self)
|
401
448
|
if @exception
|
402
449
|
record_finished :failed
|
403
450
|
execution_result.exception = @exception
|
@@ -442,7 +489,7 @@ module RSpec
|
|
442
489
|
|
443
490
|
def verify_mocks
|
444
491
|
@example_group_instance.verify_mocks_for_rspec if mocks_need_verification?
|
445
|
-
rescue
|
492
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
446
493
|
set_exception(e)
|
447
494
|
end
|
448
495
|
|
@@ -461,7 +508,7 @@ module RSpec
|
|
461
508
|
|
462
509
|
def generate_description
|
463
510
|
RSpec::Matchers.generated_description
|
464
|
-
rescue
|
511
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
465
512
|
location_description + " (Got an error when generating description " \
|
466
513
|
"from matcher: #{e.class}: #{e.message} -- #{e.backtrace.first})"
|
467
514
|
end
|
@@ -2,6 +2,7 @@ RSpec::Support.require_rspec_support 'recursive_const_methods'
|
|
2
2
|
|
3
3
|
module RSpec
|
4
4
|
module Core
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
5
6
|
# ExampleGroup and {Example} are the main structural elements of
|
6
7
|
# rspec-core. Consider this example:
|
7
8
|
#
|
@@ -340,6 +341,27 @@ module RSpec
|
|
340
341
|
find_and_eval_shared("examples", name, caller.first, *args, &block)
|
341
342
|
end
|
342
343
|
|
344
|
+
# Clear memoized values when adding/removing examples
|
345
|
+
# @private
|
346
|
+
def self.reset_memoized
|
347
|
+
@descendant_filtered_examples = nil
|
348
|
+
@_descendants = nil
|
349
|
+
@parent_groups = nil
|
350
|
+
@declaration_line_numbers = nil
|
351
|
+
end
|
352
|
+
|
353
|
+
# Adds an example to the example group
|
354
|
+
def self.add_example(example)
|
355
|
+
reset_memoized
|
356
|
+
examples << example
|
357
|
+
end
|
358
|
+
|
359
|
+
# Removes an example from the example group
|
360
|
+
def self.remove_example(example)
|
361
|
+
reset_memoized
|
362
|
+
examples.delete example
|
363
|
+
end
|
364
|
+
|
343
365
|
# @private
|
344
366
|
def self.find_and_eval_shared(label, name, inclusion_location, *args, &customization_block)
|
345
367
|
shared_block = RSpec.world.shared_example_group_registry.find(parent_groups, name)
|
@@ -524,9 +546,9 @@ module RSpec
|
|
524
546
|
rescue Pending::SkipDeclaredInExample => ex
|
525
547
|
for_filtered_examples(reporter) { |example| example.skip_with_exception(reporter, ex) }
|
526
548
|
true
|
527
|
-
rescue
|
528
|
-
RSpec.world.wants_to_quit = true if fail_fast?
|
549
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
|
529
550
|
for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
|
551
|
+
RSpec.world.wants_to_quit = true if reporter.fail_fast_limit_met?
|
530
552
|
false
|
531
553
|
ensure
|
532
554
|
run_after_context_hooks(new('after(:context) hook')) if should_run_context_hooks
|
@@ -557,7 +579,9 @@ module RSpec
|
|
557
579
|
instance = new(example.inspect_output)
|
558
580
|
set_ivars(instance, before_context_ivars)
|
559
581
|
succeeded = example.run(instance, reporter)
|
560
|
-
|
582
|
+
if !succeeded && reporter.fail_fast_limit_met?
|
583
|
+
RSpec.world.wants_to_quit = true
|
584
|
+
end
|
561
585
|
succeeded
|
562
586
|
end.all?
|
563
587
|
end
|
@@ -574,11 +598,6 @@ module RSpec
|
|
574
598
|
false
|
575
599
|
end
|
576
600
|
|
577
|
-
# @private
|
578
|
-
def self.fail_fast?
|
579
|
-
RSpec.configuration.fail_fast?
|
580
|
-
end
|
581
|
-
|
582
601
|
# @private
|
583
602
|
def self.declaration_line_numbers
|
584
603
|
@declaration_line_numbers ||= [metadata[:line_number]] +
|
@@ -671,6 +690,7 @@ module RSpec
|
|
671
690
|
super
|
672
691
|
end
|
673
692
|
end
|
693
|
+
# rubocop:enable Metrics/ClassLength
|
674
694
|
|
675
695
|
# @private
|
676
696
|
# Unnamed example group used by `SuiteHookContext`.
|
@@ -21,24 +21,28 @@ module RSpec
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def persist
|
24
|
-
|
24
|
+
RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name))
|
25
|
+
File.open(@file_name, File::RDWR | File::CREAT) do |f|
|
26
|
+
# lock the file while reading / persisting to avoid a race
|
27
|
+
# condition where parallel or unrelated spec runs race to
|
28
|
+
# update the same file
|
29
|
+
f.flock(File::LOCK_EX)
|
30
|
+
unparsed_previous_runs = f.read
|
31
|
+
f.rewind
|
32
|
+
f.write(dump_statuses(unparsed_previous_runs))
|
33
|
+
f.flush
|
34
|
+
f.truncate(f.pos)
|
35
|
+
end
|
25
36
|
end
|
26
37
|
|
27
38
|
private
|
28
39
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def dumped_statuses
|
40
|
+
def dump_statuses(unparsed_previous_runs)
|
41
|
+
statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs)
|
42
|
+
merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs)
|
35
43
|
ExampleStatusDumper.dump(merged_statuses)
|
36
44
|
end
|
37
45
|
|
38
|
-
def merged_statuses
|
39
|
-
ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs)
|
40
|
-
end
|
41
|
-
|
42
46
|
def statuses_from_this_run
|
43
47
|
@examples.map do |ex|
|
44
48
|
result = ex.execution_result
|
@@ -50,10 +54,6 @@ module RSpec
|
|
50
54
|
}
|
51
55
|
end
|
52
56
|
end
|
53
|
-
|
54
|
-
def statuses_from_previous_runs
|
55
|
-
self.class.load_from(@file_name)
|
56
|
-
end
|
57
57
|
end
|
58
58
|
|
59
59
|
# Merges together a list of example statuses from this run
|
@@ -130,7 +130,7 @@ module RSpec
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def sort_value_from(example)
|
133
|
-
file, scoped_id = example.fetch(:example_id)
|
133
|
+
file, scoped_id = Example.parse_id(example.fetch(:example_id))
|
134
134
|
[file, *scoped_id.split(":").map(&method(:Integer))]
|
135
135
|
end
|
136
136
|
end
|
@@ -73,6 +73,7 @@ module RSpec::Core::Formatters
|
|
73
73
|
autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter'
|
74
74
|
autoload :JsonFormatter, 'rspec/core/formatters/json_formatter'
|
75
75
|
autoload :BisectFormatter, 'rspec/core/formatters/bisect_formatter'
|
76
|
+
autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter'
|
76
77
|
|
77
78
|
# Register the formatter class
|
78
79
|
# @param formatter_class [Class] formatter class to register
|
@@ -9,10 +9,14 @@ module RSpec
|
|
9
9
|
# We've named all events with a `bisect_` prefix to prevent naming collisions.
|
10
10
|
Formatters.register self, :bisect_starting, :bisect_original_run_complete,
|
11
11
|
:bisect_round_started, :bisect_individual_run_complete,
|
12
|
-
:
|
13
|
-
:bisect_failed, :bisect_aborted
|
12
|
+
:bisect_complete, :bisect_repro_command,
|
13
|
+
:bisect_failed, :bisect_aborted,
|
14
|
+
:bisect_round_ignoring_ids, :bisect_round_detected_multiple_culprits,
|
15
|
+
:bisect_dependency_check_started, :bisect_dependency_check_passed,
|
16
|
+
:bisect_dependency_check_failed
|
14
17
|
|
15
18
|
def bisect_starting(notification)
|
19
|
+
@round_count = 0
|
16
20
|
options = notification.original_cli_args.join(' ')
|
17
21
|
output.puts "Bisect started using options: #{options.inspect}"
|
18
22
|
output.print "Running suite to find failures..."
|
@@ -26,17 +30,35 @@ module RSpec
|
|
26
30
|
output.puts "Starting bisect with #{failures} and #{non_failures}."
|
27
31
|
end
|
28
32
|
|
33
|
+
def bisect_dependency_check_started(_notification)
|
34
|
+
output.print "Checking that failure(s) are order-dependent.."
|
35
|
+
end
|
36
|
+
|
37
|
+
def bisect_dependency_check_passed(_notification)
|
38
|
+
output.puts " failure appears to be order-dependent"
|
39
|
+
end
|
40
|
+
|
41
|
+
def bisect_dependency_check_failed(_notification)
|
42
|
+
output.puts " failure is not order-dependent"
|
43
|
+
end
|
44
|
+
|
29
45
|
def bisect_round_started(notification, include_trailing_space=true)
|
30
|
-
|
31
|
-
|
32
|
-
)
|
46
|
+
@round_count += 1
|
47
|
+
range_desc = notification.candidate_range.description
|
33
48
|
|
34
|
-
output.print "\nRound #{
|
35
|
-
" (of #{notification.remaining_count}) to ignore:"
|
49
|
+
output.print "\nRound #{@round_count}: bisecting over non-failing #{range_desc}"
|
36
50
|
output.print " " if include_trailing_space
|
37
51
|
end
|
38
52
|
|
39
|
-
def
|
53
|
+
def bisect_round_ignoring_ids(notification)
|
54
|
+
range_desc = notification.ignore_range.description
|
55
|
+
|
56
|
+
output.print " ignoring #{range_desc}"
|
57
|
+
output.print " (#{Helpers.format_duration(notification.duration)})"
|
58
|
+
end
|
59
|
+
|
60
|
+
def bisect_round_detected_multiple_culprits(notification)
|
61
|
+
output.print " multiple culprits detected - splitting candidates"
|
40
62
|
output.print " (#{Helpers.format_duration(notification.duration)})"
|
41
63
|
end
|
42
64
|
|
@@ -71,7 +93,7 @@ module RSpec
|
|
71
93
|
# Designed to provide details for us when we need to troubleshoot bisect bugs.
|
72
94
|
class BisectDebugFormatter < BisectProgressFormatter
|
73
95
|
Formatters.register self, :bisect_original_run_complete, :bisect_individual_run_start,
|
74
|
-
:bisect_individual_run_complete, :
|
96
|
+
:bisect_individual_run_complete, :bisect_round_ignoring_ids
|
75
97
|
|
76
98
|
def bisect_original_run_complete(notification)
|
77
99
|
output.puts " (#{Helpers.format_duration(notification.duration)})"
|
@@ -88,20 +110,27 @@ module RSpec
|
|
88
110
|
output.print " (#{Helpers.format_duration(notification.duration)})"
|
89
111
|
end
|
90
112
|
|
91
|
-
def
|
92
|
-
|
113
|
+
def bisect_dependency_check_passed(_notification)
|
114
|
+
output.print "\n - Failure appears to be order-dependent"
|
93
115
|
end
|
94
116
|
|
95
|
-
def
|
96
|
-
output.print "\n -
|
97
|
-
super
|
117
|
+
def bisect_dependency_check_failed(_notification)
|
118
|
+
output.print "\n - Failure is not order-dependent"
|
98
119
|
end
|
99
120
|
|
100
|
-
def
|
121
|
+
def bisect_round_started(notification)
|
122
|
+
super(notification, false)
|
123
|
+
end
|
124
|
+
|
125
|
+
def bisect_round_ignoring_ids(notification)
|
101
126
|
output.print "\n - #{describe_ids 'Examples we can safely ignore', notification.ids_to_ignore}"
|
102
127
|
output.print "\n - #{describe_ids 'Remaining non-failing examples', notification.remaining_ids}"
|
103
128
|
end
|
104
129
|
|
130
|
+
def bisect_round_detected_multiple_culprits(_notification)
|
131
|
+
output.print "\n - Multiple culprits detected - splitting candidates"
|
132
|
+
end
|
133
|
+
|
105
134
|
private
|
106
135
|
|
107
136
|
def describe_ids(description, ids)
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
RSpec::Support.require_rspec_core "formatters/snippet_extractor"
|
3
|
+
RSpec::Support.require_rspec_support "encoded_string"
|
4
|
+
|
1
5
|
module RSpec
|
2
6
|
module Core
|
3
7
|
module Formatters
|
@@ -11,7 +15,7 @@ module RSpec
|
|
11
15
|
@exception = exception
|
12
16
|
@example = example
|
13
17
|
@message_color = options.fetch(:message_color) { RSpec.configuration.failure_color }
|
14
|
-
@description = options.fetch(:
|
18
|
+
@description = options.fetch(:description) { example.full_description }
|
15
19
|
@detail_formatter = options.fetch(:detail_formatter) { Proc.new {} }
|
16
20
|
@extra_detail_formatter = options.fetch(:extra_detail_formatter) { Proc.new {} }
|
17
21
|
@backtrace_formatter = options.fetch(:backtrace_formatter) { RSpec.configuration.backtrace_formatter }
|
@@ -30,8 +34,36 @@ module RSpec
|
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
|
-
def formatted_backtrace
|
34
|
-
backtrace_formatter.format_backtrace(
|
37
|
+
def formatted_backtrace(exception=@exception)
|
38
|
+
backtrace_formatter.format_backtrace((exception.backtrace || []), example.metadata) +
|
39
|
+
formatted_cause(exception)
|
40
|
+
end
|
41
|
+
|
42
|
+
if RSpec::Support::RubyFeatures.supports_exception_cause?
|
43
|
+
def formatted_cause(exception)
|
44
|
+
last_cause = final_exception(exception)
|
45
|
+
cause = []
|
46
|
+
|
47
|
+
if exception.cause
|
48
|
+
cause << '------------------'
|
49
|
+
cause << '--- Caused by: ---'
|
50
|
+
cause << "#{exception_class_name(last_cause)}:" unless exception_class_name(last_cause) =~ /RSpec/
|
51
|
+
|
52
|
+
encoded_string(last_cause.message.to_s).split("\n").each do |line|
|
53
|
+
cause << " #{line}"
|
54
|
+
end
|
55
|
+
|
56
|
+
cause << (" #{backtrace_formatter.format_backtrace(last_cause.backtrace, example.metadata).first}")
|
57
|
+
end
|
58
|
+
|
59
|
+
cause
|
60
|
+
end
|
61
|
+
else
|
62
|
+
# :nocov:
|
63
|
+
def formatted_cause(_)
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
# :nocov:
|
35
67
|
end
|
36
68
|
|
37
69
|
def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
|
@@ -41,24 +73,31 @@ module RSpec
|
|
41
73
|
end
|
42
74
|
|
43
75
|
def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
|
44
|
-
|
45
|
-
|
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)}"
|
76
|
+
lines = fully_formatted_lines(failure_number, colorizer)
|
77
|
+
lines.join("\n") << "\n"
|
50
78
|
end
|
51
79
|
|
52
|
-
def
|
53
|
-
|
80
|
+
def fully_formatted_lines(failure_number, colorizer)
|
81
|
+
lines = [
|
82
|
+
description,
|
83
|
+
detail_formatter.call(example, colorizer),
|
84
|
+
formatted_message_and_backtrace(colorizer),
|
85
|
+
extra_detail_formatter.call(failure_number, colorizer),
|
86
|
+
].compact.flatten
|
87
|
+
|
88
|
+
lines = indent_lines(lines, failure_number)
|
89
|
+
lines.unshift("")
|
90
|
+
lines
|
54
91
|
end
|
55
92
|
|
56
93
|
private
|
57
94
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
95
|
+
def final_exception(exception)
|
96
|
+
if exception.cause
|
97
|
+
final_exception(exception.cause)
|
98
|
+
else
|
99
|
+
exception
|
100
|
+
end
|
62
101
|
end
|
63
102
|
|
64
103
|
if String.method_defined?(:encoding)
|
@@ -80,23 +119,71 @@ module RSpec
|
|
80
119
|
# :nocov:
|
81
120
|
end
|
82
121
|
|
83
|
-
def
|
122
|
+
def indent_lines(lines, failure_number)
|
123
|
+
alignment_basis = "#{' ' * @indentation}#{failure_number}) "
|
124
|
+
indentation = ' ' * alignment_basis.length
|
125
|
+
|
126
|
+
lines.each_with_index.map do |line, index|
|
127
|
+
if index == 0
|
128
|
+
"#{alignment_basis}#{line}"
|
129
|
+
elsif line.empty?
|
130
|
+
line
|
131
|
+
else
|
132
|
+
"#{indentation}#{line}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def exception_class_name(exception=@exception)
|
84
138
|
name = exception.class.name.to_s
|
85
139
|
name = "(anonymous error class)" if name == ''
|
86
140
|
name
|
87
141
|
end
|
88
142
|
|
89
143
|
def failure_lines
|
90
|
-
@failure_lines ||=
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
lines << " #{line}"
|
97
|
-
end
|
98
|
-
lines
|
144
|
+
@failure_lines ||= [].tap do |lines|
|
145
|
+
lines.concat(failure_slash_error_lines)
|
146
|
+
|
147
|
+
sections = [failure_slash_error_lines, exception_lines]
|
148
|
+
if sections.any? { |section| section.size > 1 } && !exception_lines.first.empty?
|
149
|
+
lines << ''
|
99
150
|
end
|
151
|
+
|
152
|
+
lines.concat(exception_lines)
|
153
|
+
lines.concat(extra_failure_lines)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def failure_slash_error_lines
|
158
|
+
lines = read_failed_lines
|
159
|
+
if lines.count == 1
|
160
|
+
lines[0] = "Failure/Error: #{lines[0].strip}"
|
161
|
+
else
|
162
|
+
least_indentation = SnippetExtractor.least_indentation_from(lines)
|
163
|
+
lines = lines.map { |line| line.sub(/^#{least_indentation}/, ' ') }
|
164
|
+
lines.unshift('Failure/Error:')
|
165
|
+
end
|
166
|
+
lines
|
167
|
+
end
|
168
|
+
|
169
|
+
def exception_lines
|
170
|
+
lines = []
|
171
|
+
lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/
|
172
|
+
encoded_string(exception.message.to_s).split("\n").each do |line|
|
173
|
+
lines << (line.empty? ? line : " #{line}")
|
174
|
+
end
|
175
|
+
lines
|
176
|
+
end
|
177
|
+
|
178
|
+
def extra_failure_lines
|
179
|
+
@extra_failure_lines ||= begin
|
180
|
+
lines = Array(example.metadata[:extra_failure_lines])
|
181
|
+
unless lines.empty?
|
182
|
+
lines.unshift('')
|
183
|
+
lines.push('')
|
184
|
+
end
|
185
|
+
lines
|
186
|
+
end
|
100
187
|
end
|
101
188
|
|
102
189
|
def add_shared_group_lines(lines, colorizer)
|
@@ -109,42 +196,41 @@ module RSpec
|
|
109
196
|
lines
|
110
197
|
end
|
111
198
|
|
112
|
-
def
|
199
|
+
def read_failed_lines
|
113
200
|
matching_line = find_failed_line
|
114
201
|
unless matching_line
|
115
|
-
return "Unable to find matching line from backtrace"
|
202
|
+
return ["Unable to find matching line from backtrace"]
|
116
203
|
end
|
117
204
|
|
118
205
|
file_path, line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)[1..2]
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
206
|
+
max_line_count = RSpec.configuration.max_displayed_failure_line_count
|
207
|
+
lines = SnippetExtractor.extract_expression_lines_at(file_path, line_number.to_i, max_line_count)
|
208
|
+
RSpec.world.source_cache.syntax_highlighter.highlight(lines)
|
209
|
+
rescue SnippetExtractor::NoSuchFileError
|
210
|
+
["Unable to find #{file_path} to read failed line"]
|
211
|
+
rescue SnippetExtractor::NoSuchLineError
|
212
|
+
["Unable to find matching line in #{file_path}"]
|
126
213
|
rescue SecurityError
|
127
|
-
"Unable to read failed line"
|
214
|
+
["Unable to read failed line"]
|
128
215
|
end
|
129
216
|
|
130
217
|
def find_failed_line
|
131
|
-
|
218
|
+
line_regex = RSpec.configuration.in_project_source_dir_regex
|
219
|
+
loaded_spec_files = RSpec.configuration.loaded_spec_files
|
220
|
+
|
132
221
|
exception_backtrace.find do |line|
|
133
222
|
next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1])
|
134
|
-
File.expand_path(line_path)
|
135
|
-
|
223
|
+
path = File.expand_path(line_path)
|
224
|
+
loaded_spec_files.include?(path) || path =~ line_regex
|
225
|
+
end || exception_backtrace.first
|
136
226
|
end
|
137
227
|
|
138
|
-
def formatted_message_and_backtrace(colorizer
|
228
|
+
def formatted_message_and_backtrace(colorizer)
|
139
229
|
lines = colorized_message_lines(colorizer) + colorized_formatted_backtrace(colorizer)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
lines.each do |line|
|
144
|
-
formatted << RSpec::Support::EncodedString.new("#{indentation}#{line}\n", encoding_of(formatted))
|
230
|
+
encoding = encoding_of("")
|
231
|
+
lines.map do |line|
|
232
|
+
RSpec::Support::EncodedString.new(line, encoding)
|
145
233
|
end
|
146
|
-
|
147
|
-
formatted
|
148
234
|
end
|
149
235
|
|
150
236
|
def exception_backtrace
|
@@ -180,9 +266,9 @@ module RSpec
|
|
180
266
|
def pending_options
|
181
267
|
if @execution_result.pending_fixed?
|
182
268
|
{
|
183
|
-
:
|
184
|
-
:message_color
|
185
|
-
:failure_lines
|
269
|
+
:description => "#{@example.full_description} FIXED",
|
270
|
+
:message_color => RSpec.configuration.fixed_color,
|
271
|
+
:failure_lines => [
|
186
272
|
"Expected pending '#{@execution_result.pending_message}' to fail. No Error was raised."
|
187
273
|
]
|
188
274
|
}
|
@@ -205,8 +291,6 @@ module RSpec
|
|
205
291
|
options[:message_color])
|
206
292
|
)
|
207
293
|
|
208
|
-
options[:description_formatter] &&= Proc.new {}
|
209
|
-
|
210
294
|
return options unless exception.aggregation_metadata[:hide_backtrace]
|
211
295
|
options[:backtrace_formatter] = EmptyBacktraceFormatter
|
212
296
|
options
|
@@ -217,7 +301,7 @@ module RSpec
|
|
217
301
|
end
|
218
302
|
|
219
303
|
def multiple_exception_summarizer(exception, prior_detail_formatter, color)
|
220
|
-
lambda do |example, colorizer
|
304
|
+
lambda do |example, colorizer|
|
221
305
|
summary = if exception.aggregation_metadata[:hide_backtrace]
|
222
306
|
# Since the backtrace is hidden, the subfailures will come
|
223
307
|
# immediately after this, and using `:` will read well.
|
@@ -230,27 +314,30 @@ module RSpec
|
|
230
314
|
|
231
315
|
summary = colorizer.wrap(summary, color || RSpec.configuration.failure_color)
|
232
316
|
return summary unless prior_detail_formatter
|
233
|
-
|
317
|
+
[
|
318
|
+
prior_detail_formatter.call(example, colorizer),
|
319
|
+
summary
|
320
|
+
]
|
234
321
|
end
|
235
322
|
end
|
236
323
|
|
237
324
|
def sub_failure_list_formatter(exception, message_color)
|
238
325
|
common_backtrace_truncater = CommonBacktraceTruncater.new(exception)
|
239
326
|
|
240
|
-
lambda do |failure_number, colorizer
|
241
|
-
exception.all_exceptions.each_with_index
|
327
|
+
lambda do |failure_number, colorizer|
|
328
|
+
FlatMap.flat_map(exception.all_exceptions.each_with_index) do |failure, index|
|
242
329
|
options = with_multiple_error_options_as_needed(
|
243
330
|
failure,
|
244
|
-
:
|
245
|
-
:indentation =>
|
331
|
+
:description => nil,
|
332
|
+
:indentation => 0,
|
246
333
|
:message_color => message_color || RSpec.configuration.failure_color,
|
247
334
|
:skip_shared_group_trace => true
|
248
335
|
)
|
249
336
|
|
250
337
|
failure = common_backtrace_truncater.with_truncated_backtrace(failure)
|
251
338
|
presenter = ExceptionPresenter.new(failure, @example, options)
|
252
|
-
presenter.
|
253
|
-
end
|
339
|
+
presenter.fully_formatted_lines("#{failure_number}.#{index + 1}", colorizer)
|
340
|
+
end
|
254
341
|
end
|
255
342
|
end
|
256
343
|
|