rspec-core 3.3.0 → 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/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +88 -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 +55 -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/bisect_progress_formatter.rb +44 -15
- data/lib/rspec/core/formatters/exception_presenter.rb +150 -59
- data/lib/rspec/core/formatters/helpers.rb +1 -1
- data/lib/rspec/core/formatters/html_formatter.rb +3 -3
- 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/formatters.rb +2 -1
- 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/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/source.rb +76 -0
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/world.rb +25 -6
- data.tar.gz.sig +0 -0
- metadata +14 -11
- 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
|
@@ -558,6 +605,7 @@ module RSpec
|
|
558
605
|
class SuiteHookContext < Example
|
559
606
|
def initialize
|
560
607
|
super(AnonymousExampleGroup, "", {})
|
608
|
+
@example_group_instance = AnonymousExampleGroup.new
|
561
609
|
end
|
562
610
|
|
563
611
|
# rubocop:disable Style/AccessorMethodName
|
@@ -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
|
@@ -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)
|