rspec-core 3.1.7 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +1 -0
  5. data/Changelog.md +84 -0
  6. data/README.md +10 -1
  7. data/lib/rspec/core.rb +28 -8
  8. data/lib/rspec/core/backport_random.rb +12 -9
  9. data/lib/rspec/core/configuration.rb +350 -112
  10. data/lib/rspec/core/configuration_options.rb +14 -7
  11. data/lib/rspec/core/dsl.rb +7 -4
  12. data/lib/rspec/core/example.rb +86 -50
  13. data/lib/rspec/core/example_group.rb +247 -86
  14. data/lib/rspec/core/filter_manager.rb +38 -93
  15. data/lib/rspec/core/flat_map.rb +4 -4
  16. data/lib/rspec/core/formatters.rb +10 -6
  17. data/lib/rspec/core/formatters/base_formatter.rb +7 -4
  18. data/lib/rspec/core/formatters/base_text_formatter.rb +12 -12
  19. data/lib/rspec/core/formatters/console_codes.rb +8 -7
  20. data/lib/rspec/core/formatters/deprecation_formatter.rb +5 -3
  21. data/lib/rspec/core/formatters/documentation_formatter.rb +10 -4
  22. data/lib/rspec/core/formatters/helpers.rb +6 -4
  23. data/lib/rspec/core/formatters/html_formatter.rb +13 -8
  24. data/lib/rspec/core/formatters/html_printer.rb +26 -10
  25. data/lib/rspec/core/formatters/profile_formatter.rb +10 -7
  26. data/lib/rspec/core/formatters/protocol.rb +27 -18
  27. data/lib/rspec/core/formatters/snippet_extractor.rb +14 -7
  28. data/lib/rspec/core/hooks.rb +252 -211
  29. data/lib/rspec/core/memoized_helpers.rb +16 -16
  30. data/lib/rspec/core/metadata.rb +67 -28
  31. data/lib/rspec/core/metadata_filter.rb +151 -24
  32. data/lib/rspec/core/minitest_assertions_adapter.rb +5 -2
  33. data/lib/rspec/core/mocking_adapters/flexmock.rb +1 -1
  34. data/lib/rspec/core/mocking_adapters/mocha.rb +8 -8
  35. data/lib/rspec/core/notifications.rb +155 -94
  36. data/lib/rspec/core/option_parser.rb +16 -10
  37. data/lib/rspec/core/pending.rb +11 -9
  38. data/lib/rspec/core/project_initializer.rb +1 -1
  39. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +10 -8
  40. data/lib/rspec/core/rake_task.rb +37 -52
  41. data/lib/rspec/core/reporter.rb +30 -7
  42. data/lib/rspec/core/ruby_project.rb +12 -4
  43. data/lib/rspec/core/runner.rb +5 -8
  44. data/lib/rspec/core/sandbox.rb +37 -0
  45. data/lib/rspec/core/shared_example_group.rb +41 -15
  46. data/lib/rspec/core/test_unit_assertions_adapter.rb +3 -3
  47. data/lib/rspec/core/version.rb +1 -1
  48. data/lib/rspec/core/warnings.rb +2 -2
  49. data/lib/rspec/core/world.rb +12 -28
  50. metadata +44 -31
  51. metadata.gz.sig +0 -0
@@ -1,7 +1,7 @@
1
1
  module RSpec
2
2
  module Core
3
3
  module Formatters
4
- # Formatters helpers
4
+ # Formatters helpers.
5
5
  module Helpers
6
6
  # @private
7
7
  SUB_SECOND_PRECISION = 5
@@ -39,8 +39,9 @@ module RSpec
39
39
 
40
40
  # @api private
41
41
  #
42
- # Formats seconds to have 5 digits of precision with trailing zeros removed if the number
43
- # is less than 1 or with 2 digits of precision if the number is greater than zero.
42
+ # Formats seconds to have 5 digits of precision with trailing zeros
43
+ # removed if the number is less than 1 or with 2 digits of precision if
44
+ # the number is greater than zero.
44
45
  #
45
46
  # @param float [Float]
46
47
  # @return [String] formatted float
@@ -50,7 +51,8 @@ module RSpec
50
51
  # format_seconds(0.020000) #=> "0.02"
51
52
  # format_seconds(1.00000000001) #=> "1"
52
53
  #
53
- # The precision used is set in {Helpers::SUB_SECOND_PRECISION} and {Helpers::DEFAULT_PRECISION}.
54
+ # The precision used is set in {Helpers::SUB_SECOND_PRECISION} and
55
+ # {Helpers::DEFAULT_PRECISION}.
54
56
  #
55
57
  # @see #strip_trailing_zeroes
56
58
  def self.format_seconds(float, precision=nil)
@@ -31,7 +31,9 @@ module RSpec
31
31
  @example_group_number += 1
32
32
 
33
33
  @printer.print_example_group_end unless example_group_number == 1
34
- @printer.print_example_group_start(example_group_number, notification.group.description, notification.group.parent_groups.size)
34
+ @printer.print_example_group_start(example_group_number,
35
+ notification.group.description,
36
+ notification.group.parent_groups.size)
35
37
  @printer.flush
36
38
  end
37
39
 
@@ -111,15 +113,16 @@ module RSpec
111
113
 
112
114
  private
113
115
 
114
- # If these methods are declared with attr_reader Ruby will issue a warning because they are private
116
+ # If these methods are declared with attr_reader Ruby will issue a
117
+ # warning because they are private.
115
118
  # rubocop:disable Style/TrivialAccessors
116
119
 
117
- # The number of the currently running example_group
120
+ # The number of the currently running example_group.
118
121
  def example_group_number
119
122
  @example_group_number
120
123
  end
121
124
 
122
- # The number of the currently running example (a global counter)
125
+ # The number of the currently running example (a global counter).
123
126
  def example_number
124
127
  @example_number
125
128
  end
@@ -133,12 +136,14 @@ module RSpec
133
136
  result
134
137
  end
135
138
 
