rspec-core 3.1.7 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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