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.
- 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
|