136
- # Override this method if you wish to output extra HTML for a failed spec. For example, you
137
- # could output links to images or other files produced during the specs.
138
- #
139
+ # Override this method if you wish to output extra HTML for a failed
140
+ # spec. For example, you could output links to images or other files
141
+ # produced during the specs.
139
142
  def extra_failure_content(failure)
140
143
  RSpec::Support.require_rspec_core "formatters/snippet_extractor"
141
- backtrace = failure.exception.backtrace.map { |line| RSpec.configuration.backtrace_formatter.backtrace_line(line) }
144
+ backtrace = failure.exception.backtrace.map do |line|
145
+ RSpec.configuration.backtrace_formatter.backtrace_line(line)
146
+ end
142
147
  backtrace.compact!
143
148
  @snippet_extractor ||= SnippetExtractor.new
144
149
  " <pre class=\"ruby\"><code>#{@snippet_extractor.snippet(backtrace)}</code></pre>"
@@ -5,7 +5,7 @@ module RSpec
5
5
  module Formatters
6
6
  # @private
7
7
  class HtmlPrinter
8
- include ERB::Util # for the #h method
8
+ include ERB::Util # For the #h method.
9
9
  def initialize(output)
10
10
  @output = output
11
11
  end
@@ -28,11 +28,14 @@ module RSpec
28
28
 
29
29
  def print_example_passed(description, run_time)
30
30
  formatted_run_time = "%.5f" % run_time
31
- @output.puts " <dd class=\"example passed\"><span class=\"passed_spec_name\">#{h(description)}</span><span class='duration'>#{formatted_run_time}s</span></dd>"
31
+ @output.puts " <dd class=\"example passed\">" \
32
+ "<span class=\"passed_spec_name\">#{h(description)}</span>" \
33
+ "<span class='duration'>#{formatted_run_time}s</span></dd>"
32
34
  end
33
35
 
34
36
  # rubocop:disable Style/ParameterLists
35
- def print_example_failed(pending_fixed, description, run_time, failure_id, exception, extra_content, escape_backtrace=false)
37
+ def print_example_failed(pending_fixed, description, run_time, failure_id,
38
+ exception, extra_content, escape_backtrace=false)
36
39
  # rubocop:enable Style/ParameterLists
37
40
  formatted_run_time = "%.5f" % run_time
38
41
 
@@ -54,7 +57,9 @@ module RSpec
54
57
  end
55
58
 
56
59
  def print_example_pending(description, pending_message)
57
- @output.puts " <dd class=\"example not_implemented\"><span class=\"not_implemented_spec_name\">#{h(description)} (PENDING: #{h(pending_message)})</span></dd>"
60
+ @output.puts " <dd class=\"example not_implemented\">" \
61
+ "<span class=\"not_implemented_spec_name\">#{h(description)} " \
62
+ "(PENDING: #{h(pending_message)})</span></dd>"
58
63
  end
59
64
 
60
65
  def print_summary(duration, example_count, failure_count, pending_count)
@@ -64,8 +69,11 @@ module RSpec
64
69
 
65
70
  formatted_duration = "%.5f" % duration
66
71
 
67
- @output.puts "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{formatted_duration} seconds</strong>\";</script>"
68
- @output.puts "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{totals}\";</script>"
72
+ @output.puts "<script type=\"text/javascript\">" \
73
+ "document.getElementById('duration').innerHTML = \"Finished in " \
74
+ "<strong>#{formatted_duration} seconds</strong>\";</script>"
75
+ @output.puts "<script type=\"text/javascript\">" \
76
+ "document.getElementById('totals').innerHTML = \"#{totals}\";</script>"
69
77
  @output.puts "</div>"
70
78
  @output.puts "</div>"
71
79
  @output.puts "</body>"
@@ -90,13 +98,17 @@ module RSpec
90
98
  end
91
99
 
92
100
  def make_example_group_header_red(group_id)
93
- @output.puts " <script type=\"text/javascript\">makeRed('div_group_#{group_id}');</script>"
94
- @output.puts " <script type=\"text/javascript\">makeRed('example_group_#{group_id}');</script>"
101
+ @output.puts " <script type=\"text/javascript\">" \
102
+ "makeRed('div_group_#{group_id}');</script>"
103
+ @output.puts " <script type=\"text/javascript\">" \
104
+ "makeRed('example_group_#{group_id}');</script>"
95
105
  end
96
106
 
97
107
  def make_example_group_header_yellow(group_id)
98
- @output.puts " <script type=\"text/javascript\">makeYellow('div_group_#{group_id}');</script>"
99
- @output.puts " <script type=\"text/javascript\">makeYellow('example_group_#{group_id}');</script>"
108
+ @output.puts " <script type=\"text/javascript\">" \
109
+ "makeYellow('div_group_#{group_id}');</script>"
110
+ @output.puts " <script type=\"text/javascript\">" \
111
+ "makeYellow('example_group_#{group_id}');</script>"
100
112
  end
101
113
 
102
114
  private
@@ -105,6 +117,7 @@ module RSpec
105
117
  "style=\"margin-left: #{(number_of_parents - 1) * 15}px;\""
106
118
  end
107
119
 
120
+ # rubocop:disable LineLength
108
121
  REPORT_HEADER = <<-EOF
109
122
  <div class="rspec-report">
110
123
 
@@ -128,7 +141,9 @@ module RSpec
128
141
 
129
142
  <div class="results">
130
143
  EOF
144
+ # rubocop:enable LineLength
131
145
 
146
+ # rubocop:disable LineLength
132
147
  GLOBAL_SCRIPTS = <<-EOF
133
148
 
134
149
  function addClass(element_id, classname) {
@@ -205,6 +220,7 @@ function assign_display_style_for_group(classname, display_flag, subgroup_flag)
205
220
  }
206
221
  }
207
222
  EOF
223
+ # rubocop:enable LineLength
208
224
 
209
225
  GLOBAL_STYLES = <<-EOF
210
226
  #rspec-header {
@@ -4,7 +4,7 @@ module RSpec
4
4
  module Core
5
5
  module Formatters
