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/hooks.rb
CHANGED
|
@@ -11,18 +11,21 @@ module RSpec
|
|
|
11
11
|
#
|
|
12
12
|
# @overload before(&block)
|
|
13
13
|
# @overload before(scope, &block)
|
|
14
|
-
# @param scope [Symbol] `:example`, `:context`, or `:suite`
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# @param
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
14
|
+
# @param scope [Symbol] `:example`, `:context`, or `:suite`
|
|
15
|
+
# (defaults to `:example`)
|
|
16
|
+
# @overload before(scope, *conditions, &block)
|
|
17
|
+
# @param scope [Symbol] `:example`, `:context`, or `:suite`
|
|
18
|
+
# (defaults to `:example`)
|
|
19
|
+
# @param conditions [Array<Symbol>, Hash] constrains this hook to
|
|
20
|
+
# examples matching these conditions e.g.
|
|
21
|
+
# `before(:example, :ui => true) { ... }` will only run with examples
|
|
22
|
+
# or groups declared with `:ui => true`. Symbols will be transformed
|
|
23
|
+
# into hash entries with `true` values.
|
|
21
24
|
# @overload before(conditions, &block)
|
|
22
25
|
# @param conditions [Hash]
|
|
23
26
|
# constrains this hook to examples matching these conditions e.g.
|
|
24
|
-
# `before(:example, :ui => true) { ... }` will only run with examples
|
|
25
|
-
# groups declared with `:ui => true`.
|
|
27
|
+
# `before(:example, :ui => true) { ... }` will only run with examples
|
|
28
|
+
# or groups declared with `:ui => true`.
|
|
26
29
|
#
|
|
27
30
|
# @see #after
|
|
28
31
|
# @see #around
|
|
@@ -32,39 +35,41 @@ module RSpec
|
|
|
32
35
|
# @see Configuration
|
|
33
36
|
#
|
|
34
37
|
# Declare a block of code to be run before each example (using `:example`)
|
|
35
|
-
# or once before any example (using `:context`). These are usually
|
|
36
|
-
# directly in the {ExampleGroup} to which they apply, but they
|
|
37
|
-
# be shared across multiple groups.
|
|
38
|
+
# or once before any example (using `:context`). These are usually
|
|
39
|
+
# declared directly in the {ExampleGroup} to which they apply, but they
|
|
40
|
+
# can also be shared across multiple groups.
|
|
38
41
|
#
|
|
39
42
|
# You can also use `before(:suite)` to run a block of code before any
|
|
40
|
-
# example groups are run. This should be declared in {RSpec.configure}
|
|
43
|
+
# example groups are run. This should be declared in {RSpec.configure}.
|
|
41
44
|
#
|
|
42
|
-
# Instance variables declared in `before(:example)` or `before(:context)`
|
|
43
|
-
# accessible within each example.
|
|
45
|
+
# Instance variables declared in `before(:example)` or `before(:context)`
|
|
46
|
+
# are accessible within each example.
|
|
44
47
|
#
|
|
45
48
|
# ### Order
|
|
46
49
|
#
|
|
47
50
|
# `before` hooks are stored in three scopes, which are run in order:
|
|
48
|
-
# `:suite`, `:context`, and `:example`. They can also be declared in
|
|
49
|
-
# different places: `RSpec.configure`, a parent group, the current
|
|
50
|
-
# They are run in the following order:
|
|
51
|
-
#
|
|
52
|
-
# before(:suite) #
|
|
53
|
-
# before(:context) #
|
|
54
|
-
# before(:context) #
|
|
55
|
-
# before(:context) #
|
|
56
|
-
# before(:example) #
|
|
57
|
-
# before(:example) #
|
|
58
|
-
# before(:example) #
|
|
59
|
-
#
|
|
60
|
-
# If more than one `before` is declared within any one
|
|
61
|
-
# in the order in which they are declared.
|
|
51
|
+
# `:suite`, `:context`, and `:example`. They can also be declared in
|
|
52
|
+
# several different places: `RSpec.configure`, a parent group, the current
|
|
53
|
+
# group. They are run in the following order:
|
|
54
|
+
#
|
|
55
|
+
# before(:suite) # Declared in RSpec.configure.
|
|
56
|
+
# before(:context) # Declared in RSpec.configure.
|
|
57
|
+
# before(:context) # Declared in a parent group.
|
|
58
|
+
# before(:context) # Declared in the current group.
|
|
59
|
+
# before(:example) # Declared in RSpec.configure.
|
|
60
|
+
# before(:example) # Declared in a parent group.
|
|
61
|
+
# before(:example) # Declared in the current group.
|
|
62
|
+
#
|
|
63
|
+
# If more than one `before` is declared within any one example group, they
|
|
64
|
+
# are run in the order in which they are declared. Any `around` hooks will
|
|
65
|
+
# execute after `before` context hooks but before any `before` example
|
|
66
|
+
# hook regardless of where they are declared.
|
|
62
67
|
#
|
|
63
68
|
# ### Conditions
|
|
64
69
|
#
|
|
65
|
-
# When you add a conditions hash to `before(:example)` or
|
|
66
|
-
# RSpec will only apply that hook to groups or
|
|
67
|
-
# conditions. e.g.
|
|
70
|
+
# When you add a conditions hash to `before(:example)` or
|
|
71
|
+
# `before(:context)`, RSpec will only apply that hook to groups or
|
|
72
|
+
# examples that match the conditions. e.g.
|
|
68
73
|
#
|
|
69
74
|
# RSpec.configure do |config|
|
|
70
75
|
# config.before(:example, :authorized => true) do
|
|
@@ -72,20 +77,27 @@ module RSpec
|
|
|
72
77
|
# end
|
|
73
78
|
# end
|
|
74
79
|
#
|
|
75
|
-
# describe Something, :authorized => true do
|
|
76
|
-
# #
|
|
80
|
+
# RSpec.describe Something, :authorized => true do
|
|
81
|
+
# # The before hook will run in before each example in this group.
|
|
77
82
|
# end
|
|
78
83
|
#
|
|
79
|
-
# describe SomethingElse do
|
|
84
|
+
# RSpec.describe SomethingElse do
|
|
80
85
|
# it "does something", :authorized => true do
|
|
81
|
-
# #
|
|
86
|
+
# # The before hook will run before this example.
|
|
82
87
|
# end
|
|
83
88
|
#
|
|
84
89
|
# it "does something else" do
|
|
85
|
-
# #
|
|
90
|
+
# # The hook will not run before this example.
|
|
86
91
|
# end
|
|
87
92
|
# end
|
|
88
93
|
#
|
|
94
|
+
# Note that filtered config `:context` hooks can still be applied
|
|
95
|
+
# to individual examples that have matching metadata. Just like
|
|
96
|
+
# Ruby's object model is that every object has a singleton class
|
|
97
|
+
# which has only a single instance, RSpec's model is that every
|
|
98
|
+
# example has a singleton example group containing just the one
|
|
99
|
+
# example.
|
|
100
|
+
#
|
|
89
101
|
# ### Warning: `before(:suite, :with => :conditions)`
|
|
90
102
|
#
|
|
91
103
|
# The conditions hash is used to match against specific examples. Since
|
|
@@ -113,22 +125,23 @@ module RSpec
|
|
|
113
125
|
# recommend that you avoid this as there are a number of gotchas, as well
|
|
114
126
|
# as things that simply don't work.
|
|
115
127
|
#
|
|
116
|
-
# ####
|
|
128
|
+
# #### Context
|
|
117
129
|
#
|
|
118
|
-
# `before(:context)` is run in an example that is generated to provide
|
|
119
|
-
# context for the block.
|
|
130
|
+
# `before(:context)` is run in an example that is generated to provide
|
|
131
|
+
# group context for the block.
|
|
120
132
|
#
|
|
121
|
-
# ####
|
|
133
|
+
# #### Instance variables
|
|
122
134
|
#
|
|
123
|
-
# Instance variables declared in `before(:context)` are shared across all
|
|
124
|
-
# examples in the group.
|
|
135
|
+
# Instance variables declared in `before(:context)` are shared across all
|
|
136
|
+
# the examples in the group. This means that each example can change the
|
|
125
137
|
# state of a shared object, resulting in an ordering dependency that can
|
|
126
138
|
# make it difficult to reason about failures.
|
|
127
139
|
#
|
|
128
|
-
# ####
|
|
140
|
+
# #### Unsupported RSpec constructs
|
|
129
141
|
#
|
|
130
142
|
# RSpec has several constructs that reset state between each example
|
|
131
|
-
# automatically. These are not intended for use from within
|
|
143
|
+
# automatically. These are not intended for use from within
|
|
144
|
+
# `before(:context)`:
|
|
132
145
|
#
|
|
133
146
|
# * `let` declarations
|
|
134
147
|
# * `subject` declarations
|
|
@@ -138,30 +151,30 @@ module RSpec
|
|
|
138
151
|
#
|
|
139
152
|
# Mock object frameworks and database transaction managers (like
|
|
140
153
|
# ActiveRecord) are typically designed around the idea of setting up
|
|
141
|
-
# before an example, running that one example, and then tearing down.
|
|
142
|
-
#
|
|
143
|
-
# `before(:context)`, but get torn down before the first real example is
|
|
144
|
-
# run.
|
|
154
|
+
# before an example, running that one example, and then tearing down. This
|
|
155
|
+
# means that mocks and stubs can (sometimes) be declared in
|
|
156
|
+
# `before(:context)`, but get torn down before the first real example is
|
|
157
|
+
# ever run.
|
|
145
158
|
#
|
|
146
|
-
# You _can_ create database-backed model objects in a `before(:context)`
|
|
147
|
-
# rspec-rails, but it will not be wrapped in a transaction for you, so
|
|
159
|
+
# You _can_ create database-backed model objects in a `before(:context)`
|
|
160
|
+
# in rspec-rails, but it will not be wrapped in a transaction for you, so
|
|
148
161
|
# you are on your own to clean up in an `after(:context)` block.
|
|
149
162
|
#
|
|
150
163
|
# @example before(:example) declared in an {ExampleGroup}
|
|
151
164
|
#
|
|
152
|
-
# describe Thing do
|
|
165
|
+
# RSpec.describe Thing do
|
|
153
166
|
# before(:example) do
|
|
154
167
|
# @thing = Thing.new
|
|
155
168
|
# end
|
|
156
169
|
#
|
|
157
170
|
# it "does something" do
|
|
158
|
-
# #
|
|
171
|
+
# # Here you can access @thing.
|
|
159
172
|
# end
|
|
160
173
|
# end
|
|
161
174
|
#
|
|
162
175
|
# @example before(:context) declared in an {ExampleGroup}
|
|
163
176
|
#
|
|
164
|
-
# describe Parser do
|
|
177
|
+
# RSpec.describe Parser do
|
|
165
178
|
# before(:context) do
|
|
166
179
|
# File.open(file_to_parse, 'w') do |f|
|
|
167
180
|
# f.write <<-CONTENT
|
|
@@ -181,6 +194,9 @@ module RSpec
|
|
|
181
194
|
#
|
|
182
195
|
# @note The `:example` and `:context` scopes are also available as
|
|
183
196
|
# `:each` and `:all`, respectively. Use whichever you prefer.
|
|
197
|
+
# @note The `:suite` scope is only supported for hooks registered on
|
|
198
|
+
# `RSpec.configuration` since they exist independently of any
|
|
199
|
+
# example or example group.
|
|
184
200
|
def before(*args, &block)
|
|
185
201
|
hooks.register :append, :before, *args, &block
|
|
186
202
|
end
|
|
@@ -198,18 +214,21 @@ module RSpec
|
|
|
198
214
|
# @api public
|
|
199
215
|
# @overload after(&block)
|
|
200
216
|
# @overload after(scope, &block)
|
|
201
|
-
# @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
# @param
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
217
|
+
# @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
|
|
218
|
+
# `:example`)
|
|
219
|
+
# @overload after(scope, *conditions, &block)
|
|
220
|
+
# @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
|
|
221
|
+
# `:example`)
|
|
222
|
+
# @param conditions [Array<Symbol>, Hash] constrains this hook to
|
|
223
|
+
# examples matching these conditions e.g.
|
|
224
|
+
# `after(:example, :ui => true) { ... }` will only run with examples
|
|
225
|
+
# or groups declared with `:ui => true`. Symbols will be transformed
|
|
226
|
+
# into hash entries with `true` values.
|
|
208
227
|
# @overload after(conditions, &block)
|
|
209
228
|
# @param conditions [Hash]
|
|
210
229
|
# constrains this hook to examples matching these conditions e.g.
|
|
211
|
-
# `after(:example, :ui => true) { ... }` will only run with examples
|
|
212
|
-
# groups declared with `:ui => true`.
|
|
230
|
+
# `after(:example, :ui => true) { ... }` will only run with examples
|
|
231
|
+
# or groups declared with `:ui => true`.
|
|
213
232
|
#
|
|
214
233
|
# @see #before
|
|
215
234
|
# @see #around
|
|
@@ -218,38 +237,43 @@ module RSpec
|
|
|
218
237
|
# @see SharedExampleGroup
|
|
219
238
|
# @see Configuration
|
|
220
239
|
#
|
|
221
|
-
# Declare a block of code to be run after each example (using `:example`)
|
|
222
|
-
# once after all examples n the context (using `:context`). See
|
|
223
|
-
# more information about ordering.
|
|
240
|
+
# Declare a block of code to be run after each example (using `:example`)
|
|
241
|
+
# or once after all examples n the context (using `:context`). See
|
|
242
|
+
# {#before} for more information about ordering.
|
|
224
243
|
#
|
|
225
244
|
# ### Exceptions
|
|
226
245
|
#
|
|
227
246
|
# `after` hooks are guaranteed to run even when there are exceptions in
|
|
228
|
-
# `before` hooks or examples.
|
|
247
|
+
# `before` hooks or examples. When an exception is raised in an after
|
|
229
248
|
# block, the exception is captured for later reporting, and subsequent
|
|
230
249
|
# `after` blocks are run.
|
|
231
250
|
#
|
|
232
251
|
# ### Order
|
|
233
252
|
#
|
|
234
253
|
# `after` hooks are stored in three scopes, which are run in order:
|
|
235
|
-
# `:example`, `:context`, and `:suite`. They can also be declared in
|
|
236
|
-
# different places: `RSpec.configure`, a parent group, the current
|
|
237
|
-
# They are run in the following order:
|
|
238
|
-
#
|
|
239
|
-
# after(:example) #
|
|
240
|
-
# after(:example) #
|
|
241
|
-
# after(:example) #
|
|
242
|
-
# after(:context) #
|
|
243
|
-
# after(:context) #
|
|
244
|
-
# after(:context) #
|
|
245
|
-
# after(:suite) #
|
|
254
|
+
# `:example`, `:context`, and `:suite`. They can also be declared in
|
|
255
|
+
# several different places: `RSpec.configure`, a parent group, the current
|
|
256
|
+
# group. They are run in the following order:
|
|
257
|
+
#
|
|
258
|
+
# after(:example) # Declared in the current group.
|
|
259
|
+
# after(:example) # Declared in a parent group.
|
|
260
|
+
# after(:example) # Declared in RSpec.configure.
|
|
261
|
+
# after(:context) # Declared in the current group.
|
|
262
|
+
# after(:context) # Declared in a parent group.
|
|
263
|
+
# after(:context) # Declared in RSpec.configure.
|
|
264
|
+
# after(:suite) # Declared in RSpec.configure.
|
|
246
265
|
#
|
|
247
266
|
# This is the reverse of the order in which `before` hooks are run.
|
|
248
|
-
# Similarly, if more than one `after` is declared within any
|
|
249
|
-
# they are run in reverse order of that in which they are declared.
|
|
267
|
+
# Similarly, if more than one `after` is declared within any example
|
|
268
|
+
# group, they are run in reverse order of that in which they are declared.
|
|
269
|
+
# Also `around` hooks will run after any `after` example hooks are
|
|
270
|
+
# invoked but before any `after` context hooks.
|
|
250
271
|
#
|
|
251
272
|
# @note The `:example` and `:context` scopes are also available as
|
|
252
273
|
# `:each` and `:all`, respectively. Use whichever you prefer.
|
|
274
|
+
# @note The `:suite` scope is only supported for hooks registered on
|
|
275
|
+
# `RSpec.configuration` since they exist independently of any
|
|
276
|
+
# example or example group.
|
|
253
277
|
def after(*args, &block)
|
|
254
278
|
hooks.register :prepend, :after, *args, &block
|
|
255
279
|
end
|
|
@@ -270,25 +294,25 @@ module RSpec
|
|
|
270
294
|
# @param scope [Symbol] `:example` (defaults to `:example`)
|
|
271
295
|
# present for syntax parity with `before` and `after`, but
|
|
272
296
|
# `:example`/`:each` is the only supported value.
|
|
273
|
-
# @overload around(scope, conditions, &block)
|
|
297
|
+
# @overload around(scope, *conditions, &block)
|
|
274
298
|
# @param scope [Symbol] `:example` (defaults to `:example`)
|
|
275
299
|
# present for syntax parity with `before` and `after`, but
|
|
276
300
|
# `:example`/`:each` is the only supported value.
|
|
277
|
-
# @param conditions [Hash]
|
|
278
|
-
#
|
|
279
|
-
# `around(:example, :ui => true) { ... }` will only run with examples
|
|
280
|
-
# groups declared with `:ui => true`.
|
|
301
|
+
# @param conditions [Array<Symbol>, Hash] constrains this hook to
|
|
302
|
+
# examples matching these conditions e.g.
|
|
303
|
+
# `around(:example, :ui => true) { ... }` will only run with examples
|
|
304
|
+
# or groups declared with `:ui => true`. Symbols will be transformed
|
|
305
|
+
# into hash entries with `true` values.
|
|
281
306
|
# @overload around(conditions, &block)
|
|
282
|
-
# @param conditions [Hash]
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
# groups declared with `:ui => true`.
|
|
307
|
+
# @param conditions [Hash] constrains this hook to examples matching
|
|
308
|
+
# these conditions e.g. `around(:example, :ui => true) { ... }` will
|
|
309
|
+
# only run with examples or groups declared with `:ui => true`.
|
|
286
310
|
#
|
|
287
311
|
# @yield [Example] the example to run
|
|
288
312
|
#
|
|
289
313
|
# @note the syntax of `around` is similar to that of `before` and `after`
|
|
290
314
|
# but the semantics are quite different. `before` and `after` hooks are
|
|
291
|
-
# run in the context of
|
|
315
|
+
# run in the context of the examples with which they are associated,
|
|
292
316
|
# whereas `around` hooks are actually responsible for running the
|
|
293
317
|
# examples. Consequently, `around` hooks do not have direct access to
|
|
294
318
|
# resources that are made available within the examples and their
|
|
@@ -300,19 +324,28 @@ module RSpec
|
|
|
300
324
|
# after the example. It is your responsibility to run the example:
|
|
301
325
|
#
|
|
302
326
|
# around(:example) do |ex|
|
|
303
|
-
# #
|
|
327
|
+
# # Do some stuff before.
|
|
304
328
|
# ex.run
|
|
305
|
-
# #
|
|
329
|
+
# # Do some stuff after.
|
|
306
330
|
# end
|
|
307
331
|
#
|
|
308
332
|
# The yielded example aliases `run` with `call`, which lets you treat it
|
|
309
|
-
# like a `Proc`.
|
|
333
|
+
# like a `Proc`. This is especially handy when working with libraries
|
|
310
334
|
# that manage their own setup and teardown using a block or proc syntax,
|
|
311
335
|
# e.g.
|
|
312
336
|
#
|
|
313
337
|
# around(:example) {|ex| Database.transaction(&ex)}
|
|
314
338
|
# around(:example) {|ex| FakeFS(&ex)}
|
|
315
339
|
#
|
|
340
|
+
# ### Order
|
|
341
|
+
#
|
|
342
|
+
# The `around` hooks execute surrounding an example and its hooks.
|
|
343
|
+
#
|
|
344
|
+
# This means after any `before` context hooks, but before any `before`
|
|
345
|
+
# example hooks, and similarly after any `after` example hooks but before
|
|
346
|
+
# any `after` context hooks.
|
|
347
|
+
#
|
|
348
|
+
# They are not a synonym for `before`/`after`.
|
|
316
349
|
def around(*args, &block)
|
|
317
350
|
hooks.register :prepend, :around, *args, &block
|
|
318
351
|
end
|
|
@@ -320,28 +353,11 @@ module RSpec
|
|
|
320
353
|
# @private
|
|
321
354
|
# Holds the various registered hooks.
|
|
322
355
|
def hooks
|
|
323
|
-
@hooks ||= HookCollections.new(self,
|
|
324
|
-
:around => { :example => AroundHookCollection.new },
|
|
325
|
-
:before => { :example => HookCollection.new, :context => HookCollection.new, :suite => HookCollection.new },
|
|
326
|
-
:after => { :example => HookCollection.new, :context => HookCollection.new, :suite => HookCollection.new }
|
|
327
|
-
)
|
|
356
|
+
@hooks ||= HookCollections.new(self, FilterableItemRepository::UpdateOptimized)
|
|
328
357
|
end
|
|
329
358
|
|
|
330
|
-
private
|
|
331
|
-
|
|
332
359
|
# @private
|
|
333
|
-
|
|
334
|
-
attr_reader :block, :options
|
|
335
|
-
|
|
336
|
-
def initialize(block, options)
|
|
337
|
-
@block = block
|
|
338
|
-
@options = options
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def options_apply?(example_or_group)
|
|
342
|
-
example_or_group.all_apply?(options)
|
|
343
|
-
end
|
|
344
|
-
end
|
|
360
|
+
Hook = Struct.new(:block, :options)
|
|
345
361
|
|
|
346
362
|
# @private
|
|
347
363
|
class BeforeHook < Hook
|
|
@@ -353,7 +369,9 @@ module RSpec
|
|
|
353
369
|
# @private
|
|
354
370
|
class AfterHook < Hook
|
|
355
371
|
def run(example)
|
|
356
|
-
example.
|
|
372
|
+
example.instance_exec(example, &block)
|
|
373
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
|
|
374
|
+
example.set_exception(ex)
|
|
357
375
|
end
|
|
358
376
|
end
|
|
359
377
|
|
|
@@ -361,131 +379,116 @@ module RSpec
|
|
|
361
379
|
class AfterContextHook < Hook
|
|
362
380
|
def run(example)
|
|
363
381
|
example.instance_exec(example, &block)
|
|
364
|
-
rescue
|
|
365
|
-
|
|
366
|
-
RSpec.configuration.reporter.message <<-EOS
|
|
367
|
-
|
|
368
|
-
An error occurred in an `after(:context)` hook.
|
|
369
|
-
#{e.class}: #{e.message}
|
|
370
|
-
occurred at #{e.backtrace.first}
|
|
371
|
-
|
|
372
|
-
EOS
|
|
382
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
|
383
|
+
RSpec.configuration.reporter.notify_non_example_exception(e, "An error occurred in an `after(:context)` hook.")
|
|
373
384
|
end
|
|
374
385
|
end
|
|
375
386
|
|
|
376
387
|
# @private
|
|
377
|
-
AroundHook
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
attr_reader :hooks
|
|
386
|
-
protected :hooks
|
|
387
|
-
|
|
388
|
-
alias append push
|
|
389
|
-
alias prepend unshift
|
|
390
|
-
|
|
391
|
-
def initialize(hooks=[])
|
|
392
|
-
@hooks = hooks
|
|
393
|
-
end
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
# @private
|
|
397
|
-
class HookCollection < BaseHookCollection
|
|
398
|
-
def for(example_or_group)
|
|
399
|
-
self.class.
|
|
400
|
-
new(hooks.select {|hook| hook.options_apply?(example_or_group)}).
|
|
401
|
-
with(example_or_group)
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
def with(example)
|
|
405
|
-
@example = example
|
|
406
|
-
self
|
|
388
|
+
class AroundHook < Hook
|
|
389
|
+
def execute_with(example, procsy)
|
|
390
|
+
example.instance_exec(procsy, &block)
|
|
391
|
+
return if procsy.executed?
|
|
392
|
+
Pending.mark_skipped!(example,
|
|
393
|
+
"#{hook_description} did not execute the example")
|
|
407
394
|
end
|
|
408
395
|
|
|
409
|
-
|
|
410
|
-
|
|
396
|
+
if Proc.method_defined?(:source_location)
|
|
397
|
+
def hook_description
|
|
398
|
+
"around hook at #{Metadata.relative_path(block.source_location.join(':'))}"
|
|
399
|
+
end
|
|
400
|
+
else # for 1.8.7
|
|
401
|
+
# :nocov:
|
|
402
|
+
def hook_description
|
|
403
|
+
"around hook"
|
|
404
|
+
end
|
|
405
|
+
# :nocov:
|
|
411
406
|
end
|
|
412
407
|
end
|
|
413
408
|
|
|
414
409
|
# @private
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
410
|
+
#
|
|
411
|
+
# This provides the primary API used by other parts of rspec-core. By hiding all
|
|
412
|
+
# implementation details behind this facade, it's allowed us to heavily optimize
|
|
413
|
+
# this, so that, for example, hook collection objects are only instantiated when
|
|
414
|
+
# a hook is added. This allows us to avoid many object allocations for the common
|
|
415
|
+
# case of a group having no hooks.
|
|
416
|
+
#
|
|
417
|
+
# This is only possible because this interface provides a "tell, don't ask"-style
|
|
418
|
+
# API, so that callers _tell_ this class what to do with the hooks, rather than
|
|
419
|
+
# asking this class for a list of hooks, and then doing something with them.
|
|
420
|
+
class HookCollections
|
|
421
|
+
def initialize(owner, filterable_item_repo_class)
|
|
422
|
+
@owner = owner
|
|
423
|
+
@filterable_item_repo_class = filterable_item_repo_class
|
|
424
|
+
@before_example_hooks = nil
|
|
425
|
+
@after_example_hooks = nil
|
|
426
|
+
@before_context_hooks = nil
|
|
427
|
+
@after_context_hooks = nil
|
|
428
|
+
@around_example_hooks = nil
|
|
425
429
|
end
|
|
426
430
|
|
|
427
|
-
def
|
|
428
|
-
|
|
429
|
-
procsy.wrap do
|
|
430
|
-
@example.instance_exec(procsy, &around_hook.block)
|
|
431
|
-
end
|
|
432
|
-
end.call
|
|
433
|
-
end
|
|
434
|
-
end
|
|
431
|
+
def register_globals(host, globals)
|
|
432
|
+
parent_groups = host.parent_groups
|
|
435
433
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
@group = group
|
|
440
|
-
self
|
|
441
|
-
end
|
|
434
|
+
process(host, parent_groups, globals, :before, :example, &:options)
|
|
435
|
+
process(host, parent_groups, globals, :after, :example, &:options)
|
|
436
|
+
process(host, parent_groups, globals, :around, :example, &:options)
|
|
442
437
|
|
|
443
|
-
|
|
444
|
-
|
|
438
|
+
process(host, parent_groups, globals, :before, :context, &:options)
|
|
439
|
+
process(host, parent_groups, globals, :after, :context, &:options)
|
|
445
440
|
end
|
|
446
|
-
end
|
|
447
441
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
def initialize(owner, data)
|
|
451
|
-
@owner = owner
|
|
452
|
-
@data = data
|
|
453
|
-
end
|
|
442
|
+
def register_global_singleton_context_hooks(example, globals)
|
|
443
|
+
parent_groups = example.example_group.parent_groups
|
|
454
444
|
|
|
455
|
-
|
|
456
|
-
|
|
445
|
+
process(example, parent_groups, globals, :before, :context) { {} }
|
|
446
|
+
process(example, parent_groups, globals, :after, :context) { {} }
|
|
457
447
|
end
|
|
458
448
|
|
|
459
|
-
def
|
|
460
|
-
|
|
461
|
-
process(host, globals, :after, :example)
|
|
462
|
-
process(host, globals, :around, :example)
|
|
463
|
-
|
|
464
|
-
process(host, globals, :before, :context)
|
|
465
|
-
process(host, globals, :after, :context)
|
|
466
|
-
end
|
|
449
|
+
def register(prepend_or_append, position, *args, &block)
|
|
450
|
+
scope, options = scope_and_options_from(*args)
|
|
467
451
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
452
|
+
if scope == :suite
|
|
453
|
+
# TODO: consider making this an error in RSpec 4. For SemVer reasons,
|
|
454
|
+
# we are only warning in RSpec 3.
|
|
455
|
+
RSpec.warn_with "WARNING: `#{position}(:suite)` hooks are only supported on " \
|
|
456
|
+
"the RSpec configuration object. This " \
|
|
457
|
+
"`#{position}(:suite)` hook, registered on an example " \
|
|
458
|
+
"group, will be ignored."
|
|
459
|
+
return
|
|
460
|
+
elsif scope == :context && position == :around
|
|
461
|
+
# TODO: consider making this an error in RSpec 4. For SemVer reasons,
|
|
462
|
+
# we are only warning in RSpec 3.
|
|
463
|
+
RSpec.warn_with "WARNING: `around(:context)` hooks are not supported and " \
|
|
464
|
+
"behave like `around(:example)."
|
|
465
|
+
end
|
|
473
466
|
|
|
474
|
-
|
|
475
|
-
scope, options
|
|
476
|
-
self[hook][scope].__send__(prepend_or_append, HOOK_TYPES[hook][scope].new(block, options))
|
|
467
|
+
hook = HOOK_TYPES[position][scope].new(block, options)
|
|
468
|
+
ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options)
|
|
477
469
|
end
|
|
478
470
|
|
|
479
471
|
# @private
|
|
480
472
|
#
|
|
481
473
|
# Runs all of the blocks stored with the hook in the context of the
|
|
482
474
|
# example. If no example is provided, just calls the hook directly.
|
|
483
|
-
def run(
|
|
475
|
+
def run(position, scope, example_or_group)
|
|
484
476
|
return if RSpec.configuration.dry_run?
|
|
485
|
-
|
|
477
|
+
|
|
478
|
+
if scope == :context
|
|
479
|
+
unless example_or_group.class.metadata[:skip]
|
|
480
|
+
run_owned_hooks_for(position, :context, example_or_group)
|
|
481
|
+
end
|
|
482
|
+
else
|
|
483
|
+
case position
|
|
484
|
+
when :before then run_example_hooks_for(example_or_group, :before, :reverse_each)
|
|
485
|
+
when :after then run_example_hooks_for(example_or_group, :after, :each)
|
|
486
|
+
when :around then run_around_example_hooks_for(example_or_group) { yield }
|
|
487
|
+
end
|
|
488
|
+
end
|
|
486
489
|
end
|
|
487
490
|
|
|
488
|
-
SCOPES = [:example, :context
|
|
491
|
+
SCOPES = [:example, :context]
|
|
489
492
|
|
|
490
493
|
SCOPE_ALIASES = { :each => :example, :all => :context }
|
|
491
494
|
|
|
@@ -497,17 +500,89 @@ EOS
|
|
|
497
500
|
|
|
498
501
|
HOOK_TYPES[:after][:context] = AfterContextHook
|
|
499
502
|
|
|
503
|
+
protected
|
|
504
|
+
|
|
505
|
+
EMPTY_HOOK_ARRAY = [].freeze
|
|
506
|
+
|
|
507
|
+
def matching_hooks_for(position, scope, example_or_group)
|
|
508
|
+
repository = hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }
|
|
509
|
+
|
|
510
|
+
# It would be nice to not have to switch on type here, but
|
|
511
|
+
# we don't want to define `ExampleGroup#metadata` because then
|
|
512
|
+
# `metadata` from within an individual example would return the
|
|
513
|
+
# group's metadata but the user would probably expect it to be
|
|
514
|
+
# the example's metadata.
|
|
515
|
+
metadata = case example_or_group
|
|
516
|
+
when ExampleGroup then example_or_group.class.metadata
|
|
517
|
+
else example_or_group.metadata
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
repository.items_for(metadata)
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def all_hooks_for(position, scope)
|
|
524
|
+
hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def run_owned_hooks_for(position, scope, example_or_group)
|
|
528
|
+
matching_hooks_for(position, scope, example_or_group).each do |hook|
|
|
529
|
+
hook.run(example_or_group)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def processable_hooks_for(position, scope, host)
|
|
534
|
+
if scope == :example
|
|
535
|
+
all_hooks_for(position, scope)
|
|
536
|
+
else
|
|
537
|
+
matching_hooks_for(position, scope, host)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
500
541
|
private
|
|
501
542
|
|
|
502
|
-
def
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
543
|
+
def hooks_for(position, scope)
|
|
544
|
+
if position == :before
|
|
545
|
+
scope == :example ? @before_example_hooks : @before_context_hooks
|
|
546
|
+
elsif position == :after
|
|
547
|
+
scope == :example ? @after_example_hooks : @after_context_hooks
|
|
548
|
+
else # around
|
|
549
|
+
@around_example_hooks
|
|
550
|
+
end || yield
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def ensure_hooks_initialized_for(position, scope)
|
|
554
|
+
if position == :before
|
|
555
|
+
if scope == :example
|
|
556
|
+
@before_example_hooks ||= @filterable_item_repo_class.new(:all?)
|
|
557
|
+
else
|
|
558
|
+
@before_context_hooks ||= @filterable_item_repo_class.new(:all?)
|
|
559
|
+
end
|
|
560
|
+
elsif position == :after
|
|
561
|
+
if scope == :example
|
|
562
|
+
@after_example_hooks ||= @filterable_item_repo_class.new(:all?)
|
|
563
|
+
else
|
|
564
|
+
@after_context_hooks ||= @filterable_item_repo_class.new(:all?)
|
|
565
|
+
end
|
|
566
|
+
else # around
|
|
567
|
+
@around_example_hooks ||= @filterable_item_repo_class.new(:all?)
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def process(host, parent_groups, globals, position, scope)
|
|
572
|
+
hooks_to_process = globals.processable_hooks_for(position, scope, host)
|
|
573
|
+
return if hooks_to_process.empty?
|
|
574
|
+
|
|
575
|
+
hooks_to_process -= FlatMap.flat_map(parent_groups) do |group|
|
|
576
|
+
group.hooks.all_hooks_for(position, scope)
|
|
507
577
|
end
|
|
578
|
+
return if hooks_to_process.empty?
|
|
579
|
+
|
|
580
|
+
repository = ensure_hooks_initialized_for(position, scope)
|
|
581
|
+
hooks_to_process.each { |hook| repository.append hook, (yield hook) }
|
|
508
582
|
end
|
|
509
583
|
|
|
510
584
|
def scope_and_options_from(*args)
|
|
585
|
+
return :suite if args.first == :suite
|
|
511
586
|
scope = extract_scope_from(args)
|
|
512
587
|
meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
|
|
513
588
|
return scope, meta
|
|
@@ -517,58 +592,53 @@ EOS
|
|
|
517
592
|
if known_scope?(args.first)
|
|
518
593
|
normalized_scope_for(args.shift)
|
|
519
594
|
elsif args.any? { |a| a.is_a?(Symbol) }
|
|
520
|
-
error_message = "You must explicitly give a scope
|
|
595
|
+
error_message = "You must explicitly give a scope " \
|
|
596
|
+
"(#{SCOPES.join(", ")}) or scope alias " \
|
|
597
|
+
"(#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as " \
|
|
598
|
+
"metadata for a hook."
|
|
521
599
|
raise ArgumentError.new error_message
|
|
522
600
|
else
|
|
523
601
|
:example
|
|
524
602
|
end
|
|
525
603
|
end
|
|
526
604
|
|
|
527
|
-
# @api private
|
|
528
605
|
def known_scope?(scope)
|
|
529
606
|
SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope)
|
|
530
607
|
end
|
|
531
608
|
|
|
532
|
-
# @api private
|
|
533
609
|
def normalized_scope_for(scope)
|
|
534
610
|
SCOPE_ALIASES[scope] || scope
|
|
535
611
|
end
|
|
536
612
|
|
|
537
|
-
def
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
before_context_hooks_for(example_or_group)
|
|
541
|
-
when [:after, :context]
|
|
542
|
-
after_context_hooks_for(example_or_group)
|
|
543
|
-
when [:around, :example]
|
|
544
|
-
around_example_hooks_for(example_or_group, initial_procsy)
|
|
545
|
-
when [:before, :example]
|
|
546
|
-
before_example_hooks_for(example_or_group)
|
|
547
|
-
when [:after, :example]
|
|
548
|
-
after_example_hooks_for(example_or_group)
|
|
549
|
-
when [:before, :suite], [:after, :suite]
|
|
550
|
-
self[hook][:suite].with(example_or_group)
|
|
613
|
+
def run_example_hooks_for(example, position, each_method)
|
|
614
|
+
owner_parent_groups.__send__(each_method) do |group|
|
|
615
|
+
group.hooks.run_owned_hooks_for(position, :example, example)
|
|
551
616
|
end
|
|
552
617
|
end
|
|
553
618
|
|
|
554
|
-
def
|
|
555
|
-
|
|
556
|
-
|
|
619
|
+
def run_around_example_hooks_for(example)
|
|
620
|
+
hooks = FlatMap.flat_map(owner_parent_groups) do |group|
|
|
621
|
+
group.hooks.matching_hooks_for(:around, :example, example)
|
|
622
|
+
end
|
|
557
623
|
|
|
558
|
-
|
|
559
|
-
GroupHookCollection.new(self[:after][:context]).for(group)
|
|
560
|
-
end
|
|
624
|
+
return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy`
|
|
561
625
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
end
|
|
626
|
+
initial_procsy = Example::Procsy.new(example) { yield }
|
|
627
|
+
hooks.inject(initial_procsy) do |procsy, around_hook|
|
|
628
|
+
procsy.wrap { around_hook.execute_with(example, procsy) }
|
|
629
|
+
end.call
|
|
566
630
|
end
|
|
567
631
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
end
|
|
632
|
+
if respond_to?(:singleton_class) && singleton_class.ancestors.include?(singleton_class)
|
|
633
|
+
def owner_parent_groups
|
|
634
|
+
@owner.parent_groups
|
|
635
|
+
end
|
|
636
|
+
else # Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035)
|
|
637
|
+
# :nocov:
|
|
638
|
+
def owner_parent_groups
|
|
639
|
+
@owner_parent_groups ||= [@owner] + @owner.parent_groups
|
|
640
|
+
end
|
|
641
|
+
# :nocov:
|
|
572
642
|
end
|
|
573
643
|
end
|
|
574
644
|
end
|