rspec-core 3.2.3 → 3.3.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.
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