6
6
  # @api private
7
- # Formatter for providing profile output
7
+ # Formatter for providing profile output.
8
8
  class ProfileFormatter
9
9
  Formatters.register self, :dump_profile
10
10
 
@@ -15,14 +15,13 @@ module RSpec
15
15
  # @private
16
16
  attr_reader :output
17
17
 
18
- # @method dump_profile
19
18
  # @api public
20
19
  #
21
20
  # This method is invoked after the dumping the summary if profiling is
22
21
  # enabled.
23
22
  #
24
- # @param profile [ProfileNotification] containing duration, slowest_examples
25
- # and slowest_example_groups
23
+ # @param profile [ProfileNotification] containing duration,
24
+ # slowest_examples and slowest_example_groups
26
25
  def dump_profile(profile)
27
26
  dump_profile_slowest_examples(profile)
28
27
  dump_profile_slowest_example_groups(profile)
@@ -31,11 +30,14 @@ module RSpec
31
30
  private
32
31
 
33
32
  def dump_profile_slowest_examples(profile)
34
- @output.puts "\nTop #{profile.slowest_examples.size} slowest examples (#{Helpers.format_seconds(profile.slow_duration)} seconds, #{profile.percentage}% of total time):\n"
33
+ @output.puts "\nTop #{profile.slowest_examples.size} slowest " \
34
+ "examples (#{Helpers.format_seconds(profile.slow_duration)} " \
35
+ "seconds, #{profile.percentage}% of total time):\n"
35
36
 
36
37
  profile.slowest_examples.each do |example|
37
38
  @output.puts " #{example.full_description}"
38
- @output.puts " #{bold(Helpers.format_seconds(example.execution_result.run_time))} #{bold("seconds")} #{format_caller(example.location)}"
39
+ @output.puts " #{bold(Helpers.format_seconds(example.execution_result.run_time))} " \
40
+ "#{bold("seconds")} #{format_caller(example.location)}"
39
41
  end
40
42
  end
41
43
 
@@ -53,7 +55,8 @@ module RSpec
53
55
  end
54
56
 
55
57
  def format_caller(caller_info)
56
- RSpec.configuration.backtrace_formatter.backtrace_line(caller_info.to_s.split(':in `block').first)
58
+ RSpec.configuration.backtrace_formatter.backtrace_line(
59
+ caller_info.to_s.split(':in `block').first)
57
60
  end
58
61
 
59
62
  def bold(text)
@@ -39,12 +39,14 @@ module RSpec
39
39
  # @api public
40
40
  # @group Group Notifications
41
41
  #
42
- # This method is invoked at the beginning of the execution of each example group.
42
+ # This method is invoked at the beginning of the execution of each
43
+ # example group.
43
44
  #
44
45
  # The next method to be invoked after this is {#example_passed},
45
46
  # {#example_pending}, or {#example_group_finished}.
46
47
  #
47
- # @param notification [GroupNotification] containing example_group subclass of `RSpec::Core::ExampleGroup`
48
+ # @param notification [GroupNotification] containing example_group
49
+ # subclass of `RSpec::Core::ExampleGroup`
48
50
 
49
51
  # @method example_group_finished
50
52
  # @api public
@@ -52,7 +54,8 @@ module RSpec
52
54
  #
53
55
  # Invoked at the end of the execution of each example group.
54
56
  #
55
- # @param notification [GroupNotification] containing example_group subclass of `RSpec::Core::ExampleGroup`
57
+ # @param notification [GroupNotification] containing example_group
58
+ # subclass of `RSpec::Core::ExampleGroup`
56
59
 
57
60
  # @method example_started
58
61
  # @api public
@@ -60,7 +63,8 @@ module RSpec
60
63
  #
61
64
  # Invoked at the beginning of the execution of each example.
62
65
  #
63
- # @param notification [ExampleNotification] containing example subclass of `RSpec::Core::Example`
66
+ # @param notification [ExampleNotification] containing example subclass
67
+ # of `RSpec::Core::Example`
64
68
 
65
69
  # @method example_passed
66
70
  # @api public
@@ -68,7 +72,8 @@ module RSpec
68
72
  #
69
73
  # Invoked when an example passes.
70
74
  #
71
- # @param notification [ExampleNotification] containing example subclass of `RSpec::Core::Example`
75
+ # @param notification [ExampleNotification] containing example subclass
76
+ # of `RSpec::Core::Example`
72
77
 
73
78
  # @method example_pending
74
79
  # @api public
@@ -76,7 +81,8 @@ module RSpec
76
81
  #
77
82
  # Invoked when an example is pending.
78
83
  #
79
- # @param notification [ExampleNotification] containing example subclass of `RSpec::Core::Example`
84
+ # @param notification [ExampleNotification] containing example subclass
85
+ # of `RSpec::Core::Example`
80
86
 
81
87
  # @method example_failed
82
88
  # @api public
@@ -84,7 +90,8 @@ module RSpec
84
90
  #
85
91
  # Invoked when an example fails.
86
92
  #
87
- # @param notification [ExampleNotification] containing example subclass of `RSpec::Core::Example`
93
+ # @param notification [ExampleNotification] containing example subclass
94
+ # of `RSpec::Core::Example`
88
95
 
89
96
  # @method message
90
97
  # @api public
@@ -98,7 +105,8 @@ module RSpec
98
105
  # @api public
99
106
  # @group Suite Notifications
100
107
  #
101
- # Invoked after all examples have executed, before dumping post-run reports.
108
+ # Invoked after all examples have executed, before dumping post-run
109
+ # reports.
102
110
  #
103
111
  # @param notification [NullNotification]
104
112
 
@@ -106,9 +114,10 @@ module RSpec
106
114
  # @api public
107
115
  # @group Suite Notifications
108
116
  #
109
- # This method is invoked after all of the examples have executed. The next method
110
- # to be invoked after this one is {#dump_failures}
111
- # (BaseTextFormatter then calls {#dump_failure} once for each failed example.)
117
+ # This method is invoked after all of the examples have executed. The
118
+ # next method to be invoked after this one is {#dump_failures}
119
+ # (BaseTextFormatter then calls {#dump_failures} once for each failed
120
+ # example).
112
121
  #
