rspec-core 3.3.0 → 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/.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)
|