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.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +2 -1
  5. data/Changelog.md +888 -2
  6. data/{License.txt → LICENSE.md} +6 -5
  7. data/README.md +165 -24
  8. data/lib/rspec/autorun.rb +1 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +19 -20
  10. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  11. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  12. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  13. data/lib/rspec/core/bisect/server.rb +61 -0
  14. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  15. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  16. data/lib/rspec/core/bisect/utilities.rb +69 -0
  17. data/lib/rspec/core/configuration.rb +1287 -246
  18. data/lib/rspec/core/configuration_options.rb +95 -35
  19. data/lib/rspec/core/did_you_mean.rb +46 -0
  20. data/lib/rspec/core/drb.rb +21 -12
  21. data/lib/rspec/core/dsl.rb +10 -6
  22. data/lib/rspec/core/example.rb +305 -113
  23. data/lib/rspec/core/example_group.rb +431 -223
  24. data/lib/rspec/core/example_status_persister.rb +235 -0
  25. data/lib/rspec/core/filter_manager.rb +86 -115
  26. data/lib/rspec/core/flat_map.rb +6 -4
  27. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  28. data/lib/rspec/core/formatters/base_formatter.rb +14 -116
  29. data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
  30. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  31. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  32. data/lib/rspec/core/formatters/console_codes.rb +29 -18
  33. data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
  34. data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
  35. data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
  36. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  37. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  38. data/lib/rspec/core/formatters/helpers.rb +45 -15
  39. data/lib/rspec/core/formatters/html_formatter.rb +33 -28
  40. data/lib/rspec/core/formatters/html_printer.rb +30 -20
  41. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  42. data/lib/rspec/core/formatters/json_formatter.rb +18 -9
  43. data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
  44. data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
  45. data/lib/rspec/core/formatters/protocol.rb +182 -0
  46. data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
  47. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  48. data/lib/rspec/core/formatters.rb +81 -41
  49. data/lib/rspec/core/hooks.rb +314 -244
  50. data/lib/rspec/core/invocations.rb +87 -0
  51. data/lib/rspec/core/memoized_helpers.rb +161 -51
  52. data/lib/rspec/core/metadata.rb +132 -61
  53. data/lib/rspec/core/metadata_filter.rb +224 -64
  54. data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
  55. data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
  56. data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
  57. data/lib/rspec/core/mocking_adapters/null.rb +2 -0
  58. data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
  59. data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
  60. data/lib/rspec/core/notifications.rb +192 -206
  61. data/lib/rspec/core/option_parser.rb +174 -69
  62. data/lib/rspec/core/ordering.rb +48 -35
  63. data/lib/rspec/core/output_wrapper.rb +29 -0
  64. data/lib/rspec/core/pending.rb +25 -33
  65. data/lib/rspec/core/profiler.rb +34 -0
  66. data/lib/rspec/core/project_initializer/.rspec +0 -2
  67. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
  68. data/lib/rspec/core/project_initializer.rb +5 -3
  69. data/lib/rspec/core/rake_task.rb +99 -55
  70. data/lib/rspec/core/reporter.rb +128 -15
  71. data/lib/rspec/core/ruby_project.rb +14 -6
  72. data/lib/rspec/core/runner.rb +96 -45
  73. data/lib/rspec/core/sandbox.rb +37 -0
  74. data/lib/rspec/core/set.rb +54 -0
  75. data/lib/rspec/core/shared_example_group.rb +133 -43
  76. data/lib/rspec/core/shell_escape.rb +49 -0
  77. data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
  78. data/lib/rspec/core/version.rb +1 -1
  79. data/lib/rspec/core/warnings.rb +6 -6
  80. data/lib/rspec/core/world.rb +172 -68
  81. data/lib/rspec/core.rb +66 -21
  82. data.tar.gz.sig +0 -0
  83. metadata +93 -69
  84. metadata.gz.sig +0 -0
  85. data/lib/rspec/core/backport_random.rb +0 -336
@@ -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` (defaults to `:example`)
15
- # @overload before(scope, conditions, &block)
16
- # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to `:example`)
17
- # @param conditions [Hash]
18
- # constrains this hook to examples matching these conditions e.g.
19
- # `before(:example, :ui => true) { ... }` will only run with examples or
20
- # groups declared with `:ui => true`.
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 or
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 declared
36
- # directly in the {ExampleGroup} to which they apply, but they can also
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)` are
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 several
49
- # different places: `RSpec.configure`, a parent group, the current group.
50
- # They are run in the following order:
51
- #
52
- # before(:suite) # declared in RSpec.configure
53
- # before(:context) # declared in RSpec.configure
54
- # before(:context) # declared in a parent group
55
- # before(:context) # declared in the current group
56
- # before(:example) # declared in RSpec.configure
57
- # before(:example) # declared in a parent group
58
- # before(:example) # declared in the current group
59
- #
60
- # If more than one `before` is declared within any one scope, they are run
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 `before(:context)`,
66
- # RSpec will only apply that hook to groups or examples that match the
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
- # # the before hook will run in before each example in this group
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
- # # the before hook will run before this example
86
+ # # The before hook will run before this example.
82
87
  # end