113
122
  # @param notification [NullNotification]
114
123
 
@@ -124,11 +133,11 @@ module RSpec
124
133
  # @api public
125
134
  # @group Suite Notifications
126
135
  #
127
- # This method is invoked after the dumping of examples and failures. Each parameter
128
- # is assigned to a corresponding attribute.
136
+ # This method is invoked after the dumping of examples and failures.
137
+ # Each parameter is assigned to a corresponding attribute.
129
138
  #
130
- # @param summary [SummaryNotification] containing duration, example_count,
131
- # failure_count and pending_count
139
+ # @param summary [SummaryNotification] containing duration,
140
+ # example_count, failure_count and pending_count
132
141
 
133
142
  # @method dump_profile
134
143
  # @api public
@@ -137,14 +146,14 @@ module RSpec
137
146
  # This method is invoked after the dumping the summary if profiling is
138
147
  # enabled.
139
148
  #
140
- # @param profile [ProfileNotification] containing duration, slowest_examples
141
- # and slowest_example_groups
149
+ # @param profile [ProfileNotification] containing duration,
150
+ # slowest_examples and slowest_example_groups
142
151
 
143
152
  # @method dump_pending
144
153
  # @api public
145
154
  # @group Suite Notifications
146
155
  #
147
- # Outputs a report of pending examples. This gets invoked
156
+ # Outputs a report of pending examples. This gets invoked
148
157
  # after the summary if option is set to do so.
149
158
  #
150
159
  # @param notification [NullNotification]
@@ -3,7 +3,8 @@ module RSpec
3
3
  module Formatters
4
4
  # @api private
5
5
  #
6
- # Extracts code snippets by looking at the backtrace of the passed error and applies synax highlighting and line numbers using html.
6
+ # Extracts code snippets by looking at the backtrace of the passed error
7
+ # and applies synax highlighting and line numbers using html.
7
8
  class SnippetExtractor
8
9
  # @private
9
10
  class NullConverter
@@ -33,7 +34,8 @@ module RSpec
33
34
  # Extract lines of code corresponding to a backtrace.
34
35
  #
35
36
  # @param backtrace [String] the backtrace from a test failure
36
- # @return [String] highlighted code snippet indicating where the test failure occured
37
+ # @return [String] highlighted code snippet indicating where the test
38
+ # failure occured
37
39
  #
38
40
  # @see #post_process
39
41
  def snippet(backtrace)
@@ -46,7 +48,8 @@ module RSpec
46
48
  #
47
49
  # Create a snippet from a line of code.
48
50
  #
49
- # @param error_line [String] file name with line number (i.e. 'foo_spec.rb:12')
51
+ # @param error_line [String] file name with line number (i.e.
52
+ # 'foo_spec.rb:12')
50
53
  # @return [String] lines around the target line within the file
51
54
  #
52
55
  # @see #lines_around
@@ -62,11 +65,13 @@ module RSpec
62
65
 
63
66
  # @api private
64
67
  #
65
- # Extract lines of code centered around a particular line within a source file.
68
+ # Extract lines of code centered around a particular line within a
69
+ # source file.
66
70
  #
67
71
  # @param file [String] filename
68
72
  # @param line [Fixnum] line number
69
- # @return [String] lines around the target line within the file (2 above and 1 below).
73
+ # @return [String] lines around the target line within the file (2 above
74
+ # and 1 below).
70
75
  def lines_around(file, line)
71
76
  if File.file?(file)
72
77
  lines = File.read(file).split("\n")
@@ -84,9 +89,11 @@ module RSpec
84
89
 
85
90
  # @api private
86
91
  #
87
- # Adds line numbers to all lines and highlights the line where the failure occurred using html `span` tags.
92
+ # Adds line numbers to all lines and highlights the line where the
93
+ # failure occurred using html `span` tags.
88
94
  #
89
- # @param highlighted [String] syntax-highlighted snippet surrounding the offending line of code
95
+ # @param highlighted [String] syntax-highlighted snippet surrounding the
96
+ # offending line of code
90
97
  # @param offending_line [Fixnum] line where failure occured
91
98
  # @return [String] completed snippet
92
99
  def post_process(highlighted, offending_line)
@@ -11,18 +11,20 @@ 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`)
14
+ # @param scope [Symbol] `:example`, `:context`, or `:suite`
15
+ # (defaults to `:example`)
15
16
  # @overload before(scope, conditions, &block)
16
- # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to `:example`)
17
+ # @param scope [Symbol] `:example`, `:context`, or `:suite`
18
+ # (defaults to `:example`)
17
19
  # @param conditions [Hash]
18
20
  # 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`.
21
+ # `before(:example, :ui => true) { ... }` will only run with examples
22
+ # or groups declared with `:ui => true`.
21
23
  # @overload before(conditions, &block)
22
24
  # @param conditions [Hash]
23
25
  # 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`.
26
+ # `before(:example, :ui => true) { ... }` will only run with examples
27
+ # or groups declared with `:ui => true`.
26
28
  #
27
29
  # @see #after
28
30
  # @see #around
@@ -32,39 +34,39 @@ module RSpec
32
34
  # @see Configuration
33
35
  #
34
36
  # 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.
37
+ # or once before any example (using `:context`). These are usually
38
+ # declared directly in the {ExampleGroup} to which they apply, but they
39
+ # can also be shared across multiple groups.
38
40
  #
39
41
  # 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}
42
+ # example groups are run. This should be declared in {RSpec.configure}.
41
43
  #
42
- # Instance variables declared in `before(:example)` or `before(:context)` are
43
- # accessible within each example.
44
+ # Instance variables declared in `before(:example)` or `before(:context)`
45
+ # are accessible within each example.
44
46
  #
45
47
  # ### Order
46
48
  #
47
49
  # `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
