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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +75 -0
- data/README.md +137 -20
- data/lib/rspec/autorun.rb +1 -0
- data/lib/rspec/core.rb +8 -16
- data/lib/rspec/core/backtrace_formatter.rb +1 -3
- data/lib/rspec/core/bisect/coordinator.rb +66 -0
- data/lib/rspec/core/bisect/example_minimizer.rb +130 -0
- data/lib/rspec/core/bisect/runner.rb +139 -0
- data/lib/rspec/core/bisect/server.rb +61 -0
- data/lib/rspec/core/bisect/subset_enumerator.rb +39 -0
- data/lib/rspec/core/configuration.rb +134 -5
- data/lib/rspec/core/configuration_options.rb +21 -10
- data/lib/rspec/core/example.rb +84 -50
- data/lib/rspec/core/example_group.rb +46 -18
- data/lib/rspec/core/example_status_persister.rb +235 -0
- data/lib/rspec/core/filter_manager.rb +43 -28
- data/lib/rspec/core/flat_map.rb +2 -0
- data/lib/rspec/core/formatters.rb +30 -20
- data/lib/rspec/core/formatters/base_text_formatter.rb +1 -0
- data/lib/rspec/core/formatters/bisect_formatter.rb +68 -0
- data/lib/rspec/core/formatters/bisect_progress_formatter.rb +115 -0
- data/lib/rspec/core/formatters/deprecation_formatter.rb +0 -1
- data/lib/rspec/core/formatters/documentation_formatter.rb +0 -4
- data/lib/rspec/core/formatters/exception_presenter.rb +389 -0
- data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
- data/lib/rspec/core/formatters/helpers.rb +22 -2
- data/lib/rspec/core/formatters/html_formatter.rb +1 -4
- data/lib/rspec/core/formatters/html_printer.rb +2 -6
- data/lib/rspec/core/formatters/json_formatter.rb +6 -4
- data/lib/rspec/core/formatters/snippet_extractor.rb +12 -7
- data/lib/rspec/core/hooks.rb +8 -2
- data/lib/rspec/core/memoized_helpers.rb +77 -17
- data/lib/rspec/core/metadata.rb +24 -10
- data/lib/rspec/core/metadata_filter.rb +16 -3
- data/lib/rspec/core/mutex.rb +63 -0
- data/lib/rspec/core/notifications.rb +84 -189
- data/lib/rspec/core/option_parser.rb +105 -32
- data/lib/rspec/core/ordering.rb +28 -25
- data/lib/rspec/core/profiler.rb +32 -0
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +6 -1
- data/lib/rspec/core/rake_task.rb +6 -20
- data/lib/rspec/core/reentrant_mutex.rb +52 -0
- data/lib/rspec/core/reporter.rb +65 -17
- data/lib/rspec/core/runner.rb +38 -14
- data/lib/rspec/core/set.rb +49 -0
- data/lib/rspec/core/shared_example_group.rb +3 -1
- data/lib/rspec/core/shell_escape.rb +49 -0
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/world.rb +31 -20
- metadata +35 -7
- metadata.gz.sig +0 -0
- 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
|
-
|
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
|
-
|
1164
|
-
@include_modules.items_for(example.metadata)
|
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
|