rspec-core 3.3.2 → 3.4.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 +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
|
|