50
+ # `:suite`, `:context`, and `:example`. They can also be declared in
51
+ # several different places: `RSpec.configure`, a parent group, the current
52
+ # group. They are run in the following order:
53
+ #
54
+ # before(:suite) # Declared in RSpec.configure.
55
+ # before(:context) # Declared in RSpec.configure.
56
+ # before(:context) # Declared in a parent group.
57
+ # before(:context) # Declared in the current group.
58
+ # before(:example) # Declared in RSpec.configure.
59
+ # before(:example) # Declared in a parent group.
60
+ # before(:example) # Declared in the current group.
59
61
  #
60
62
  # If more than one `before` is declared within any one scope, they are run
61
63
  # in the order in which they are declared.
62
64
  #
63
65
  # ### Conditions
64
66
  #
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.
67
+ # When you add a conditions hash to `before(:example)` or
68
+ # `before(:context)`, RSpec will only apply that hook to groups or
69
+ # examples that match the conditions. e.g.
68
70
  #
69
71
  # RSpec.configure do |config|
70
72
  # config.before(:example, :authorized => true) do
@@ -73,19 +75,26 @@ module RSpec
73
75
  # end
74
76
  #
75
77
  # describe Something, :authorized => true do
76
- # # the before hook will run in before each example in this group
78
+ # # The before hook will run in before each example in this group.
77
79
  # end
78
80
  #
79
81
  # describe SomethingElse do
80
82
  # it "does something", :authorized => true do
81
- # # the before hook will run before this example
83
+ # # The before hook will run before this example.
82
84
  # end
83
85
  #
84
86
  # it "does something else" do
85
- # # the hook will not run before this example
87
+ # # The hook will not run before this example.
86
88
  # end
87
89
  # end
88
90
  #
91
+ # Note that filtered config `:context` hooks can still be applied
92
+ # to individual examples that have matching metadata. Just like
93
+ # Ruby's object model is that every object has a singleton class
94
+ # which has only a single instance, RSpec's model is that every
95
+ # example has a singleton example group containing just the one
96
+ # example.
97
+ #
89
98
  # ### Warning: `before(:suite, :with => :conditions)`
90
99
  #
91
100
  # The conditions hash is used to match against specific examples. Since
@@ -113,22 +122,23 @@ module RSpec
113
122
  # recommend that you avoid this as there are a number of gotchas, as well
114
123
  # as things that simply don't work.
115
124
  #
116
- # #### context
125
+ # #### Context
117
126
  #
118
- # `before(:context)` is run in an example that is generated to provide group
119
- # context for the block.
127
+ # `before(:context)` is run in an example that is generated to provide
128
+ # group context for the block.
120
129
  #
121
- # #### instance variables
130
+ # #### Instance variables
122
131
  #
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
132
+ # Instance variables declared in `before(:context)` are shared across all
133
+ # the examples in the group. This means that each example can change the
125
134
  # state of a shared object, resulting in an ordering dependency that can
126
135
  # make it difficult to reason about failures.
127
136
  #
128
- # #### unsupported rspec constructs
137
+ # #### Unsupported RSpec constructs
129
138
  #
130
139
  # RSpec has several constructs that reset state between each example
131
- # automatically. These are not intended for use from within `before(:context)`:
140
+ # automatically. These are not intended for use from within
141
+ # `before(:context)`:
132
142
  #
133
143
  # * `let` declarations
134
144
  # * `subject` declarations
@@ -138,13 +148,13 @@ module RSpec
138
148
  #
139
149
  # Mock object frameworks and database transaction managers (like
140
150
  # 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.
151
+ # before an example, running that one example, and then tearing down. This
152
+ # means that mocks and stubs can (sometimes) be declared in
153
+ # `before(:context)`, but get torn down before the first real example is
154
+ # ever run.
145
155
  #
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
156
+ # You _can_ create database-backed model objects in a `before(:context)`
157
+ # in rspec-rails, but it will not be wrapped in a transaction for you, so
148
158
  # you are on your own to clean up in an `after(:context)` block.
149
159
  #
150
160
  # @example before(:example) declared in an {ExampleGroup}
@@ -155,7 +165,7 @@ module RSpec
155
165
  # end
156
166
  #
157
167
  # it "does something" do
158
- # # here you can access @thing
168
+ # # Here you can access @thing.
159
169
  # end
160
170
  # end
161
171
  #
@@ -181,6 +191,9 @@ module RSpec
181
191
  #
182
192
  # @note The `:example` and `:context` scopes are also available as
183
193
  # `:each` and `:all`, respectively. Use whichever you prefer.
194
+ # @note The `:scope` alias is only supported for hooks registered on
195
+ # `RSpec.configuration` since they exist independently of any
196
+ # example or example group.
184
197
  def before(*args, &block)
185
198
  hooks.register :append, :before, *args, &block
186
199
  end
@@ -198,18 +211,20 @@ module RSpec
198
211
  # @api public
199
212
  # @overload after(&block)
200
213
  # @overload after(scope, &block)
201
- # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to `:example`)
214
+ # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
215
+ # `:example`)
202
216
  # @overload after(scope, conditions, &block)
203
- # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to `:example`)
217
+ # @param scope [Symbol] `:example`, `:context`, or `:suite` (defaults to
218
+ # `:example`)
204
219
  # @param conditions [Hash]
205
220
  # 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`.
221
+ # `after(:example, :ui => true) { ... }` will only run with examples
222
+ # or groups declared with `:ui => true`.
208
223
  # @overload after(conditions, &block)
209
224
  # @param conditions [Hash]
210
225
  # 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`.
226
+ # `after(:example, :ui => true) { ... }` will only run with examples
227
+ # or groups declared with `:ui => true`.
213
228
  #
214
229
  # @see #before
215
230
  # @see #around
@@ -218,31 +233,31 @@ module RSpec
218
233
  # @see SharedExampleGroup
219
234
  # @see Configuration
220
235
  #
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.
236
+ # Declare a block of code to be run after each example (using `:example`)
237
+ # or once after all examples n the context (using `:context`). See
238
+ # {#before} for more information about ordering.
224
239
  #
