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
data/lib/rspec/core/metadata.rb
CHANGED
|
@@ -7,7 +7,7 @@ module RSpec
|
|
|
7
7
|
# In addition to metadata that is used internally, this also stores
|
|
8
8
|
# user-supplied metadata, e.g.
|
|
9
9
|
#
|
|
10
|
-
# describe Something, :type => :ui do
|
|
10
|
+
# RSpec.describe Something, :type => :ui do
|
|
11
11
|
# it "does something", :slow => true do
|
|
12
12
|
# # ...
|
|
13
13
|
# end
|
|
@@ -25,17 +25,51 @@ module RSpec
|
|
|
25
25
|
# @see Configuration#filter_run_including
|
|
26
26
|
# @see Configuration#filter_run_excluding
|
|
27
27
|
module Metadata
|
|
28
|
+
# Matches strings either at the beginning of the input or prefixed with a
|
|
29
|
+
# whitespace, containing the current path, either postfixed with the
|
|
30
|
+
# separator, or at the end of the string. Match groups are the character
|
|
31
|
+
# before and the character after the string if any.
|
|
32
|
+
#
|
|
33
|
+
# http://rubular.com/r/fT0gmX6VJX
|
|
34
|
+
# http://rubular.com/r/duOrD4i3wb
|
|
35
|
+
# http://rubular.com/r/sbAMHFrOx1
|
|
36
|
+
def self.relative_path_regex
|
|
37
|
+
@relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
|
|
38
|
+
end
|
|
39
|
+
|
|
28
40
|
# @api private
|
|
29
41
|
#
|
|
30
42
|
# @param line [String] current code line
|
|
31
43
|
# @return [String] relative path to line
|
|
32
44
|
def self.relative_path(line)
|
|
33
|
-
line = line.sub(
|
|
34
|
-
line = line.sub(/\A([^:]+:\d+)$/, '\\1')
|
|
35
|
-
return nil if line == '-e:1'
|
|
45
|
+
line = line.sub(relative_path_regex, "\\1.\\2".freeze)
|
|
46
|
+
line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
|
|
47
|
+
return nil if line == '-e:1'.freeze
|
|
36
48
|
line
|
|
37
49
|
rescue SecurityError
|
|
50
|
+
# :nocov:
|
|
38
51
|
nil
|
|
52
|
+
# :nocov:
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @private
|
|
56
|
+
# Iteratively walks up from the given metadata through all
|
|
57
|
+
# example group ancestors, yielding each metadata hash along the way.
|
|
58
|
+
def self.ascending(metadata)
|
|
59
|
+
yield metadata
|
|
60
|
+
return unless (group_metadata = metadata.fetch(:example_group) { metadata[:parent_example_group] })
|
|
61
|
+
|
|
62
|
+
loop do
|
|
63
|
+
yield group_metadata
|
|
64
|
+
break unless (group_metadata = group_metadata[:parent_example_group])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @private
|
|
69
|
+
# Returns an enumerator that iteratively walks up the given metadata through all
|
|
70
|
+
# example group ancestors, yielding each metadata hash along the way.
|
|
71
|
+
def self.ascend(metadata)
|
|
72
|
+
enum_for(:ascending, metadata)
|
|
39
73
|
end
|
|
40
74
|
|
|
41
75
|
# @private
|
|
@@ -46,9 +80,7 @@ module RSpec
|
|
|
46
80
|
def self.build_hash_from(args, warn_about_example_group_filtering=false)
|
|
47
81
|
hash = args.last.is_a?(Hash) ? args.pop : {}
|
|
48
82
|
|
|
49
|
-
while args.last.is_a?(Symbol)
|
|
50
|
-
hash[args.pop] = true
|
|
51
|
-
end
|
|
83
|
+
hash[args.pop] = true while args.last.is_a?(Symbol)
|
|
52
84
|
|
|
53
85
|
if warn_about_example_group_filtering && hash.key?(:example_group)
|
|
54
86
|
RSpec.deprecate("Filtering by an `:example_group` subhash",
|
|
@@ -59,9 +91,24 @@ module RSpec
|
|
|
59
91
|
end
|
|
60
92
|
|
|
61
93
|
# @private
|
|
62
|
-
def self.
|
|
63
|
-
return
|
|
64
|
-
|
|
94
|
+
def self.deep_hash_dup(object)
|
|
95
|
+
return object.dup if Array === object
|
|
96
|
+
return object unless Hash === object
|
|
97
|
+
|
|
98
|
+
object.inject(object.dup) do |duplicate, (key, value)|
|
|
99
|
+
duplicate[key] = deep_hash_dup(value)
|
|
100
|
+
duplicate
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @private
|
|
105
|
+
def self.id_from(metadata)
|
|
106
|
+
"#{metadata[:rerun_file_path]}[#{metadata[:scoped_id]}]"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @private
|
|
110
|
+
def self.location_tuple_from(metadata)
|
|
111
|
+
[metadata[:absolute_file_path], metadata[:line_number]]
|
|
65
112
|
end
|
|
66
113
|
|
|
67
114
|
# @private
|
|
@@ -70,9 +117,10 @@ module RSpec
|
|
|
70
117
|
class HashPopulator
|
|
71
118
|
attr_reader :metadata, :user_metadata, :description_args, :block
|
|
72
119
|
|
|
73
|
-
def initialize(metadata, user_metadata, description_args, block)
|
|
120
|
+
def initialize(metadata, user_metadata, index_provider, description_args, block)
|
|
74
121
|
@metadata = metadata
|
|
75
122
|
@user_metadata = user_metadata
|
|
123
|
+
@index_provider = index_provider
|
|
76
124
|
@description_args = description_args
|
|
77
125
|
@block = block
|
|
78
126
|
end
|
|
@@ -80,7 +128,6 @@ module RSpec
|
|
|
80
128
|
def populate
|
|
81
129
|
ensure_valid_user_keys
|
|
82
130
|
|
|
83
|
-
metadata[:execution_result] = Example::ExecutionResult.new
|
|
84
131
|
metadata[:block] = block
|
|
85
132
|
metadata[:description_args] = description_args
|
|
86
133
|
metadata[:description] = build_description_from(*metadata[:description_args])
|
|
@@ -89,81 +136,95 @@ module RSpec
|
|
|
89
136
|
|
|
90
137
|
populate_location_attributes
|
|
91
138
|
metadata.update(user_metadata)
|
|
92
|
-
RSpec.configuration.apply_derived_metadata_to(metadata)
|
|
93
139
|
end
|
|
94
140
|
|
|
95
141
|
private
|
|
96
142
|
|
|
97
143
|
def populate_location_attributes
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
144
|
+
backtrace = user_metadata.delete(:caller)
|
|
145
|
+
|
|
146
|
+
file_path, line_number = if backtrace
|
|
147
|
+
file_path_and_line_number_from(backtrace)
|
|
148
|
+
elsif block.respond_to?(:source_location)
|
|
149
|
+
block.source_location
|
|
150
|
+
else
|
|
151
|
+
file_path_and_line_number_from(caller)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
relative_file_path = Metadata.relative_path(file_path)
|
|
155
|
+
absolute_file_path = File.expand_path(relative_file_path)
|
|
156
|
+
metadata[:file_path] = relative_file_path
|
|
157
|
+
metadata[:line_number] = line_number.to_i
|
|
158
|
+
metadata[:location] = "#{relative_file_path}:#{line_number}"
|
|
159
|
+
metadata[:absolute_file_path] = absolute_file_path
|
|
160
|
+
metadata[:rerun_file_path] ||= relative_file_path
|
|
161
|
+
metadata[:scoped_id] = build_scoped_id_for(absolute_file_path)
|
|
110
162
|
end
|
|
111
163
|
|
|
112
164
|
def file_path_and_line_number_from(backtrace)
|
|
113
|
-
first_caller_from_outside_rspec = backtrace.
|
|
165
|
+
first_caller_from_outside_rspec = backtrace.find { |l| l !~ CallerFilter::LIB_REGEX }
|
|
114
166
|
first_caller_from_outside_rspec ||= backtrace.first
|
|
115
167
|
/(.+?):(\d+)(?:|:\d+)/.match(first_caller_from_outside_rspec).captures
|
|
116
168
|
end
|
|
117
169
|
|
|
118
170
|
def description_separator(parent_part, child_part)
|
|
119
|
-
if parent_part.is_a?(Module) &&
|
|
120
|
-
''
|
|
171
|
+
if parent_part.is_a?(Module) && /^(?:#|::|\.)/.match(child_part.to_s)
|
|
172
|
+
''.freeze
|
|
121
173
|
else
|
|
122
|
-
' '
|
|
174
|
+
' '.freeze
|
|
123
175
|
end
|
|
124
176
|
end
|
|
125
177
|
|
|
126
178
|
def build_description_from(parent_description=nil, my_description=nil)
|
|
127
179
|
return parent_description.to_s unless my_description
|
|
180
|
+
return my_description.to_s if parent_description.to_s == ''
|
|
128
181
|
separator = description_separator(parent_description, my_description)
|
|
129
|
-
parent_description.to_s + separator
|
|
182
|
+
(parent_description.to_s + separator) << my_description.to_s
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def build_scoped_id_for(file_path)
|
|
186
|
+
index = @index_provider.call(file_path).to_s
|
|
187
|
+
parent_scoped_id = metadata.fetch(:scoped_id) { return index }
|
|
188
|
+
"#{parent_scoped_id}:#{index}"
|
|
130
189
|
end
|
|
131
190
|
|
|
132
191
|
def ensure_valid_user_keys
|
|
133
192
|
RESERVED_KEYS.each do |key|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
end
|
|
193
|
+
next unless user_metadata.key?(key)
|
|
194
|
+
raise <<-EOM.gsub(/^\s+\|/, '')
|
|
195
|
+
|#{"*" * 50}
|
|
196
|
+
|:#{key} is not allowed
|
|
197
|
+
|
|
|
198
|
+
|RSpec reserves some hash keys for its own internal use,
|
|
199
|
+
|including :#{key}, which is used on:
|
|
200
|
+
|
|
|
201
|
+
| #{CallerFilter.first_non_rspec_line}.
|
|
202
|
+
|
|
|
203
|
+
|Here are all of RSpec's reserved hash keys:
|
|
204
|
+
|
|
|
205
|
+
| #{RESERVED_KEYS.join("\n ")}
|
|
206
|
+
|#{"*" * 50}
|
|
207
|
+
EOM
|
|
150
208
|
end
|
|
151
209
|
end
|
|
152
210
|
end
|
|
153
211
|
|
|
154
212
|
# @private
|
|
155
213
|
class ExampleHash < HashPopulator
|
|
156
|
-
def self.create(group_metadata, user_metadata, description, block)
|
|
214
|
+
def self.create(group_metadata, user_metadata, index_provider, description, block)
|
|
157
215
|
example_metadata = group_metadata.dup
|
|
158
216
|
group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash|
|
|
159
217
|
hash[:parent_example_group]
|
|
160
218
|
end)
|
|
161
219
|
group_metadata.update(example_metadata)
|
|
162
220
|
|
|
221
|
+
example_metadata[:execution_result] = Example::ExecutionResult.new
|
|
163
222
|
example_metadata[:example_group] = group_metadata
|
|
223
|
+
example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace
|
|
164
224
|
example_metadata.delete(:parent_example_group)
|
|
165
225
|
|
|
166
|
-
|
|
226
|
+
description_args = description.nil? ? [] : [description]
|
|
227
|
+
hash = new(example_metadata, user_metadata, index_provider, description_args, block)
|
|
167
228
|
hash.populate
|
|
168
229
|
hash.metadata
|
|
169
230
|
end
|
|
@@ -184,7 +245,7 @@ module RSpec
|
|
|
184
245
|
|
|
185
246
|
# @private
|
|
186
247
|
class ExampleGroupHash < HashPopulator
|
|
187
|
-
def self.create(parent_group_metadata, user_metadata, *args, &block)
|
|
248
|
+
def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block)
|
|
188
249
|
group_metadata = hash_with_backwards_compatibility_default_proc
|
|
189
250
|
|
|
190
251
|
if parent_group_metadata
|
|
@@ -192,7 +253,7 @@ module RSpec
|
|
|
192
253
|
group_metadata[:parent_example_group] = parent_group_metadata
|
|
193
254
|
end
|
|
194
255
|
|
|
195
|
-
hash = new(group_metadata, user_metadata, args, block)
|
|
256
|
+
hash = new(group_metadata, user_metadata, example_group_index, args, block)
|
|
196
257
|
hash.populate
|
|
197
258
|
hash.metadata
|
|
198
259
|
end
|
|
@@ -205,20 +266,22 @@ module RSpec
|
|
|
205
266
|
Proc.new do |hash, key|
|
|
206
267
|
case key
|
|
207
268
|
when :example_group
|
|
208
|
-
# We commonly get here when rspec-core is applying a previously
|
|
209
|
-
# filter rule, such as when a gem configures:
|
|
269
|
+
# We commonly get here when rspec-core is applying a previously
|
|
270
|
+
# configured filter rule, such as when a gem configures:
|
|
210
271
|
#
|
|
211
272
|
# RSpec.configure do |c|
|
|
212
273
|
# c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ }
|
|
213
274
|
# end
|
|
214
275
|
#
|
|
215
|
-
# It's confusing for a user to get a deprecation at this point in
|
|
216
|
-
# we issue a deprecation from the config APIs
|
|
217
|
-
#
|
|
218
|
-
|
|
276
|
+
# It's confusing for a user to get a deprecation at this point in
|
|
277
|
+
# the code, so instead we issue a deprecation from the config APIs
|
|
278
|
+
# that take a metadata hash, and MetadataFilter sets this thread
|
|
279
|
+
# local to silence the warning here since it would be so
|
|
280
|
+
# confusing.
|
|
281
|
+
unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations]
|
|
219
282
|
RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
|
|
220
|
-
:replacement => "the example group's hash directly for the "
|
|
221
|
-
"computed keys and `:parent_example_group` to access the parent "
|
|
283
|
+
:replacement => "the example group's hash directly for the " \
|
|
284
|
+
"computed keys and `:parent_example_group` to access the parent " \
|
|
222
285
|
"example group metadata")
|
|
223
286
|
end
|
|
224
287
|
|
|
@@ -261,14 +324,21 @@ module RSpec
|
|
|
261
324
|
# @private
|
|
262
325
|
RESERVED_KEYS = [
|
|
263
326
|
:description,
|
|
327
|
+
:description_args,
|
|
328
|
+
:described_class,
|
|
264
329
|
:example_group,
|
|
265
330
|
:parent_example_group,
|
|
266
331
|
:execution_result,
|
|
332
|
+
:last_run_status,
|
|
267
333
|
:file_path,
|
|
334
|
+
:absolute_file_path,
|
|
335
|
+
:rerun_file_path,
|
|
268
336
|
:full_description,
|
|
269
337
|
:line_number,
|
|
270
338
|
:location,
|
|
271
|
-
:
|
|
339
|
+
:scoped_id,
|
|
340
|
+
:block,
|
|
341
|
+
:shared_group_inclusion_backtrace
|
|
272
342
|
]
|
|
273
343
|
end
|
|
274
344
|
|
|
@@ -357,7 +427,7 @@ module RSpec
|
|
|
357
427
|
to_h
|
|
358
428
|
end
|
|
359
429
|
|
|
360
|
-
def issue_deprecation(
|
|
430
|
+
def issue_deprecation(_method_name, *_args)
|
|
361
431
|
# no-op by default: subclasses can override
|
|
362
432
|
end
|
|
363
433
|
|
|
@@ -395,7 +465,8 @@ module RSpec
|
|
|
395
465
|
# `metadata[:example_group][:described_class]` when you use
|
|
396
466
|
# anonymous controller specs) such that changes are written
|
|
397
467
|
# back to the top-level metadata hash.
|
|
398
|
-
# * Exposes the parent group metadata as
|
|
468
|
+
# * Exposes the parent group metadata as
|
|
469
|
+
# `[:example_group][:example_group]`.
|
|
399
470
|
class LegacyExampleGroupHash
|
|
400
471
|
include HashImitatable
|
|
401
472
|
|
|
@@ -6,89 +6,249 @@ module RSpec
|
|
|
6
6
|
# having metadata be a raw hash (not a custom subclass), so externalizing
|
|
7
7
|
# this filtering logic helps us move in that direction.
|
|
8
8
|
module MetadataFilter
|
|
9
|
-
|
|
9
|
+
class << self
|
|
10
|
+
# @private
|
|
11
|
+
def apply?(predicate, filters, metadata)
|
|
12
|
+
filters.__send__(predicate) { |k, v| filter_applies?(k, v, metadata) }
|
|
13
|
+
end
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
# @private
|
|
16
|
+
def filter_applies?(key, filter_value, metadata)
|
|
17
|
+
silence_metadata_example_group_deprecations do
|
|
18
|
+
return location_filter_applies?(filter_value, metadata) if key == :locations
|
|
19
|
+
return id_filter_applies?(filter_value, metadata) if key == :ids
|
|
20
|
+
return filters_apply?(key, filter_value, metadata) if Hash === filter_value
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
meta_value = metadata.fetch(key) { return false }
|
|
23
|
+
|
|
24
|
+
return true if TrueClass === filter_value && meta_value
|
|
25
|
+
return proc_filter_applies?(key, filter_value, metadata) if Proc === filter_value
|
|
26
|
+
return filter_applies_to_any_value?(key, filter_value, metadata) if Array === meta_value
|
|
27
|
+
|
|
28
|
+
filter_value === meta_value || filter_value.to_s == meta_value.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @private
|
|
33
|
+
def silence_metadata_example_group_deprecations
|
|
34
|
+
RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] = true
|
|
35
|
+
yield
|
|
36
|
+
ensure
|
|
37
|
+
RSpec::Support.thread_local_data.delete(:silence_metadata_example_group_deprecations)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def filter_applies_to_any_value?(key, value, metadata)
|
|
43
|
+
metadata[key].any? { |v| filter_applies?(key, v, key => value) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def id_filter_applies?(rerun_paths_to_scoped_ids, metadata)
|
|
47
|
+
scoped_ids = rerun_paths_to_scoped_ids.fetch(metadata[:rerun_file_path]) { return false }
|
|
48
|
+
|
|
49
|
+
Metadata.ascend(metadata).any? do |meta|
|
|
50
|
+
scoped_ids.include?(meta[:scoped_id])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def location_filter_applies?(locations, metadata)
|
|
55
|
+
Metadata.ascend(metadata).any? do |meta|
|
|
56
|
+
file_path = meta[:absolute_file_path]
|
|
57
|
+
line_num = meta[:line_number]
|
|
58
|
+
|
|
59
|
+
locations[file_path].any? do |filter_line_num|
|
|
60
|
+
line_num == RSpec.world.preceding_declaration_line(file_path, filter_line_num)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def proc_filter_applies?(key, proc, metadata)
|
|
66
|
+
case proc.arity
|
|
67
|
+
when 0 then proc.call
|
|
68
|
+
when 2 then proc.call(metadata[key], metadata)
|
|
69
|
+
else proc.call(metadata[key])
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def filters_apply?(key, value, metadata)
|
|
74
|
+
subhash = metadata[key]
|
|
75
|
+
return false unless Hash === subhash || HashImitatable === subhash
|
|
76
|
+
value.all? { |k, v| filter_applies?(k, v, subhash) }
|
|
77
|
+
end
|
|
19
78
|
end
|
|
79
|
+
end
|
|
20
80
|
|
|
81
|
+
# Tracks a collection of filterable items (e.g. modules, hooks, etc)
|
|
82
|
+
# and provides an optimized API to get the applicable items for the
|
|
83
|
+
# metadata of an example or example group.
|
|
84
|
+
#
|
|
85
|
+
# There are two implementations, optimized for different uses.
|
|
86
|
+
# @private
|
|
87
|
+
module FilterableItemRepository
|
|
88
|
+
# This implementation is simple, and is optimized for frequent
|
|
89
|
+
# updates but rare queries. `append` and `prepend` do no extra
|
|
90
|
+
# processing, and no internal memoization is done, since this
|
|
91
|
+
# is not optimized for queries.
|
|
92
|
+
#
|
|
93
|
+
# This is ideal for use by a example or example group, which may
|
|
94
|
+
# be updated multiple times with globally configured hooks, etc,
|
|
95
|
+
# but will not be queried frequently by other examples or example
|
|
96
|
+
# groups.
|
|
21
97
|
# @private
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
98
|
+
class UpdateOptimized
|
|
99
|
+
attr_reader :items_and_filters
|
|
100
|
+
|
|
101
|
+
def initialize(applies_predicate)
|
|
102
|
+
@applies_predicate = applies_predicate
|
|
103
|
+
@items_and_filters = []
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def append(item, metadata)
|
|
107
|
+
@items_and_filters << [item, metadata]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def prepend(item, metadata)
|
|
111
|
+
@items_and_filters.unshift [item, metadata]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def delete(item, metadata)
|
|
115
|
+
@items_and_filters.delete [item, metadata]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def items_for(request_meta)
|
|
119
|
+
@items_and_filters.each_with_object([]) do |(item, item_meta), to_return|
|
|
120
|
+
to_return << item if item_meta.empty? ||
|
|
121
|
+
MetadataFilter.apply?(@applies_predicate, item_meta, request_meta)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
unless [].respond_to?(:each_with_object) # For 1.8.7
|
|
126
|
+
# :nocov:
|
|
127
|
+
undef items_for
|
|
128
|
+
def items_for(request_meta)
|
|
129
|
+
@items_and_filters.inject([]) do |to_return, (item, item_meta)|
|
|
130
|
+
to_return << item if item_meta.empty? ||
|
|
131
|
+
MetadataFilter.apply?(@applies_predicate, item_meta, request_meta)
|
|
132
|
+
to_return
|
|
38
133
|
end
|
|
39
|
-
else
|
|
40
|
-
metadata[key].to_s == value.to_s
|
|
41
134
|
end
|
|
135
|
+
# :nocov:
|
|
42
136
|
end
|
|
43
137
|
end
|
|
44
138
|
|
|
45
|
-
|
|
139
|
+
# This implementation is much more complex, and is optimized for
|
|
140
|
+
# rare (or hopefully no) updates once the queries start. Updates
|
|
141
|
+
# incur a cost as it has to clear the memoization and keep track
|
|
142
|
+
# of applicable keys. Queries will be O(N) the first time an item
|
|
143
|
+
# is provided with a given set of applicable metadata; subsequent
|
|
144
|
+
# queries with items with the same set of applicable metadata will
|
|
145
|
+
# be O(1) due to internal memoization.
|
|
146
|
+
#
|
|
147
|
+
# This is ideal for use by config, where filterable items (e.g. hooks)
|
|
148
|
+
# are typically added at the start of the process (e.g. in `spec_helper`)
|
|
149
|
+
# and then repeatedly queried as example groups and examples are defined.
|
|
150
|
+
# @private
|
|
151
|
+
class QueryOptimized < UpdateOptimized
|
|
152
|
+
alias find_items_for items_for
|
|
153
|
+
private :find_items_for
|
|
46
154
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
155
|
+
def initialize(applies_predicate)
|
|
156
|
+
super
|
|
157
|
+
@applicable_keys = Set.new
|
|
158
|
+
@proc_keys = Set.new
|
|
159
|
+
@memoized_lookups = Hash.new do |hash, applicable_metadata|
|
|
160
|
+
hash[applicable_metadata] = find_items_for(applicable_metadata)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
50
163
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
164
|
+
def append(item, metadata)
|
|
165
|
+
super
|
|
166
|
+
handle_mutation(metadata)
|
|
167
|
+
end
|
|
56
168
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
169
|
+
def prepend(item, metadata)
|
|
170
|
+
super
|
|
171
|
+
handle_mutation(metadata)
|
|
172
|
+
end
|
|
61
173
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
174
|
+
def delete(item, metadata)
|
|
175
|
+
super
|
|
176
|
+
reconstruct_caches
|
|
177
|
+
end
|
|
66
178
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
179
|
+
def items_for(metadata)
|
|
180
|
+
# The filtering of `metadata` to `applicable_metadata` is the key thing
|
|
181
|
+
# that makes the memoization actually useful in practice, since each
|
|
182
|
+
# example and example group have different metadata (e.g. location and
|
|
183
|
+
# description). By filtering to the metadata keys our items care about,
|
|
184
|
+
# we can ignore extra metadata keys that differ for each example/group.
|
|
185
|
+
# For example, given `config.include DBHelpers, :db`, example groups
|
|
186
|
+
# can be split into these two sets: those that are tagged with `:db` and those
|
|
187
|
+
# that are not. For each set, this method for the first group in the set is
|
|
188
|
+
# still an `O(N)` calculation, but all subsequent groups in the set will be
|
|
189
|
+
# constant time lookups when they call this method.
|
|
190
|
+
applicable_metadata = applicable_metadata_from(metadata)
|
|
72
191
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
192
|
+
if applicable_metadata.any? { |k, _| @proc_keys.include?(k) }
|
|
193
|
+
# It's unsafe to memoize lookups involving procs (since they can
|
|
194
|
+
# be non-deterministic), so we skip the memoization in this case.
|
|
195
|
+
find_items_for(applicable_metadata)
|
|
196
|
+
else
|
|
197
|
+
@memoized_lookups[applicable_metadata]
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
78
202
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
203
|
+
def reconstruct_caches
|
|
204
|
+
@applicable_keys.clear
|
|
205
|
+
@proc_keys.clear
|
|
206
|
+
@items_and_filters.each do |_item, metadata|
|
|
207
|
+
handle_mutation(metadata)
|
|
208
|
+
end
|
|
84
209
|
end
|
|
85
|
-
end
|
|
86
210
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
211
|
+
def handle_mutation(metadata)
|
|
212
|
+
@applicable_keys.merge(metadata.keys)
|
|
213
|
+
@proc_keys.merge(proc_keys_from metadata)
|
|
214
|
+
@memoized_lookups.clear
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def applicable_metadata_from(metadata)
|
|
218
|
+
MetadataFilter.silence_metadata_example_group_deprecations do
|
|
219
|
+
@applicable_keys.inject({}) do |hash, key|
|
|
220
|
+
# :example_group is treated special here because...
|
|
221
|
+
# - In RSpec 2, example groups had an `:example_group` key
|
|
222
|
+
# - In RSpec 3, that key is deprecated (it was confusing!).
|
|
223
|
+
# - The key is not technically present in an example group metadata hash
|
|
224
|
+
# (and thus would fail the `metadata.key?(key)` check) but a value
|
|
225
|
+
# is provided when accessed via the hash's `default_proc`
|
|
226
|
+
# - Thus, for backwards compatibility, we have to explicitly check
|
|
227
|
+
# for `:example_group` here if it is one of the keys being used to
|
|
228
|
+
# filter.
|
|
229
|
+
hash[key] = metadata[key] if metadata.key?(key) || key == :example_group
|
|
230
|
+
hash
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def proc_keys_from(metadata)
|
|
236
|
+
metadata.each_with_object([]) do |(key, value), to_return|
|
|
237
|
+
to_return << key if Proc === value
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
unless [].respond_to?(:each_with_object) # For 1.8.7
|
|
242
|
+
# :nocov:
|
|
243
|
+
undef proc_keys_from
|
|
244
|
+
def proc_keys_from(metadata)
|
|
245
|
+
metadata.inject([]) do |to_return, (key, value)|
|
|
246
|
+
to_return << key if Proc === value
|
|
247
|
+
to_return
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
# :nocov:
|
|
251
|
+
end
|
|
92
252
|
end
|
|
93
253
|
end
|
|
94
254
|
end
|