rspec-core 3.2.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +75 -0
  5. data/README.md +137 -20
  6. data/lib/rspec/autorun.rb +1 -0
  7. data/lib/rspec/core.rb +8 -16
  8. data/lib/rspec/core/backtrace_formatter.rb +1 -3
  9. data/lib/rspec/core/bisect/coordinator.rb +66 -0
  10. data/lib/rspec/core/bisect/example_minimizer.rb +130 -0
  11. data/lib/rspec/core/bisect/runner.rb +139 -0
  12. data/lib/rspec/core/bisect/server.rb +61 -0
  13. data/lib/rspec/core/bisect/subset_enumerator.rb +39 -0
  14. data/lib/rspec/core/configuration.rb +134 -5
  15. data/lib/rspec/core/configuration_options.rb +21 -10
  16. data/lib/rspec/core/example.rb +84 -50
  17. data/lib/rspec/core/example_group.rb +46 -18
  18. data/lib/rspec/core/example_status_persister.rb +235 -0
  19. data/lib/rspec/core/filter_manager.rb +43 -28
  20. data/lib/rspec/core/flat_map.rb +2 -0
  21. data/lib/rspec/core/formatters.rb +30 -20
  22. data/lib/rspec/core/formatters/base_text_formatter.rb +1 -0
  23. data/lib/rspec/core/formatters/bisect_formatter.rb +68 -0
  24. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +115 -0
  25. data/lib/rspec/core/formatters/deprecation_formatter.rb +0 -1
  26. data/lib/rspec/core/formatters/documentation_formatter.rb +0 -4
  27. data/lib/rspec/core/formatters/exception_presenter.rb +389 -0
  28. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  29. data/lib/rspec/core/formatters/helpers.rb +22 -2
  30. data/lib/rspec/core/formatters/html_formatter.rb +1 -4
  31. data/lib/rspec/core/formatters/html_printer.rb +2 -6
  32. data/lib/rspec/core/formatters/json_formatter.rb +6 -4
  33. data/lib/rspec/core/formatters/snippet_extractor.rb +12 -7
  34. data/lib/rspec/core/hooks.rb +8 -2
  35. data/lib/rspec/core/memoized_helpers.rb +77 -17
  36. data/lib/rspec/core/metadata.rb +24 -10
  37. data/lib/rspec/core/metadata_filter.rb +16 -3
  38. data/lib/rspec/core/mutex.rb +63 -0
  39. data/lib/rspec/core/notifications.rb +84 -189
  40. data/lib/rspec/core/option_parser.rb +105 -32
  41. data/lib/rspec/core/ordering.rb +28 -25
  42. data/lib/rspec/core/profiler.rb +32 -0
  43. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +6 -1
  44. data/lib/rspec/core/rake_task.rb +6 -20
  45. data/lib/rspec/core/reentrant_mutex.rb +52 -0
  46. data/lib/rspec/core/reporter.rb +65 -17
  47. data/lib/rspec/core/runner.rb +38 -14
  48. data/lib/rspec/core/set.rb +49 -0
  49. data/lib/rspec/core/shared_example_group.rb +3 -1
  50. data/lib/rspec/core/shell_escape.rb +49 -0
  51. data/lib/rspec/core/version.rb +1 -1
  52. data/lib/rspec/core/world.rb +31 -20
  53. metadata +35 -7
  54. metadata.gz.sig +0 -0
  55. data/lib/rspec/core/backport_random.rb +0 -339