225
240
  # ### Exceptions
226
241
  #
227
242
  # `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
243
+ # `before` hooks or examples. When an exception is raised in an after
229
244
  # block, the exception is captured for later reporting, and subsequent
230
245
  # `after` blocks are run.
231
246
  #
232
247
  # ### Order
233
248
  #
234
249
  # `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
250
+ # `:example`, `:context`, and `:suite`. They can also be declared in
251
+ # several different places: `RSpec.configure`, a parent group, the current
252
+ # group. They are run in the following order:
253
+ #
254
+ # after(:example) # Declared in the current group.
255
+ # after(:example) # Declared in a parent group.
256
+ # after(:example) # Declared in RSpec.configure.
257
+ # after(:context) # Declared in the current group.
258
+ # after(:context) # Declared in a parent group.
259
+ # after(:context) # Declared in RSpec.configure.
260
+ # after(:suite) # Declared in RSpec.configure.
246
261
  #
247
262
  # This is the reverse of the order in which `before` hooks are run.
248
263
  # Similarly, if more than one `after` is declared within any one scope,
@@ -250,6 +265,9 @@ module RSpec
250
265
  #
251
266
  # @note The `:example` and `:context` scopes are also available as
252
267
  # `:each` and `:all`, respectively. Use whichever you prefer.
268
+ # @note The `:scope` alias is only supported for hooks registered on
269
+ # `RSpec.configuration` since they exist independently of any
270
+ # example or example group.
253
271
  def after(*args, &block)
254
272
  hooks.register :prepend, :after, *args, &block
255
273
  end
@@ -274,15 +292,13 @@ module RSpec
274
292
  # @param scope [Symbol] `:example` (defaults to `:example`)
275
293
  # present for syntax parity with `before` and `after`, but
276
294
  # `: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`.
295
+ # @param conditions [Hash] constrains this hook to examples matching
296
+ # these conditions e.g. `around(:example, :ui => true) { ... }` will
297
+ # only run with examples or groups declared with `:ui => true`.
281
298
  # @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`.
299
+ # @param conditions [Hash] constrains this hook to examples matching
300
+ # these conditions e.g. `around(:example, :ui => true) { ... }` will
301
+ # only run with examples or groups declared with `:ui => true`.
286
302
  #
287
303
  # @yield [Example] the example to run
288
304
  #
@@ -300,13 +316,13 @@ module RSpec
300
316
  # after the example. It is your responsibility to run the example:
301
317
  #
302
318
  # around(:example) do |ex|
303
- # # do some stuff before
319
+ # # Do some stuff before.
304
320
  # ex.run
305
- # # do some stuff after
321
+ # # Do some stuff after.
306
322
  # end
307
323
  #
308
324
  # The yielded example aliases `run` with `call`, which lets you treat it
309
- # like a `Proc`. This is especially handy when working with libaries
325
+ # like a `Proc`. This is especially handy when working with libaries
310
326
  # that manage their own setup and teardown using a block or proc syntax,
311
327
  # e.g.
312
328
  #
@@ -320,12 +336,7 @@ module RSpec
320
336
  # @private
321
337
  # Holds the various registered hooks.
322
338
  def hooks
323
- @hooks ||= HookCollections.new(
324
- self,
325
- :around => { :example => AroundHookCollection.new },
326
- :before => { :example => HookCollection.new, :context => HookCollection.new, :suite => HookCollection.new },
327
- :after => { :example => HookCollection.new, :context => HookCollection.new, :suite => HookCollection.new }
328
- )
339
+ @hooks ||= HookCollections.new(self, FilterableItemRepository::UpdateOptimized)
329
340
  end
330
341
 
331
342
  private
@@ -338,10 +349,6 @@ module RSpec
338
349
  @block = block
339
350
  @options = options
340
351
  end
341
-
342
- def options_apply?(example_or_group)
343
- example_or_group.all_apply?(options)
344
- end
345
352
  end
346
353
 
347
354
  # @private
@@ -363,7 +370,7 @@ module RSpec
363
370
  def run(example)
364
371
  example.instance_exec(example, &block)
365
372
  rescue Exception => e
366
- # TODO: come up with a better solution for this.
373
+ # TODO: Come up with a better solution for this.
367
374
  RSpec.configuration.reporter.message <<-EOS
368
375
 
369
376
  An error occurred in an `after(:context)` hook.
@@ -379,7 +386,8 @@ EOS
379
386
  def execute_with(example, procsy)
380
387
  example.instance_exec(procsy, &block)
381
388
  return if procsy.executed?
382
- Pending.mark_skipped!(example, "#{hook_description} did not execute the example")
389
+ Pending.mark_skipped!(example,
390
+ "#{hook_description} did not execute the example")
383
391
  end
384
392
 
385
393
  if Proc.method_defined?(:source_location)
@@ -394,113 +402,81 @@ EOS
394
402
  end
395
403
 
396
404
  # @private
397
- class BaseHookCollection
398
- Array.public_instance_methods(false).each do |name|
399
- define_method(name) { |*a, &b| hooks.__send__(name, *a, &b) }
400
- end
401
-
402
- attr_reader :hooks
403
- protected :hooks
404
-
405
- alias append push
406
- alias prepend unshift
407
-
408
- def initialize(hooks=[])
409
- @hooks = hooks
410
- end
411
- end
412
-
413
- # @private
414
- class HookCollection < BaseHookCollection
415
- def for(example_or_group)
416
- self.class.
417
- new(hooks.select { |hook| hook.options_apply?(example_or_group) }).
418
- with(example_or_group)
419
- end
420
-
421
- def with(example)
422
- @example = example
423
- self
424
- end
425
-
426
- def run
427
- hooks.each { |h| h.run(@example) }
428
- end
429
- end
430
-
431
- # @private
432
- class AroundHookCollection < BaseHookCollection
433
- def for(example, initial_procsy=nil)
434
- self.class.new(hooks.select { |hook| hook.options_apply?(example) }).
435
- with(example, initial_procsy)
436
- end
437
-
438
- def with(example, initial_procsy)
439
- @example = example
440
- @initial_procsy = initial_procsy
441
- self
405
+ #
406
+ # This provides the primary API used by other parts of rspec-core. By hiding all
407
+ # implementation details behind this facade, it's allowed us to heavily optimize
408
+ # this, so that, for example, hook collection objects are only instantiated when
409
+ # a hook is added. This allows us to avoid many object allocations for the common
410
+ # case of a group having no hooks.
411
+ #
412
+ # This is only possible because this interface provides a "tell, don't ask"-style
413
+ # API, so that callers _tell_ this class what to do with the hooks, rather than
414
+ # asking this class for a list of hooks, and then doing something with them.
415
+ class HookCollections
416
+ def initialize(owner, filterable_item_repo_class)
417
+ @owner = owner
418
+ @filterable_item_repo_class = filterable_item_repo_class
419
+ @before_example_hooks = nil
420
+ @after_example_hooks = nil
421
+ @before_context_hooks = nil
422
+ @after_context_hooks = nil
423
+ @around_example_hooks = nil
442
424
  end
