rspec-core 3.0.4 → 3.12.2
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +2 -1
- data/Changelog.md +888 -2
- data/{License.txt → LICENSE.md} +6 -5
- data/README.md +165 -24
- data/lib/rspec/autorun.rb +1 -0
- data/lib/rspec/core/backtrace_formatter.rb +19 -20
- data/lib/rspec/core/bisect/coordinator.rb +62 -0
- data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
- data/lib/rspec/core/bisect/fork_runner.rb +138 -0
- data/lib/rspec/core/bisect/server.rb +61 -0
- data/lib/rspec/core/bisect/shell_command.rb +126 -0
- data/lib/rspec/core/bisect/shell_runner.rb +73 -0
- data/lib/rspec/core/bisect/utilities.rb +69 -0
- data/lib/rspec/core/configuration.rb +1287 -246
- data/lib/rspec/core/configuration_options.rb +95 -35
- data/lib/rspec/core/did_you_mean.rb +46 -0
- data/lib/rspec/core/drb.rb +21 -12
- data/lib/rspec/core/dsl.rb +10 -6
- data/lib/rspec/core/example.rb +305 -113
- data/lib/rspec/core/example_group.rb +431 -223
- data/lib/rspec/core/example_status_persister.rb +235 -0
- data/lib/rspec/core/filter_manager.rb +86 -115
- data/lib/rspec/core/flat_map.rb +6 -4
- data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
- data/lib/rspec/core/formatters/base_formatter.rb +14 -116
- data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
- data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
- data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
- data/lib/rspec/core/formatters/console_codes.rb +29 -18
- data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
- data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
- data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
- data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
- data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
- data/lib/rspec/core/formatters/helpers.rb +45 -15
- data/lib/rspec/core/formatters/html_formatter.rb +33 -28
- data/lib/rspec/core/formatters/html_printer.rb +30 -20
- data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
- data/lib/rspec/core/formatters/json_formatter.rb +18 -9
- data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
- data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
- data/lib/rspec/core/formatters/protocol.rb +182 -0
- data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
- data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
- data/lib/rspec/core/formatters.rb +81 -41
- data/lib/rspec/core/hooks.rb +314 -244
- data/lib/rspec/core/invocations.rb +87 -0
- data/lib/rspec/core/memoized_helpers.rb +161 -51
- data/lib/rspec/core/metadata.rb +132 -61
- data/lib/rspec/core/metadata_filter.rb +224 -64
- data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
- data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
- data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
- data/lib/rspec/core/mocking_adapters/null.rb +2 -0
- data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
- data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
- data/lib/rspec/core/notifications.rb +192 -206
- data/lib/rspec/core/option_parser.rb +174 -69
- data/lib/rspec/core/ordering.rb +48 -35
- data/lib/rspec/core/output_wrapper.rb +29 -0
- data/lib/rspec/core/pending.rb +25 -33
- data/lib/rspec/core/profiler.rb +34 -0
- data/lib/rspec/core/project_initializer/.rspec +0 -2
- data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
- data/lib/rspec/core/project_initializer.rb +5 -3
- data/lib/rspec/core/rake_task.rb +99 -55
- data/lib/rspec/core/reporter.rb +128 -15
- data/lib/rspec/core/ruby_project.rb +14 -6
- data/lib/rspec/core/runner.rb +96 -45
- data/lib/rspec/core/sandbox.rb +37 -0
- data/lib/rspec/core/set.rb +54 -0
- data/lib/rspec/core/shared_example_group.rb +133 -43
- data/lib/rspec/core/shell_escape.rb +49 -0
- data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
- data/lib/rspec/core/version.rb +1 -1
- data/lib/rspec/core/warnings.rb +6 -6
- data/lib/rspec/core/world.rb +172 -68
- data/lib/rspec/core.rb +66 -21
- data.tar.gz.sig +0 -0
- metadata +93 -69
- metadata.gz.sig +0 -0
- data/lib/rspec/core/backport_random.rb +0 -336
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
RSpec::Support.require_rspec_support "directory_maker"
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module Core
|
|
5
|
+
# Persists example ids and their statuses so that we can filter
|
|
6
|
+
# to just the ones that failed the last time they ran.
|
|
7
|
+
# @private
|
|
8
|
+
class ExampleStatusPersister
|
|
9
|
+
def self.load_from(file_name)
|
|
10
|
+
return [] unless File.exist?(file_name)
|
|
11
|
+
ExampleStatusParser.parse(File.read(file_name))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.persist(examples, file_name)
|
|
15
|
+
new(examples, file_name).persist
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(examples, file_name)
|
|
19
|
+
@examples = examples
|
|
20
|
+
@file_name = file_name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def persist
|
|
24
|
+
RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name))
|
|
25
|
+
File.open(@file_name, File::RDWR | File::CREAT) do |f|
|
|
26
|
+
# lock the file while reading / persisting to avoid a race
|
|
27
|
+
# condition where parallel or unrelated spec runs race to
|
|
28
|
+
# update the same file
|
|
29
|
+
f.flock(File::LOCK_EX)
|
|
30
|
+
unparsed_previous_runs = f.read
|
|
31
|
+
f.rewind
|
|
32
|
+
f.write(dump_statuses(unparsed_previous_runs))
|
|
33
|
+
f.flush
|
|
34
|
+
f.truncate(f.pos)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def dump_statuses(unparsed_previous_runs)
|
|
41
|
+
statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs)
|
|
42
|
+
merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs)
|
|
43
|
+
ExampleStatusDumper.dump(merged_statuses)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def statuses_from_this_run
|
|
47
|
+
@examples.map do |ex|
|
|
48
|
+
result = ex.execution_result
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
:example_id => ex.id,
|
|
52
|
+
:status => result.status ? result.status.to_s : Configuration::UNKNOWN_STATUS,
|
|
53
|
+
:run_time => result.run_time ? Formatters::Helpers.format_duration(result.run_time) : ""
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Merges together a list of example statuses from this run
|
|
60
|
+
# and a list from previous runs (presumably loaded from disk).
|
|
61
|
+
# Each example status object is expected to be a hash with
|
|
62
|
+
# at least an `:example_id` and a `:status` key. Examples that
|
|
63
|
+
# were loaded but not executed (due to filtering, `--fail-fast`
|
|
64
|
+
# or whatever) should have a `:status` of `UNKNOWN_STATUS`.
|
|
65
|
+
#
|
|
66
|
+
# This will produce a new list that:
|
|
67
|
+
# - Will be missing examples from previous runs that we know for sure
|
|
68
|
+
# no longer exist.
|
|
69
|
+
# - Will have the latest known status for any examples that either
|
|
70
|
+
# definitively do exist or may still exist.
|
|
71
|
+
# - Is sorted by file name and example definition order, so that
|
|
72
|
+
# the saved file is easily scannable if users want to inspect it.
|
|
73
|
+
# @private
|
|
74
|
+
class ExampleStatusMerger
|
|
75
|
+
def self.merge(this_run, from_previous_runs)
|
|
76
|
+
new(this_run, from_previous_runs).merge
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def initialize(this_run, from_previous_runs)
|
|
80
|
+
@this_run = hash_from(this_run)
|
|
81
|
+
@from_previous_runs = hash_from(from_previous_runs)
|
|
82
|
+
@file_exists_cache = Hash.new { |hash, file| hash[file] = File.exist?(file) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def merge
|
|
86
|
+
delete_previous_examples_that_no_longer_exist
|
|
87
|
+
|
|
88
|
+
@this_run.merge(@from_previous_runs) do |_ex_id, new, old|
|
|
89
|
+
new.fetch(:status) == Configuration::UNKNOWN_STATUS ? old : new
|
|
90
|
+
end.values.sort_by(&method(:sort_value_from))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def hash_from(example_list)
|
|
96
|
+
example_list.inject({}) do |hash, example|
|
|
97
|
+
hash[example.fetch(:example_id)] = example
|
|
98
|
+
hash
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def delete_previous_examples_that_no_longer_exist
|
|
103
|
+
@from_previous_runs.delete_if do |ex_id, _|
|
|
104
|
+
example_must_no_longer_exist?(ex_id)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def example_must_no_longer_exist?(ex_id)
|
|
109
|
+
# Obviously, it exists if it was loaded for this spec run...
|
|
110
|
+
return false if @this_run.key?(ex_id)
|
|
111
|
+
|
|
112
|
+
spec_file = spec_file_from(ex_id)
|
|
113
|
+
|
|
114
|
+
# `this_run` includes examples that were loaded but not executed.
|
|
115
|
+
# Given that, if the spec file for this example was loaded,
|
|
116
|
+
# but the id does not still exist, it's safe to assume that
|
|
117
|
+
# the example must no longer exist.
|
|
118
|
+
return true if loaded_spec_files.include?(spec_file)
|
|
119
|
+
|
|
120
|
+
# The example may still exist as long as the file exists...
|
|
121
|
+
!@file_exists_cache[spec_file]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def loaded_spec_files
|
|
125
|
+
@loaded_spec_files ||= Set.new(@this_run.keys.map(&method(:spec_file_from)))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def spec_file_from(ex_id)
|
|
129
|
+
ex_id.split("[").first
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def sort_value_from(example)
|
|
133
|
+
file, scoped_id = Example.parse_id(example.fetch(:example_id))
|
|
134
|
+
[file, *scoped_id.split(":").map(&method(:Integer))]
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Dumps a list of hashes in a pretty, human readable format
|
|
139
|
+
# for later parsing. The hashes are expected to have symbol
|
|
140
|
+
# keys and string values, and each hash should have the same
|
|
141
|
+
# set of keys.
|
|
142
|
+
# @private
|
|
143
|
+
class ExampleStatusDumper
|
|
144
|
+
def self.dump(examples)
|
|
145
|
+
new(examples).dump
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def initialize(examples)
|
|
149
|
+
@examples = examples
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def dump
|
|
153
|
+
return nil if @examples.empty?
|
|
154
|
+
(formatted_header_rows + formatted_value_rows).join("\n") << "\n"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def formatted_header_rows
|
|
160
|
+
@formatted_header_rows ||= begin
|
|
161
|
+
dividers = column_widths.map { |w| "-" * w }
|
|
162
|
+
[formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)]
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def formatted_value_rows
|
|
167
|
+
@formatted_value_rows ||= rows.map do |row|
|
|
168
|
+
formatted_row_from(row)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def rows
|
|
173
|
+
@rows ||= @examples.map { |ex| ex.values_at(*headers) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def formatted_row_from(row_values)
|
|
177
|
+
padded_values = row_values.each_with_index.map do |value, index|
|
|
178
|
+
value.ljust(column_widths[index])
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
padded_values.join(" | ") << " |"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def headers
|
|
185
|
+
@headers ||= @examples.first.keys
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def column_widths
|
|
189
|
+
@column_widths ||= begin
|
|
190
|
+
value_sets = rows.transpose
|
|
191
|
+
|
|
192
|
+
headers.each_with_index.map do |header, index|
|
|
193
|
+
values = value_sets[index] << header.to_s
|
|
194
|
+
values.map(&:length).max
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Parses a string that has been previously dumped by ExampleStatusDumper.
|
|
201
|
+
# Note that this parser is a bit naive in that it does a simple split on
|
|
202
|
+
# "\n" and " | ", with no concern for handling escaping. For now, that's
|
|
203
|
+
# OK because the values we plan to persist (example id, status, and perhaps
|
|
204
|
+
# example duration) are highly unlikely to contain "\n" or " | " -- after
|
|
205
|
+
# all, who puts those in file names?
|
|
206
|
+
# @private
|
|
207
|
+
class ExampleStatusParser
|
|
208
|
+
def self.parse(string)
|
|
209
|
+
new(string).parse
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def initialize(string)
|
|
213
|
+
@header_line, _, *@row_lines = string.lines.to_a
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def parse
|
|
217
|
+
@row_lines.map { |line| parse_row(line) }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def parse_row(line)
|
|
223
|
+
Hash[headers.zip(split_line(line))]
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def headers
|
|
227
|
+
@headers ||= split_line(@header_line).grep(/\S/).map(&:to_sym)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def split_line(line)
|
|
231
|
+
line.split(/\s+\|\s+?/, -1)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -1,71 +1,6 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Core
|
|
3
3
|
# @private
|
|
4
|
-
# Manages the filtering of examples and groups by matching tags declared on
|
|
5
|
-
# the command line or options files, or filters declared via
|
|
6
|
-
# `RSpec.configure`, with hash key/values submitted within example group
|
|
7
|
-
# and/or example declarations. For example, given this declaration:
|
|
8
|
-
#
|
|
9
|
-
# describe Thing, :awesome => true do
|
|
10
|
-
# it "does something" do
|
|
11
|
-
# # ...
|
|
12
|
-
# end
|
|
13
|
-
# end
|
|
14
|
-
#
|
|
15
|
-
# That group (or any other with `:awesome => true`) would be filtered in
|
|
16
|
-
# with any of the following commands:
|
|
17
|
-
#
|
|
18
|
-
# rspec --tag awesome:true
|
|
19
|
-
# rspec --tag awesome
|
|
20
|
-
# rspec -t awesome:true
|
|
21
|
-
# rspec -t awesome
|
|
22
|
-
#
|
|
23
|
-
# Prefixing the tag names with `~` negates the tags, thus excluding this group with
|
|
24
|
-
# any of:
|
|
25
|
-
#
|
|
26
|
-
# rspec --tag ~awesome:true
|
|
27
|
-
# rspec --tag ~awesome
|
|
28
|
-
# rspec -t ~awesome:true
|
|
29
|
-
# rspec -t ~awesome
|
|
30
|
-
#
|
|
31
|
-
# ## Options files and command line overrides
|
|
32
|
-
#
|
|
33
|
-
# Tag declarations can be stored in `.rspec`, `~/.rspec`, or a custom
|
|
34
|
-
# options file. This is useful for storing defaults. For example, let's
|
|
35
|
-
# say you've got some slow specs that you want to suppress most of the
|
|
36
|
-
# time. You can tag them like this:
|
|
37
|
-
#
|
|
38
|
-
# describe Something, :slow => true do
|
|
39
|
-
#
|
|
40
|
-
# And then store this in `.rspec`:
|
|
41
|
-
#
|
|
42
|
-
# --tag ~slow:true
|
|
43
|
-
#
|
|
44
|
-
# Now when you run `rspec`, that group will be excluded.
|
|
45
|
-
#
|
|
46
|
-
# ## Overriding
|
|
47
|
-
#
|
|
48
|
-
# Of course, you probably want to run them sometimes, so you can override
|
|
49
|
-
# this tag on the command line like this:
|
|
50
|
-
#
|
|
51
|
-
# rspec --tag slow:true
|
|
52
|
-
#
|
|
53
|
-
# ## RSpec.configure
|
|
54
|
-
#
|
|
55
|
-
# You can also store default tags with `RSpec.configure`. We use `tag` on
|
|
56
|
-
# the command line (and in options files like `.rspec`), but for historical
|
|
57
|
-
# reasons we use the term `filter` in `RSpec.configure:
|
|
58
|
-
#
|
|
59
|
-
# RSpec.configure do |c|
|
|
60
|
-
# c.filter_run_including :foo => :bar
|
|
61
|
-
# c.filter_run_excluding :foo => :bar
|
|
62
|
-
# end
|
|
63
|
-
#
|
|
64
|
-
# These declarations can also be overridden from the command line.
|
|
65
|
-
#
|
|
66
|
-
# @see RSpec.configure
|
|
67
|
-
# @see Configuration#filter_run_including
|
|
68
|
-
# @see Configuration#filter_run_excluding
|
|
69
4
|
class FilterManager
|
|
70
5
|
attr_reader :exclusions, :inclusions
|
|
71
6
|
|
|
@@ -81,9 +16,15 @@ module RSpec
|
|
|
81
16
|
# locations is a hash of expanded paths to arrays of line
|
|
82
17
|
# numbers to match against. e.g.
|
|
83
18
|
# { "path/to/file.rb" => [37, 42] }
|
|
84
|
-
locations
|
|
85
|
-
|
|
86
|
-
|
|
19
|
+
add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_ids(rerun_path, scoped_ids)
|
|
23
|
+
# ids is a hash of relative paths to arrays of ids
|
|
24
|
+
# to match against. e.g.
|
|
25
|
+
# { "./path/to/file.rb" => ["1:1", "2:4"] }
|
|
26
|
+
rerun_path = Metadata.relative_path(File.expand_path rerun_path)
|
|
27
|
+
add_path_to_arrays_filter(:ids, rerun_path, scoped_ids)
|
|
87
28
|
end
|
|
88
29
|
|
|
89
30
|
def empty?
|
|
@@ -91,11 +32,25 @@ module RSpec
|
|
|
91
32
|
end
|
|
92
33
|
|
|
93
34
|
def prune(examples)
|
|
35
|
+
# Semantically, this is unnecessary (the filtering below will return the empty
|
|
36
|
+
# array unmodified), but for perf reasons it's worth exiting early here. Users
|
|
37
|
+
# commonly have top-level examples groups that do not have any direct examples
|
|
38
|
+
# and instead have nested groups with examples. In that kind of situation,
|
|
39
|
+
# `examples` will be empty.
|
|
40
|
+
return examples if examples.empty?
|
|
41
|
+
|
|
42
|
+
examples = prune_conditionally_filtered_examples(examples)
|
|
43
|
+
|
|
94
44
|
if inclusions.standalone?
|
|
95
|
-
|
|
96
|
-
examples.select {|e| !base_exclusions.include_example?(e) && include?(e) }
|
|
45
|
+
examples.select { |e| inclusions.include_example?(e) }
|
|
97
46
|
else
|
|
98
|
-
|
|
47
|
+
locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules
|
|
48
|
+
|
|
49
|
+
examples.select do |ex|
|
|
50
|
+
file_scoped_include?(ex.metadata, ids, locations) do
|
|
51
|
+
!exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
99
54
|
end
|
|
100
55
|
end
|
|
101
56
|
|
|
@@ -111,10 +66,6 @@ module RSpec
|
|
|
111
66
|
exclusions.add_with_low_priority(args.last)
|
|
112
67
|
end
|
|
113
68
|
|
|
114
|
-
def exclude?(example)
|
|
115
|
-
exclusions.include_example?(example)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
69
|
def include(*args)
|
|
119
70
|
inclusions.add(args.last)
|
|
120
71
|
end
|
|
@@ -127,14 +78,42 @@ module RSpec
|
|
|
127
78
|
inclusions.add_with_low_priority(args.last)
|
|
128
79
|
end
|
|
129
80
|
|
|
130
|
-
|
|
131
|
-
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def add_path_to_arrays_filter(filter_key, path, values)
|
|
84
|
+
filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] }
|
|
85
|
+
filter[path].concat(values)
|
|
86
|
+
inclusions.add(filter_key => filter)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def prune_conditionally_filtered_examples(examples)
|
|
90
|
+
examples.reject do |ex|
|
|
91
|
+
meta = ex.metadata
|
|
92
|
+
!meta.fetch(:if, true) || meta[:unless]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# When a user specifies a particular spec location, that takes priority
|
|
97
|
+
# over any exclusion filters (such as if the spec is tagged with `:slow`
|
|
98
|
+
# and there is a `:slow => true` exclusion filter), but only for specs
|
|
99
|
+
# defined in the same file as the location filters. Excluded specs in
|
|
100
|
+
# other files should still be excluded.
|
|
101
|
+
def file_scoped_include?(ex_metadata, ids, locations)
|
|
102
|
+
no_id_filters = ids[ex_metadata[:rerun_file_path]].empty?
|
|
103
|
+
no_location_filters = locations[
|
|
104
|
+
File.expand_path(ex_metadata[:rerun_file_path])
|
|
105
|
+
].empty?
|
|
106
|
+
|
|
107
|
+
return yield if no_location_filters && no_id_filters
|
|
108
|
+
|
|
109
|
+
MetadataFilter.filter_applies?(:ids, ids, ex_metadata) ||
|
|
110
|
+
MetadataFilter.filter_applies?(:locations, locations, ex_metadata)
|
|
132
111
|
end
|
|
133
112
|
end
|
|
134
113
|
|
|
135
114
|
# @private
|
|
136
115
|
class FilterRules
|
|
137
|
-
PROC_HEX_NUMBER = /0x[0-9a-f]
|
|
116
|
+
PROC_HEX_NUMBER = /0x[0-9a-f]+@?/
|
|
138
117
|
PROJECT_DIR = File.expand_path('.')
|
|
139
118
|
|
|
140
119
|
attr_accessor :opposite
|
|
@@ -148,17 +127,17 @@ module RSpec
|
|
|
148
127
|
[exclusions, inclusions]
|
|
149
128
|
end
|
|
150
129
|
|
|
151
|
-
def initialize(
|
|
152
|
-
@rules =
|
|
130
|
+
def initialize(rules={})
|
|
131
|
+
@rules = rules
|
|
153
132
|
end
|
|
154
133
|
|
|
155
134
|
def add(updated)
|
|
156
135
|
@rules.merge!(updated).each_key { |k| opposite.delete(k) }
|
|
157
136
|
end
|
|
158
137
|
|
|
159
|
-
def add_with_low_priority(
|
|
160
|
-
updated =
|
|
161
|
-
opposite.each_pair { |k,v| updated.delete(k) if updated[k] == v }
|
|
138
|
+
def add_with_low_priority(updated)
|
|
139
|
+
updated = updated.merge(@rules)
|
|
140
|
+
opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v }
|
|
162
141
|
@rules.replace(updated)
|
|
163
142
|
end
|
|
164
143
|
|
|
@@ -192,47 +171,51 @@ module RSpec
|
|
|
192
171
|
end
|
|
193
172
|
|
|
194
173
|
def description
|
|
195
|
-
rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)','')
|
|
174
|
+
rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '')
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def include_example?(example)
|
|
178
|
+
MetadataFilter.apply?(:any?, @rules, example.metadata)
|
|
196
179
|
end
|
|
197
180
|
end
|
|
198
181
|
|
|
199
182
|
# @private
|
|
200
|
-
|
|
201
|
-
STANDALONE_FILTERS = [:locations, :full_description]
|
|
202
|
-
|
|
203
|
-
def add_location(locations)
|
|
204
|
-
replace_filters({ :locations => locations })
|
|
205
|
-
end
|
|
183
|
+
ExclusionRules = FilterRules
|
|
206
184
|
|
|
185
|
+
# @private
|
|
186
|
+
class InclusionRules < FilterRules
|
|
207
187
|
def add(*args)
|
|
208
|
-
|
|
188
|
+
apply_standalone_filter(*args) || super
|
|
209
189
|
end
|
|
210
190
|
|
|
211
191
|
def add_with_low_priority(*args)
|
|
212
|
-
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def use(*args)
|
|
216
|
-
set_standalone_filter(*args) || super
|
|
192
|
+
apply_standalone_filter(*args) || super
|
|
217
193
|
end
|
|
218
194
|
|
|
219
195
|
def include_example?(example)
|
|
220
|
-
@rules.empty?
|
|
196
|
+
@rules.empty? || super
|
|
221
197
|
end
|
|
222
198
|
|
|
223
199
|
def standalone?
|
|
224
200
|
is_standalone_filter?(@rules)
|
|
225
201
|
end
|
|
226
202
|
|
|
203
|
+
def split_file_scoped_rules
|
|
204
|
+
rules_dup = @rules.dup
|
|
205
|
+
locations = rules_dup.delete(:locations) { Hash.new([]) }
|
|
206
|
+
ids = rules_dup.delete(:ids) { Hash.new([]) }
|
|
207
|
+
|
|
208
|
+
return locations, ids, self.class.new(rules_dup)
|
|
209
|
+
end
|
|
210
|
+
|
|
227
211
|
private
|
|
228
212
|
|
|
229
|
-
def
|
|
213
|
+
def apply_standalone_filter(updated)
|
|
230
214
|
return true if standalone?
|
|
215
|
+
return nil unless is_standalone_filter?(updated)
|
|
231
216
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
true
|
|
235
|
-
end
|
|
217
|
+
replace_filters(updated)
|
|
218
|
+
true
|
|
236
219
|
end
|
|
237
220
|
|
|
238
221
|
def replace_filters(new_rules)
|
|
@@ -241,19 +224,7 @@ module RSpec
|
|
|
241
224
|
end
|
|
242
225
|
|
|
243
226
|
def is_standalone_filter?(rules)
|
|
244
|
-
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# @private
|
|
249
|
-
class ExclusionRules < FilterRules
|
|
250
|
-
CONDITIONAL_FILTERS = {
|
|
251
|
-
:if => lambda { |value| !value },
|
|
252
|
-
:unless => lambda { |value| value }
|
|
253
|
-
}.freeze
|
|
254
|
-
|
|
255
|
-
def include_example?(example)
|
|
256
|
-
example.any_apply?(@rules) || example.any_apply?(CONDITIONAL_FILTERS)
|
|
227
|
+
rules.key?(:full_description)
|
|
257
228
|
end
|
|
258
229
|
end
|
|
259
230
|
end
|
data/lib/rspec/core/flat_map.rb
CHANGED
|
@@ -3,13 +3,15 @@ module RSpec
|
|
|
3
3
|
# @private
|
|
4
4
|
module FlatMap
|
|
5
5
|
if [].respond_to?(:flat_map)
|
|
6
|
-
def flat_map(array)
|
|
7
|
-
array.flat_map
|
|
6
|
+
def flat_map(array, &block)
|
|
7
|
+
array.flat_map(&block)
|
|
8
8
|
end
|
|
9
9
|
else # for 1.8.7
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
# :nocov:
|
|
11
|
+
def flat_map(array, &block)
|
|
12
|
+
array.map(&block).flatten(1)
|
|
12
13
|
end
|
|
14
|
+
# :nocov:
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
module_function :flat_map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
RSpec::Support.require_rspec_core "bisect/utilities"
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module Core
|
|
5
|
+
module Formatters
|
|
6
|
+
# Contains common logic for formatters used by `--bisect` to communicate results
|
|
7
|
+
# back to the bisect runner.
|
|
8
|
+
#
|
|
9
|
+
# Subclasses must define a `notify_results(all_example_ids, failed_example_ids)`
|
|
10
|
+
# method.
|
|
11
|
+
# @private
|
|
12
|
+
class BaseBisectFormatter
|
|
13
|
+
def self.inherited(formatter)
|
|
14
|
+
Formatters.register formatter, :start_dump, :example_failed, :example_finished
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(expected_failures)
|
|
18
|
+
@all_example_ids = []
|
|
19
|
+
@failed_example_ids = []
|
|
20
|
+
@remaining_failures = expected_failures
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def example_failed(notification)
|
|
24
|
+
@failed_example_ids << notification.example.id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def example_finished(notification)
|
|
28
|
+
@all_example_ids << notification.example.id
|
|
29
|
+
return unless @remaining_failures.include?(notification.example.id)
|
|
30
|
+
@remaining_failures.delete(notification.example.id)
|
|
31
|
+
|
|
32
|
+
status = notification.example.execution_result.status
|
|
33
|
+
return if status == :failed && !@remaining_failures.empty?
|
|
34
|
+
RSpec.world.wants_to_quit = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start_dump(_notification)
|
|
38
|
+
# `notify_results` is defined in the subclass
|
|
39
|
+
notify_results(Bisect::ExampleSetDescriptor.new(
|
|
40
|
+
@all_example_ids, @failed_example_ids))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|