@@ -0,0 +1,130 @@
1
+ RSpec::Support.require_rspec_core "bisect/subset_enumerator"
2
+
3
+ module RSpec
4
+ module Core
5
+ module Bisect
6
+ # @private
7
+ # Contains the core bisect logic. Searches for examples we can ignore by
8
+ # repeatedly running different subsets of the suite.
9
+ class ExampleMinimizer
10
+ attr_reader :runner, :reporter, :all_example_ids, :failed_example_ids
11
+ attr_accessor :remaining_ids
12
+
13
+ def initialize(runner, reporter)
14
+ @runner = runner
15
+ @reporter = reporter
16
+ end
17
+
18
+ def find_minimal_repro
19
+ prep
20
+
21
+ self.remaining_ids = non_failing_example_ids
22
+
23
+ each_bisect_round do |subsets|
24
+ ids_to_ignore = subsets.find do |ids|
25
+ get_expected_failures_for?(remaining_ids - ids)
26
+ end
27
+
28
+ next :done unless ids_to_ignore
29
+
30
+ self.remaining_ids -= ids_to_ignore
31
+ notify(:bisect_ignoring_ids, :ids_to_ignore => ids_to_ignore, :remaining_ids => remaining_ids)
32
+ end
33
+
34
+ currently_needed_ids
35
+ end
36
+
37
+ def currently_needed_ids
38
+ remaining_ids + failed_example_ids
39
+ end
40
+
41
+ def repro_command_for_currently_needed_ids
42
+ return runner.repro_command_from(currently_needed_ids) if remaining_ids
43
+ "(Not yet enough information to provide any repro command)"
44
+ end
45
+
46
+ private
47
+
48
+ def prep
49
+ notify(:bisect_starting, :original_cli_args => runner.original_cli_args)
50
+
51
+ _, duration = track_duration do
52
+ original_results = runner.original_results
53
+ @all_example_ids = original_results.all_example_ids
54
+ @failed_example_ids = original_results.failed_example_ids
55
+ end
56
+
57
+ if @failed_example_ids.empty?
58
+ raise BisectFailedError, "\n\nNo failures found. Bisect only works " \
59
+ "in the presence of one or more failing examples."
60
+ else
61
+ notify(:bisect_original_run_complete, :failed_example_ids => failed_example_ids,
62
+ :non_failing_example_ids => non_failing_example_ids,
63
+ :duration => duration)
64
+ end
65
+ end
66
+
67
+ def non_failing_example_ids
68
+ @non_failing_example_ids ||= all_example_ids - failed_example_ids
69
+ end
70
+
71
+ def get_expected_failures_for?(ids)
72
+ ids_to_run = ids + failed_example_ids
73
+ notify(:bisect_individual_run_start, :command => runner.repro_command_from(ids_to_run))
74
+
75
+ results, duration = track_duration { runner.run(ids_to_run) }
76
+ notify(:bisect_individual_run_complete, :duration => duration, :results => results)
77
+
78
+ abort_if_ordering_inconsistent(results)
79
+ (failed_example_ids & results.failed_example_ids) == failed_example_ids
80
+ end
81
+
82
+ INFINITY = (1.0 / 0) # 1.8.7 doesn't define Float::INFINITY so we define our own...
83
+
84
+ def each_bisect_round(&block)
85
+ last_round, duration = track_duration do
86
+ 1.upto(INFINITY) do |round|
87
+ break if :done == bisect_round(round, &block)
88
+ end
89
+ end
90
+
91
+ notify(:bisect_complete, :round => last_round, :duration => duration,
92
+ :original_non_failing_count => non_failing_example_ids.size,
93
+ :remaining_count => remaining_ids.size)
94
+ end
95
+
96
+ def bisect_round(round)
97
+ value, duration = track_duration do
98
+ subsets = SubsetEnumerator.new(remaining_ids)
99
+ notify(:bisect_round_started, :round => round,
100
+ :subset_size => subsets.subset_size,
101
+ :remaining_count => remaining_ids.size)
102
+
103
+ yield subsets
104
+ end
105
+
106
+ notify(:bisect_round_finished, :duration => duration, :round => round)
107
+ value
108
+ end
109
+
110
+ def track_duration
111
+ start = ::RSpec::Core::Time.now
112
+ [yield, ::RSpec::Core::Time.now - start]
113
+ end
114
+
115
+ def abort_if_ordering_inconsistent(results)
116
+ expected_order = all_example_ids & results.all_example_ids
117
+ return if expected_order == results.all_example_ids
118
+
119
+ raise BisectFailedError, "\n\nThe example ordering is inconsistent. " \
120
+ "`--bisect` relies upon consistent ordering (e.g. by passing " \
121
+ "`--seed` if you're using random ordering) to work properly."
122
+ end
123
+
124
+ def notify(*args)
125
+ reporter.publish(*args)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,139 @@
1
+ RSpec::Support.require_rspec_core "shell_escape"
2
+ require 'open3'
3
+
4
+ module RSpec
5
+ module Core
6
+ module Bisect
7
+ # Provides an API to run the suite for a set of locations, using
8
+ # the given bisect server to capture the results.
9
+ # @private
10
+ class Runner
11
+ attr_reader :original_cli_args
12
+
13
+ def initialize(server, original_cli_args)
14
+ @server = server
15
+ @original_cli_args = original_cli_args.reject { |arg| arg.start_with?("--bisect") }
16
+ end
17
+
18
+ def run(locations)
19
+ run_locations(locations, original_results.failed_example_ids)
20
+ end
21
+
22
+ def command_for(locations)
23
+ parts = []
24
+
25
+ parts << RUBY << load_path
26
+ parts << open3_safe_escape(RSpec::Core.path_to_executable)
27
+
28
+ parts << "--format" << "bisect"
29
+ parts << "--drb-port" << @server.drb_port
30
+ parts.concat reusable_cli_options
31
+ parts.concat locations.map { |l| open3_safe_escape(l) }
32
+
33
+ parts.join(" ")
34
+ end
35
+
36
+ def repro_command_from(locations)
37
+ parts = []
38
+
39
+ parts << "rspec"
40
+ parts.concat Formatters::Helpers.organize_ids(locations)
41
+ parts.concat original_cli_args_without_locations
42
+
43
+ parts.join(" ")
44
+ end
45
+
46
+ def original_results
47
+ @original_results ||= run_locations(original_locations)
48
+ end
49
+
50
+ private
51
+
52
+ include RSpec::Core::ShellEscape
53
+ # On JRuby, Open3.popen3 does not handle shellescaped args properly:
54
+ # https://github.com/jruby/jruby/issues/2767
55
+ if RSpec::Support::Ruby.jruby?
56
+ # :nocov:
57
+ alias open3_safe_escape quote
58
+ # :nocov:
59
+ else
60
+ alias open3_safe_escape escape
61
+ end
62
+
63
+ def run_locations(locations, *capture_args)
64
+ @server.capture_run_results(*capture_args) do
65
+ run_command command_for(locations)
66
+ end
67
+ end
68
+
69
+ # `Open3.capture2e` does not work on JRuby:
70
+ # https://github.com/jruby/jruby/issues/2766
71
+ if Open3.respond_to?(:capture2e) && !RSpec::Support::Ruby.jruby?
72
+ def run_command(cmd)
73
+ Open3.capture2e(cmd).first
74
+ end
75
+ else # for 1.8.7
76
+ # :nocov:
77
+ def run_command(cmd)
78
+ out = err = nil
79
+
80
+ Open3.popen3(cmd) do |_, stdout, stderr|
81
+ # Reading the streams blocks until the process is complete
82
+ out = stdout.read
83
+ err = stderr.read
84
+ end
85
+
86
+ "Stdout:\n#{out}\n\nStderr:\n#{err}"
87
+ end
88
+ # :nocov:
89
+ end
90
+
91
+ def reusable_cli_options
92
+ @reusable_cli_options ||= begin
93
+ opts = original_cli_args_without_locations
94
+
95
+ if (port = parsed_original_cli_options[:drb_port])
96
+ opts -= %W[ --drb-port #{port} ]
97
+ end
98
+
99
+ parsed_original_cli_options.fetch(:formatters) { [] }.each do |(name, out)|
100
+ opts -= %W[ --format #{name} -f -f#{name} ]
101
+ opts -= %W[ --out #{out} -o -o#{out} ]
102
+ end
103
+
104
+ opts
105
+ end
106
+ end
107
+
108
+ def original_cli_args_without_locations
109
+ @original_cli_args_without_locations ||= begin
110
+ files_or_dirs = parsed_original_cli_options.fetch(:files_or_directories_to_run)
111
+ @original_cli_args - files_or_dirs
112
+ end
113
+ end
114
+
115
+ def parsed_original_cli_options
116
+ @parsed_original_cli_options ||= Parser.parse(@original_cli_args)
117
+ end
118
+
119
+ def original_locations
120
+ parsed_original_cli_options.fetch(:files_or_directories_to_run)
121
+ end
122
+
123
+ def load_path
124
+ @load_path ||= "-I#{$LOAD_PATH.map { |p| open3_safe_escape(p) }.join(':')}"
125
+ end
126
+
127
+ # Path to the currently running Ruby executable, borrowed from Rake:
128
+ # https://github.com/ruby/rake/blob/v10.4.2/lib/rake/file_utils.rb#L8-L12
129
+ # Note that we skip `ENV['RUBY']` because we don't have to deal with running
130
+ # RSpec from within a MRI source repository:
131
+ # https://github.com/ruby/rake/commit/968682759b3b65e42748cd2befb2ff3e982272d9
132
+ RUBY = File.join(
133
+ RbConfig::CONFIG['bindir'],
134
+ RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']).
135
+ sub(/.*\s.*/m, '"\&"')
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,61 @@
1
+ require 'drb/drb'
2
+ require 'drb/acl'
3
+
4
+ module RSpec
5
+ module Core
6
+ # @private
7
+ module Bisect
8
+ # @private
9
+ BisectFailedError = Class.new(StandardError)
10
+
11
+ # @private
12
+ # A DRb server that receives run results from a separate RSpec process
13
+ # started by the bisect process.
14
+ class Server
15
+ def self.run
16
+ server = new
17
+ server.start
18
+ yield server
19
+ ensure
20
+ server.stop
21
+ end
22
+
23
+ def capture_run_results(expected_failures=[])
24
+ self.expected_failures = expected_failures
25
+ self.latest_run_results = nil
26
+ run_output = yield
27
+ latest_run_results || raise_bisect_failed(run_output)
28
+ end
29
+
30
+ def start
31
+ # Only allow remote DRb requests from this machine.
32
+ DRb.install_acl ACL.new(%w[ deny all allow localhost allow 127.0.0.1 ])
33
+
34
+ # We pass `nil` as the first arg to allow it to pick a DRb port.
35
+ @drb = DRb.start_service(nil, self)
36
+ end
37
+
38
+ def stop
39
+ @drb.stop_service
40
+ end
41
+
42
+ def drb_port
43
+ @drb_port ||= Integer(@drb.uri[/\d+$/])
44
+ end
45
+
46
+ # Fetched via DRb by the BisectFormatter to determine when to abort.
47
+ attr_accessor :expected_failures
48
+
49
+ # Set via DRb by the BisectFormatter with the results of the run.
50
+ attr_accessor :latest_run_results
51
+
52
+ private
53
+
54
+ def raise_bisect_failed(run_output)
55
+ raise BisectFailedError, "Failed to get results from the spec " \
56
+ "run. Spec run output:\n\n#{run_output}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,39 @@
1
+ module RSpec
2
+ module Core
3
+ module Bisect
4
+ # Enumerates each subset of the given list of ids that is half the
5
+ # size of the total list, so that hopefully we can discard half the
6
+ # list each repeatedly in order to our minimal repro case.
7
+ # @private
8
+ class SubsetEnumerator
9
+ include Enumerable
10
+
11
+ def initialize(ids)
12
+ @ids = ids
13
+ end
14
+
15
+ def subset_size
16
+ @subset_size ||= (@ids.size / 2.0).ceil
17
+ end
18
+
19
+ def each
20
+ yielded = Set.new
21
+ slice_size = subset_size
22
+ combo_count = 1
23
+
24
+ while slice_size > 0
25
+ @ids.each_slice(slice_size).to_a.combination(combo_count) do |combos|
26
+ subset = combos.flatten
27
+ next if yielded.include?(subset)
28
+ yield subset
29
+ yielded << subset
30
+ end
31
+
32
+ slice_size /= 2
33
+ combo_count *= 2
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -154,6 +154,33 @@ module RSpec
154
154
  end
155
155
  end
156
156
 
157
+ # @macro define_reader
158
+ # The file path to use for persisting example statuses. Necessary for the
159
+ # `--only-failures` and `--next-failures` CLI options.
160
+ #
161
+ # @overload example_status_persistence_file_path
162
+ # @return [String] the file path
163
+ # @overload example_status_persistence_file_path=(value)
164
+ # @param value [String] the file path
165
+ define_reader :example_status_persistence_file_path
166
+
167
+ # Sets the file path to use for persisting example statuses. Necessary for the
168
+ # `--only-failures` and `--next-failures` CLI options.
169
+ def example_status_persistence_file_path=(value)
170
+ @example_status_persistence_file_path = value
171
+ clear_values_derived_from_example_status_persistence_file_path
172
+ end
173
+
174
+ # @macro define_reader
175
+ # Indicates if the `--only-failures` (or `--next-failure`) flag is being used.
176
+ define_reader :only_failures
177
+ alias_method :only_failures?, :only_failures
178
+
179
+ # @private
180
+ def only_failures_but_not_configured?
181
+ only_failures? && !example_status_persistence_file_path
182
+ end
183
+
157
184
  # @macro add_setting
158
185
  # Clean up and exit after the first failure (default: `false`).
159
186
  add_setting :fail_fast
@@ -281,6 +308,11 @@ module RSpec
281
308
  # Record the start time of the spec suite to measure load time.
282
309
  add_setting :start_time
283
310
 
311
+ # @macro add_setting
312
+ # Use threadsafe options where available.
313
+ # Currently this will place a mutex around memoized values such as let blocks.
314
+ add_setting :threadsafe
315
+
284
316
  # @private
285
317
  add_setting :tty
286
318
  # @private
@@ -334,6 +366,9 @@ module RSpec
334
366
  @requires = []
335
367
  @libs = []
336
368
  @derived_metadata_blocks = FilterableItemRepository::QueryOptimized.new(:any?)
369
+ @threadsafe = true
370
+
371
+ define_built_in_hooks
337
372
  end
338
373
 
339
374
  # @private
@@ -342,6 +377,9 @@ module RSpec
342
377
  def force(hash)
343
378
  ordering_manager.force(hash)
344
379
  @preferred_options.merge!(hash)
380
+
381
+ return unless hash.key?(:example_status_persistence_file_path)
382
+ clear_values_derived_from_example_status_persistence_file_path
345
383
  end
346
384
 
347
385
  # @private
@@ -600,6 +638,13 @@ module RSpec
600
638
  framework
601
639
  when :rspec
602
640
  require 'rspec/expectations'
641
+
642
+ # Tag this exception class so our exception formatting logic knows
643
+ # that it satisfies the `MultipleExceptionError` interface.
644
+ ::RSpec::Expectations::MultipleExpectationsNotMetError.__send__(
645
+ :include, MultipleExceptionError::InterfaceTag
646
+ )
647
+
603
648
  ::RSpec::Matchers
604
649
  when :test_unit
605
650
  require 'rspec/core/test_unit_assertions_adapter'
@@ -792,7 +837,11 @@ module RSpec
792
837
  # @private
793
838
  def files_or_directories_to_run=(*files)
794
839
  files = files.flatten
795
- files << default_path if (command == 'rspec' || Runner.running_in_drb?) && default_path && files.empty?
840
+
841
+ if (command == 'rspec' || Runner.running_in_drb?) && default_path && files.empty?
842
+ files << default_path
843
+ end
844
+
796
845
  @files_or_directories_to_run = files
797
846
  @files_to_run = nil
798
847
  end
@@ -803,6 +852,40 @@ module RSpec
803
852
  @files_to_run ||= get_files_to_run(@files_or_directories_to_run)
804
853
  end
805
854
 
855
+ # @private
856
+ def last_run_statuses
857
+ @last_run_statuses ||= Hash.new(UNKNOWN_STATUS).tap do |statuses|
858
+ if (path = example_status_persistence_file_path)
859
+ begin
860
+ ExampleStatusPersister.load_from(path).inject(statuses) do |hash, example|
861
+ hash[example.fetch(:example_id)] = example.fetch(:status)
862
+ hash
863
+ end
864
+ rescue SystemCallError => e
865
+ RSpec.warning "Could not read from #{path.inspect} (configured as " \
866
+ "`config.example_status_persistence_file_path`) due " \
867
+ "to a system error: #{e.inspect}. Please check that " \
868
+ "the config option is set to an accessible, valid " \
869
+ "file path", :call_site => nil
870
+ end
871
+ end
872
+ end
873
+ end
874
+
875
+ # @private
876
+ UNKNOWN_STATUS = "unknown".freeze
877
+
878
+ # @private
879
+ FAILED_STATUS = "failed".freeze
880
+
881
+ # @private
882
+ def spec_files_with_failures
883
+ @spec_files_with_failures ||= last_run_statuses.inject(Set.new) do |files, (id, status)|
884
+ files << id.split(ON_SQUARE_BRACKETS).first if status == FAILED_STATUS
885
+ files
886
+ end.to_a
887
+ end
888
+
806
889
  # Creates a method that delegates to `example` including the submitted
807
890
  # `args`. Used internally to add variants of `example` like `pending`:
808
891
  # @param name [String] example name alias
@@ -1060,6 +1143,7 @@ module RSpec
1060
1143
  def include(mod, *filters)
1061
1144
  meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
1062
1145
  @include_modules.append(mod, meta)
1146
+ configure_existing_groups(mod, meta, :safe_include)
1063
1147
  end
1064
1148
 
1065
1149
  # Tells RSpec to extend example groups with `mod`. Methods defined in
@@ -1095,6 +1179,7 @@ module RSpec
1095
1179
  def extend(mod, *filters)
1096
1180
  meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
1097
1181
  @extend_modules.append(mod, meta)
1182
+ configure_existing_groups(mod, meta, :safe_extend)
1098
1183
  end
1099
1184
 
1100
1185
  if RSpec::Support::RubyFeatures.module_prepends_supported?
@@ -1133,6 +1218,7 @@ module RSpec
1133
1218
  def prepend(mod, *filters)
1134
1219
  meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering)
1135
1220
  @prepend_modules.append(mod, meta)
1221
+ configure_existing_groups(mod, meta, :safe_prepend)
1136
1222
  end
1137
1223
  end
1138
1224
 
@@ -1153,17 +1239,30 @@ module RSpec
1153
1239
  end
1154
1240
  end
1155
1241
 
1242
+ # @private
1243
+ def configure_existing_groups(mod, meta, application_method)
1244
+ RSpec.world.all_example_groups.each do |group|
1245
+ next unless meta.empty? || MetadataFilter.apply?(:any?, meta, group.metadata)
1246
+ __send__(application_method, mod, group)
1247
+ end
1248
+ end
1249
+
1156
1250
  # @private
1157
1251
  #
1158
1252
  # Used internally to extend the singleton class of a single example's
1159
1253
  # example group instance with modules using `include` and/or `extend`.
1160
1254
  def configure_example(example)
1255
+ singleton_group = example.example_group_instance.singleton_class
1256
+
1161
1257
  # We replace the metadata so that SharedExampleGroupModule#included
1162
1258
  # has access to the example's metadata[:location].
1163
- example.example_group_instance.singleton_class.with_replaced_metadata(example.metadata) do
1164
- @include_modules.items_for(example.metadata).each do |mod|
1259
+ singleton_group.with_replaced_metadata(example.metadata) do
1260
+ modules = @include_modules.items_for(example.metadata)
1261
+ modules.each do |mod|
1165
1262
  safe_include(mod, example.example_group_instance.singleton_class)
1166
1263
  end
1264
+
1265
+ MemoizedHelpers.define_helpers_on(singleton_group) unless modules.empty?
1167
1266
  end
1168
1267
  end
1169
1268
 
@@ -1193,7 +1292,8 @@ module RSpec
1193
1292
  def safe_extend(mod, host)
1194
1293
  host.extend(mod) unless host.singleton_class < mod
1195
1294
  end
1196
- else
1295
+ else # for 1.8.7
1296
+ # :nocov:
1197
1297
  # @private
1198
1298
  def safe_include(mod, host)
1199
1299
  host.__send__(:include, mod) unless host.included_modules.include?(mod)
@@ -1203,6 +1303,7 @@ module RSpec
1203
1303
  def safe_extend(mod, host)
1204
1304
  host.extend(mod) unless (class << host; self; end).included_modules.include?(mod)
1205
1305
  end
1306
+ # :nocov:
1206
1307
  end
1207
1308
 
1208
1309
  # @private
@@ -1560,10 +1661,15 @@ module RSpec
1560
1661
  end
1561
1662
 
1562
1663
  def get_files_to_run(paths)
1563
- FlatMap.flat_map(paths_to_check(paths)) do |path|
1664
+ files = FlatMap.flat_map(paths_to_check(paths)) do |path|
1564
1665
  path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
1565
1666
  File.directory?(path) ? gather_directories(path) : extract_location(path)
1566
1667
  end.sort.uniq
1668
+
1669
+ return files unless only_failures?
1670
+ relative_files = files.map { |f| Metadata.relative_path(File.expand_path f) }
1671
+ intersection = (relative_files & spec_files_with_failures.to_a)
1672
+ intersection.empty? ? files : intersection
1567
1673
  end
1568
1674
 
1569
1675
  def paths_to_check(paths)
@@ -1592,6 +1698,7 @@ module RSpec
1592
1698
  end
1593
1699
 
1594
1700
  if RSpec::Support::OS.windows?
1701
+ # :nocov:
1595
1702
  def absolute_pattern?(pattern)
1596
1703
  pattern =~ /\A[A-Z]:\\/ || windows_absolute_network_path?(pattern)
1597
1704
  end
@@ -1600,12 +1707,16 @@ module RSpec
1600
1707
  return false unless ::File::ALT_SEPARATOR
1601
1708
  pattern.start_with?(::File::ALT_SEPARATOR + ::File::ALT_SEPARATOR)
1602
1709
  end
1710
+ # :nocov:
1603
1711
  else
1604
1712
  def absolute_pattern?(pattern)
1605
1713
  pattern.start_with?(File::Separator)
1606
1714
  end
1607
1715
  end
1608
1716
 
1717
+ # @private
1718
+ ON_SQUARE_BRACKETS = /[\[\]]/
1719
+
1609
1720
  def extract_location(path)
1610
1721
  match = /^(.*?)((?:\:\d+)+)$/.match(path)
1611
1722
 
@@ -1613,6 +1724,9 @@ module RSpec
1613
1724
  captures = match.captures
1614
1725
  path, lines = captures[0], captures[1][1..-1].split(":").map { |n| n.to_i }
1615
1726
  filter_manager.add_location path, lines
1727
+ else
1728
+ path, scoped_ids = path.split(ON_SQUARE_BRACKETS)
1729
+ filter_manager.add_ids(path, scoped_ids.split(/\s*,\s*/)) if scoped_ids
1616
1730
  end
1617
1731
 
1618
1732
  return [] if path == default_path
@@ -1627,6 +1741,16 @@ module RSpec
1627
1741
  @preferred_options.fetch(key) { yield }
1628
1742
  end
1629
1743
 
1744
+ def define_built_in_hooks
1745
+ around(:example, :aggregate_failures => true) do |procsy|
1746
+ begin
1747
+ aggregate_failures(nil, :hide_backtrace => true, &procsy)
1748
+ rescue Exception => exception
1749
+ procsy.example.set_aggregate_failures_exception(exception)
1750
+ end
1751
+ end
1752
+ end
1753
+
1630
1754
  def assert_no_example_groups_defined(config_option)
1631
1755
  return unless RSpec.world.example_groups.any?
1632
1756
 
@@ -1672,6 +1796,11 @@ module RSpec
1672
1796
  instance_variable_set(:"@#{name}", value)
1673
1797
  @files_to_run = nil
1674
1798
  end
1799
+
1800
+ def clear_values_derived_from_example_status_persistence_file_path
1801
+ @last_run_statuses = nil
1802
+ @spec_files_with_failures = nil
1803
+ end
1675
1804
  end
1676
1805
  # rubocop:enable Style/ClassLength
1677
1806
  end