443
425
 
444
- def run
445
- hooks.inject(@initial_procsy) do |procsy, around_hook|
446
- procsy.wrap { around_hook.execute_with(@example, procsy) }
447
- end.call
448
- end
449
- end
426
+ def register_globals(host, globals)
427
+ parent_groups = host.parent_groups
450
428
 
451
- # @private
452
- class GroupHookCollection < BaseHookCollection
453
- def for(group)
454
- @group = group
455
- self
456
- end
429
+ process(host, parent_groups, globals, :before, :example, &:options)
430
+ process(host, parent_groups, globals, :after, :example, &:options)
431
+ process(host, parent_groups, globals, :around, :example, &:options)
457
432
 
458
- def run
459
- hooks.shift.run(@group) until hooks.empty?
433
+ process(host, parent_groups, globals, :before, :context, &:options)
434
+ process(host, parent_groups, globals, :after, :context, &:options)
460
435
  end
461
- end
462
436
 
463
- # @private
464
- class HookCollections
465
- def initialize(owner, data)
466
- @owner = owner
467
- @data = data
468
- end
437
+ def register_global_singleton_context_hooks(example, globals)
438
+ parent_groups = example.example_group.parent_groups
469
439
 
470
- def [](key)
471
- @data[key]
440
+ process(example, parent_groups, globals, :before, :context) { {} }
441
+ process(example, parent_groups, globals, :after, :context) { {} }
472
442
  end
473
443
 
474
- def register_globals(host, globals)
475
- process(host, globals, :before, :example)
476
- process(host, globals, :after, :example)
477
- process(host, globals, :around, :example)
478
-
479
- process(host, globals, :before, :context)
480
- process(host, globals, :after, :context)
481
- end
444
+ def register(prepend_or_append, position, *args, &block)
445
+ scope, options = scope_and_options_from(*args)
482
446
 
483
- def around_example_hooks_for(example, initial_procsy=nil)
484
- AroundHookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
485
- a.hooks[:around][:example]
486
- end).for(example, initial_procsy)
487
- end
447
+ if scope == :suite
448
+ # TODO: consider making this an error in RSpec 4. For SemVer reasons,
449
+ # we are only warning in RSpec 3.
450
+ RSpec.warn_with "WARNING: `#{position}(:suite)` hooks are only supported on " \
451
+ "the RSpec configuration object. This " \
452
+ "`#{position}(:suite)` hook, registered on an example " \
453
+ "group, will be ignored."
454
+ return
455
+ end
488
456
 
489
- def register(prepend_or_append, hook, *args, &block)
490
- scope, options = scope_and_options_from(*args)
491
- self[hook][scope].__send__(prepend_or_append, HOOK_TYPES[hook][scope].new(block, options))
457
+ hook = HOOK_TYPES[position][scope].new(block, options)
458
+ ensure_hooks_initialized_for(position, scope).__send__(prepend_or_append, hook, options)
492
459
  end
493
460
 
494
461
  # @private
495
462
  #
496
463
  # Runs all of the blocks stored with the hook in the context of the
497
464
  # example. If no example is provided, just calls the hook directly.
498
- def run(hook, scope, example_or_group, initial_procsy=nil)
465
+ def run(position, scope, example_or_group)
499
466
  return if RSpec.configuration.dry_run?
500
- find_hook(hook, scope, example_or_group, initial_procsy).run
467
+
468
+ if scope == :context
469
+ run_owned_hooks_for(position, :context, example_or_group)
470
+ else
471
+ case position
472
+ when :before then run_example_hooks_for(example_or_group, :before, :reverse_each)
473
+ when :after then run_example_hooks_for(example_or_group, :after, :each)
474
+ when :around then run_around_example_hooks_for(example_or_group) { yield }
475
+ end
476
+ end
501
477
  end
502
478
 
503
- SCOPES = [:example, :context, :suite]
479
+ SCOPES = [:example, :context]
504
480
 
505
481
  SCOPE_ALIASES = { :each => :example, :all => :context }
506
482
 
@@ -512,17 +488,89 @@ EOS
512
488
 
513
489
  HOOK_TYPES[:after][:context] = AfterContextHook
514
490
 
491
+ protected
492
+
493
+ EMPTY_HOOK_ARRAY = [].freeze
494
+
495
+ def matching_hooks_for(position, scope, example_or_group)
496
+ repository = hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }
497
+
498
+ # It would be nice to not have to switch on type here, but
499
+ # we don't want to define `ExampleGroup#metadata` because then
500
+ # `metadata` from within an individual example would return the
501
+ # group's metadata but the user would probably expect it to be
502
+ # the example's metadata.
503
+ metadata = case example_or_group
504
+ when ExampleGroup then example_or_group.class.metadata
505
+ else example_or_group.metadata
506
+ end
507
+
508
+ repository.items_for(metadata)
509
+ end
510
+
511
+ def all_hooks_for(position, scope)
512
+ hooks_for(position, scope) { return EMPTY_HOOK_ARRAY }.items_and_filters.map(&:first)
513
+ end
514
+
515
+ def run_owned_hooks_for(position, scope, example_or_group)
516
+ matching_hooks_for(position, scope, example_or_group).each do |hook|
517
+ hook.run(example_or_group)
518
+ end
519
+ end
520
+
521
+ def processable_hooks_for(position, scope, host)
522
+ if scope == :example
523
+ all_hooks_for(position, scope)
524
+ else
525
+ matching_hooks_for(position, scope, host)
526
+ end
527
+ end
528
+
515
529
  private