83
88
  #
84
89
  # it "does something else" do
85
- # # the hook will not run before this example
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
- # #### context
128
+ # #### Context
117
129
  #
118
- # `before(:context)` is run in an example that is generated to provide group
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
- # #### instance variables
133
+ # #### Instance variables
122
134
  #
123
- # Instance variables declared in `before(:context)` are shared across all the
124
- # examples in the group. This means that each example can change the
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
- # #### unsupported rspec constructs
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 `before(:context)`:
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
- # This means that mocks and stubs can (sometimes) be declared in
143
- # `before(:context)`, but get torn down before the first real example is ever
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)` in
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
- # # here you can access @thing
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 `:example`)
202
- # @overload after(scope, conditions, &block)
203
- # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to `:example`)
204
- # @param conditions [Hash]
205
- # constrains this hook to examples matching these conditions e.g.
206
- # `after(:example, :ui => true) { ... }` will only run with examples or
207
- # groups declared with `:ui => true`.
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 or
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`) or
222
- # once after all examples n the context (using `:context`). See {#before} for
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. When an exception is raised in an after
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 several
236
- # different places: `RSpec.configure`, a parent group, the current group.
237
- # They are run in the following order:
238
- #
239
- # after(:example) # declared in the current group
240
- # after(:example) # declared in a parent group
241
- # after(:example) # declared in RSpec.configure
242
- # after(:context) # declared in the current group
243
- # after(:context) # declared in a parent group
244
- # after(:context) # declared in RSpec.configure
245
- # after(:suite) # declared in RSpec.configure
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 one scope,
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
- # constrains this hook to examples matching these conditions e.g.
279
- # `around(:example, :ui => true) { ... }` will only run with examples or
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
- # constrains this hook to examples matching these conditions e.g.
284
- # `around(:example, :ui => true) { ... }` will only run with examples or
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 of the examples with which they are associated,
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
- # # do some stuff before
327
+ # # Do some stuff before.
304
328
  # ex.run
305
- # # do some stuff after
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`. This is especially handy when working with libaries
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
- class Hook
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.instance_exec_with_rescue("in an after hook", &block)
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 Exception => e
365
- # TODO: come up with a better solution for this.
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 = Hook
378
-
379
- # @private
380
- class BaseHookCollection
381
- Array.public_instance_methods(false).each do |name|
382
- define_method(name) { |*a, &b| hooks.__send__(name, *a, &b) }
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
- def run
410
- hooks.each {|h| h.run(@example)}
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
- class AroundHookCollection < BaseHookCollection
416
- def for(example, initial_procsy=nil)
417
- self.class.new(hooks.select {|hook| hook.options_apply?(example)}).
418
- with(example, initial_procsy)
419
- end
420
-
421
- def with(example, initial_procsy)
422
- @example = example
423
- @initial_procsy = initial_procsy
424
- self
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 run
428
- hooks.inject(@initial_procsy) do |procsy, around_hook|
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
- # @private
437
- class GroupHookCollection < BaseHookCollection
438
- def for(group)
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
- def run
444
- hooks.shift.run(@group) until hooks.empty?
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
- # @private
449
- class HookCollections
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
- def [](key)
456
- @data[key]
445
+ process(example, parent_groups, globals, :before, :context) { {} }
446
+ process(example, parent_groups, globals, :after, :context) { {} }
457
447
  end
458
448
 
459
- def register_globals(host, globals)
460
- process(host, globals, :before, :example)
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
- def around_example_hooks_for(example, initial_procsy=nil)
469
- AroundHookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
470
- a.hooks[:around][:example]
471
- end).for(example, initial_procsy)
472
- end
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
- def register(prepend_or_append, hook, *args, &block)
475
- scope, options = scope_and_options_from(*args)
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(hook, scope, example_or_group, initial_procsy=nil)
475
+ def run(position, scope, example_or_group)
484
476
  return if RSpec.configuration.dry_run?
485
- find_hook(hook, scope, example_or_group, initial_procsy).run
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, :suite]
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 process(host, globals, position, scope)
503
- globals[position][scope].each do |hook|
504
- next unless scope == :example || hook.options_apply?(host)
505
- next if host.parent_groups.any? {|a| a.hooks[position][scope].include?(hook)}
506
- self[position][scope] << hook
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 (#{SCOPES.join(", ")}) or scope alias (#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as metadata for a hook."
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 find_hook(hook, scope, example_or_group, initial_procsy)
538
- case [hook, scope]
539
- when [:before, :context]
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 before_context_hooks_for(group)
555
- GroupHookCollection.new(self[:before][:context]).for(group)
556
- end
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
- def after_context_hooks_for(group)
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
- def before_example_hooks_for(example)
563
- HookCollection.new(FlatMap.flat_map(@owner.parent_groups.reverse) do |a|
564
- a.hooks[:before][:example]
565
- end).for(example)
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
- def after_example_hooks_for(example)
569
- HookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
570
- a.hooks[:after][:example]
571
- end).for(example)
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