516
530
 
517
- def process(host, globals, position, scope)
518
- globals[position][scope].each do |hook|
519
- next unless scope == :example || hook.options_apply?(host)
520
- next if host.parent_groups.any? { |a| a.hooks[position][scope].include?(hook) }
521
- self[position][scope] << hook
531
+ def hooks_for(position, scope)
532
+ if position == :before
533
+ scope == :example ? @before_example_hooks : @before_context_hooks
534
+ elsif position == :after
535
+ scope == :example ? @after_example_hooks : @after_context_hooks
536
+ else # around
537
+ @around_example_hooks
538
+ end || yield
539
+ end
540
+
541
+ def ensure_hooks_initialized_for(position, scope)
542
+ if position == :before
543
+ if scope == :example
544
+ @before_example_hooks ||= @filterable_item_repo_class.new(:all?)
545
+ else
546
+ @before_context_hooks ||= @filterable_item_repo_class.new(:all?)
547
+ end
548
+ elsif position == :after
549
+ if scope == :example
550
+ @after_example_hooks ||= @filterable_item_repo_class.new(:all?)
551
+ else
552
+ @after_context_hooks ||= @filterable_item_repo_class.new(:all?)
553
+ end
554
+ else # around
555
+ @around_example_hooks ||= @filterable_item_repo_class.new(:all?)
556
+ end
557
+ end
558
+
559
+ def process(host, parent_groups, globals, position, scope)
560
+ hooks_to_process = globals.processable_hooks_for(position, scope, host)
561
+ return if hooks_to_process.empty?
562
+
563
+ hooks_to_process -= FlatMap.flat_map(parent_groups) do |group|
564
+ group.hooks.all_hooks_for(position, scope)
522
565
  end
566
+ return if hooks_to_process.empty?
567
+
568
+ repository = ensure_hooks_initialized_for(position, scope)
569
+ hooks_to_process.each { |hook| repository.append hook, (yield hook) }
523
570
  end
524
571
 
525
572
  def scope_and_options_from(*args)
573
+ return :suite if args.first == :suite
526
574
  scope = extract_scope_from(args)
527
575
  meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering)
528
576
  return scope, meta
@@ -532,58 +580,51 @@ EOS
532
580
  if known_scope?(args.first)
533
581
  normalized_scope_for(args.shift)
534
582
  elsif args.any? { |a| a.is_a?(Symbol) }
535
- 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."
583
+ error_message = "You must explicitly give a scope " \
584
+ "(#{SCOPES.join(", ")}) or scope alias " \
585
+ "(#{SCOPE_ALIASES.keys.join(", ")}) when using symbols as " \
586
+ "metadata for a hook."
536
587
  raise ArgumentError.new error_message
537
588
  else
538
589
  :example
539
590
  end
540
591
  end
541
592
 
542
- # @api private
543
593
  def known_scope?(scope)
544
594
  SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope)
545
595
  end
546
596
 
547
- # @api private
548
597
  def normalized_scope_for(scope)
549
598
  SCOPE_ALIASES[scope] || scope
550
599
  end
551
600
 
552
- def find_hook(hook, scope, example_or_group, initial_procsy)
553
- case [hook, scope]
554
- when [:before, :context]
555
- before_context_hooks_for(example_or_group)
556
- when [:after, :context]
557
- after_context_hooks_for(example_or_group)
558
- when [:around, :example]
559
- around_example_hooks_for(example_or_group, initial_procsy)
560
- when [:before, :example]
561
- before_example_hooks_for(example_or_group)
562
- when [:after, :example]
563
- after_example_hooks_for(example_or_group)
564
- when [:before, :suite], [:after, :suite]
565
- self[hook][:suite].with(example_or_group)
601
+ def run_example_hooks_for(example, position, each_method)
602
+ owner_parent_groups.__send__(each_method) do |group|
603
+ group.hooks.run_owned_hooks_for(position, :example, example)
566
604
  end
567
605
  end
568
606
 
569
- def before_context_hooks_for(group)
570
- GroupHookCollection.new(self[:before][:context]).for(group)
571
- end
607
+ def run_around_example_hooks_for(example)
608
+ hooks = FlatMap.flat_map(owner_parent_groups) do |group|
609
+ group.hooks.matching_hooks_for(:around, :example, example)
610
+ end
572
611
 
573
- def after_context_hooks_for(group)
574
- GroupHookCollection.new(self[:after][:context]).for(group)
575
- end
612
+ return yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy`
576
613
 
577
- def before_example_hooks_for(example)
578
- HookCollection.new(FlatMap.flat_map(@owner.parent_groups.reverse) do |a|
579
- a.hooks[:before][:example]
580
- end).for(example)
614
+ initial_procsy = Example::Procsy.new(example) { yield }
615
+ hooks.inject(initial_procsy) do |procsy, around_hook|
616
+ procsy.wrap { around_hook.execute_with(example, procsy) }
617
+ end.call
581
618
  end
582
619
 
583
- def after_example_hooks_for(example)
584
- HookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
585
- a.hooks[:after][:example]
586
- end).for(example)
620
+ if respond_to?(:singleton_class) && singleton_class.ancestors.include?(singleton_class)
621
+ def owner_parent_groups
622
+ @owner.parent_groups
623
+ end
624
+ else # Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035)
625
+ def owner_parent_groups
626
+ @owner_parent_groups ||= [@owner] + @owner.parent_groups
627
+ end
587
628
  end
588
629
  end
589
630
  end