rspec-core 3.0.0.beta1 → 3.0.0.beta2

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 (145) hide show
  1. data.tar.gz.sig +0 -0
  2. data/Changelog.md +137 -0
  3. data/README.md +2 -2
  4. data/exe/rspec +2 -23
  5. data/features/README.md +1 -5
  6. data/features/command_line/README.md +7 -10
  7. data/features/command_line/exit_status.feature +1 -1
  8. data/features/command_line/format_option.feature +1 -1
  9. data/features/command_line/init.feature +40 -1
  10. data/features/command_line/line_number_option.feature +2 -2
  11. data/features/command_line/ruby.feature +5 -4
  12. data/features/configuration/enable_global_dsl.feature +54 -0
  13. data/features/example_groups/aliasing.feature +48 -0
  14. data/features/example_groups/basic_structure.feature +1 -1
  15. data/features/expectation_framework_integration/configure_expectation_framework.feature +1 -1
  16. data/features/filtering/if_and_unless.feature +0 -30
  17. data/features/formatters/custom_formatter.feature +32 -0
  18. data/features/formatters/regression_tests.feature +95 -0
  19. data/features/hooks/around_hooks.feature +1 -0
  20. data/features/hooks/before_and_after_hooks.feature +2 -2
  21. data/features/mock_framework_integration/use_flexmock.feature +11 -13
  22. data/features/mock_framework_integration/use_mocha.feature +11 -13
  23. data/features/mock_framework_integration/use_rr.feature +11 -13
  24. data/features/mock_framework_integration/use_rspec.feature +11 -13
  25. data/features/pending_and_skipped_examples/README.md +3 -0
  26. data/features/pending_and_skipped_examples/pending_examples.feature +118 -0
  27. data/features/pending_and_skipped_examples/skipped_examples.feature +106 -0
  28. data/features/step_definitions/additional_cli_steps.rb +34 -0
  29. data/features/subject/explicit_subject.feature +1 -1
  30. data/features/subject/one_liner_syntax.feature +71 -0
  31. data/lib/rspec/core.rb +6 -14
  32. data/lib/rspec/core/backtrace_formatter.rb +16 -4
  33. data/lib/rspec/core/command_line.rb +2 -3
  34. data/lib/rspec/core/configuration.rb +114 -125
  35. data/lib/rspec/core/configuration_options.rb +32 -18
  36. data/lib/rspec/core/dsl.rb +80 -18
  37. data/lib/rspec/core/example.rb +84 -33
  38. data/lib/rspec/core/example_group.rb +95 -43
  39. data/lib/rspec/core/filter_manager.rb +31 -40
  40. data/lib/rspec/core/formatters.rb +137 -0
  41. data/lib/rspec/core/formatters/base_formatter.rb +28 -41
  42. data/lib/rspec/core/formatters/base_text_formatter.rb +26 -37
  43. data/lib/rspec/core/formatters/deprecation_formatter.rb +48 -27
  44. data/lib/rspec/core/formatters/documentation_formatter.rb +27 -22
  45. data/lib/rspec/core/formatters/html_formatter.rb +48 -56
  46. data/lib/rspec/core/formatters/html_printer.rb +11 -18
  47. data/lib/rspec/core/formatters/json_formatter.rb +18 -22
  48. data/lib/rspec/core/formatters/legacy_formatter.rb +227 -0
  49. data/lib/rspec/core/formatters/progress_formatter.rb +7 -10
  50. data/lib/rspec/core/hooks.rb +250 -217
  51. data/lib/rspec/core/memoized_helpers.rb +43 -9
  52. data/lib/rspec/core/mocking_adapters/flexmock.rb +29 -0
  53. data/lib/rspec/core/{mocking/with_mocha.rb → mocking_adapters/mocha.rb} +19 -16
  54. data/lib/rspec/core/mocking_adapters/null.rb +12 -0
  55. data/lib/rspec/core/mocking_adapters/rr.rb +28 -0
  56. data/lib/rspec/core/mocking_adapters/rspec.rb +30 -0
  57. data/lib/rspec/core/notifications.rb +100 -0
  58. data/lib/rspec/core/option_parser.rb +11 -18
  59. data/lib/rspec/core/pending.rb +78 -47
  60. data/lib/rspec/core/project_initializer.rb +2 -49
  61. data/lib/rspec/core/project_initializer/dot_rspec +3 -0
  62. data/lib/rspec/core/project_initializer/spec_helper.rb +82 -0
  63. data/lib/rspec/core/rake_task.rb +5 -14
  64. data/lib/rspec/core/reporter.rb +24 -32
  65. data/lib/rspec/core/ruby_project.rb +1 -1
  66. data/lib/rspec/core/runner.rb +14 -4
  67. data/lib/rspec/core/shared_example_group.rb +40 -13
  68. data/lib/rspec/core/version.rb +1 -1
  69. data/spec/command_line/order_spec.rb +15 -15
  70. data/spec/rspec/core/backtrace_formatter_spec.rb +15 -1
  71. data/spec/rspec/core/command_line_spec.rb +18 -17
  72. data/spec/rspec/core/configuration_options_spec.rb +57 -34
  73. data/spec/rspec/core/configuration_spec.rb +162 -184
  74. data/spec/rspec/core/drb_command_line_spec.rb +5 -7
  75. data/spec/rspec/core/drb_options_spec.rb +2 -2
  76. data/spec/rspec/core/dsl_spec.rb +79 -15
  77. data/spec/rspec/core/example_group_spec.rb +253 -39
  78. data/spec/rspec/core/example_spec.rb +149 -33
  79. data/spec/rspec/core/filter_manager_spec.rb +9 -26
  80. data/spec/rspec/core/formatters/base_formatter_spec.rb +2 -5
  81. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +42 -145
  82. data/spec/rspec/core/formatters/deprecation_formatter_spec.rb +64 -34
  83. data/spec/rspec/core/formatters/documentation_formatter_spec.rb +15 -28
  84. data/spec/rspec/core/formatters/helpers_spec.rb +2 -2
  85. data/spec/rspec/core/formatters/{html_formatted-1.8.7.html → html_formatted-2.1.0.html} +22 -44
  86. data/spec/rspec/core/formatters/{html_formatted-1.8.7-jruby.html → html_formatted.html} +30 -49
  87. data/spec/rspec/core/formatters/html_formatter_spec.rb +35 -19
  88. data/spec/rspec/core/formatters/json_formatter_spec.rb +42 -40
  89. data/spec/rspec/core/formatters/legacy_formatter_spec.rb +137 -0
  90. data/spec/rspec/core/formatters/progress_formatter_spec.rb +38 -25
  91. data/spec/rspec/core/formatters/snippet_extractor_spec.rb +1 -1
  92. data/spec/rspec/core/formatters_spec.rb +120 -0
  93. data/spec/rspec/core/hooks_filtering_spec.rb +1 -1
  94. data/spec/rspec/core/hooks_spec.rb +13 -2
  95. data/spec/rspec/core/memoized_helpers_spec.rb +17 -8
  96. data/spec/rspec/core/metadata_spec.rb +3 -3
  97. data/spec/rspec/core/option_parser_spec.rb +53 -46
  98. data/spec/rspec/core/ordering_spec.rb +4 -4
  99. data/spec/rspec/core/pending_example_spec.rb +23 -126
  100. data/spec/rspec/core/pending_spec.rb +8 -0
  101. data/spec/rspec/core/project_initializer_spec.rb +8 -41
  102. data/spec/rspec/core/rake_task_spec.rb +15 -4
  103. data/spec/rspec/core/random_spec.rb +1 -1
  104. data/spec/rspec/core/reporter_spec.rb +50 -37
  105. data/spec/rspec/core/resources/formatter_specs.rb +9 -11
  106. data/spec/rspec/core/rspec_matchers_spec.rb +1 -1
  107. data/spec/rspec/core/ruby_project_spec.rb +3 -3
  108. data/spec/rspec/core/runner_spec.rb +65 -23
  109. data/spec/rspec/core/shared_context_spec.rb +4 -4
  110. data/spec/rspec/core/shared_example_group/collection_spec.rb +1 -1
  111. data/spec/rspec/core/shared_example_group_spec.rb +20 -11
  112. data/spec/rspec/core/warnings_spec.rb +1 -1
  113. data/spec/rspec/core/world_spec.rb +10 -10
  114. data/spec/rspec/core_spec.rb +2 -2
  115. data/spec/spec_helper.rb +12 -24
  116. data/spec/support/config_options_helper.rb +1 -3
  117. data/spec/support/formatter_support.rb +83 -0
  118. data/spec/support/isolate_load_path_mutation.rb +1 -2
  119. data/spec/support/isolated_directory.rb +1 -1
  120. data/spec/support/isolated_home_directory.rb +1 -1
  121. data/spec/support/legacy_formatter_using_sub_classing_example.rb +87 -0
  122. data/spec/support/matchers.rb +20 -0
  123. data/spec/support/mathn_integration_support.rb +2 -2
  124. data/spec/support/old_style_formatter_example.rb +69 -0
  125. data/spec/support/shared_example_groups.rb +1 -1
  126. data/spec/support/spec_files.rb +3 -3
  127. metadata +192 -69
  128. metadata.gz.sig +3 -1
  129. checksums.yaml +0 -15
  130. checksums.yaml.gz.sig +0 -2
  131. data/features/configuration/show_failures_in_pending_blocks.feature +0 -61
  132. data/features/pending/pending_examples.feature +0 -229
  133. data/features/subject/implicit_receiver.feature +0 -29
  134. data/lib/rspec/core/mocking/with_absolutely_nothing.rb +0 -11
  135. data/lib/rspec/core/mocking/with_flexmock.rb +0 -27
  136. data/lib/rspec/core/mocking/with_rr.rb +0 -27
  137. data/lib/rspec/core/mocking/with_rspec.rb +0 -27
  138. data/spec/rspec/core/formatters/html_formatted-1.8.7-rbx.html +0 -477
  139. data/spec/rspec/core/formatters/html_formatted-1.9.2.html +0 -425
  140. data/spec/rspec/core/formatters/html_formatted-1.9.3-jruby.html +0 -416
  141. data/spec/rspec/core/formatters/html_formatted-1.9.3-rbx.html +0 -477
  142. data/spec/rspec/core/formatters/html_formatted-1.9.3.html +0 -419
  143. data/spec/rspec/core/formatters/html_formatted-2.0.0.html +0 -425
  144. data/spec/support/in_sub_process.rb +0 -37
  145. data/spec/support/sandboxed_mock_space.rb +0 -100
@@ -4,8 +4,9 @@ require 'json'
4
4
  module RSpec
5
5
  module Core
6
6
  module Formatters
7
-
8
7
  class JsonFormatter < BaseFormatter
8
+ Formatters.register self, :message, :dump_summary, :stop, :close,
9
+ :dump_profile
9
10
 
10
11
  attr_reader :output_hash
11
12
 
@@ -14,32 +15,23 @@ module RSpec
14
15
  @output_hash = {}
15
16
  end
16
17
 
17
- def message(message)
18
- (@output_hash[:messages] ||= []) << message
18
+ def message(notification)
19
+ (@output_hash[:messages] ||= []) << notification.message
19
20
  end
20
21
 
21
- def dump_summary(duration, example_count, failure_count, pending_count)
22
- super(duration, example_count, failure_count, pending_count)
22
+ def dump_summary(summary)
23
23
  @output_hash[:summary] = {
24
- :duration => duration,
25
- :example_count => example_count,
26
- :failure_count => failure_count,
27
- :pending_count => pending_count
24
+ :duration => summary.duration,
25
+ :example_count => summary.example_count,
26
+ :failure_count => summary.failure_count,
27
+ :pending_count => summary.pending_count
28
28
  }
29
- @output_hash[:summary_line] = summary_line(example_count, failure_count, pending_count)
30
-
31
- dump_profile unless mute_profile_output?(failure_count)
32
- end
29
+ @output_hash[:summary_line] = summary.summary_line
33
30
 
34
- def summary_line(example_count, failure_count, pending_count)
35
- summary = pluralize(example_count, "example")
36
- summary << ", " << pluralize(failure_count, "failure")
37
- summary << ", #{pending_count} pending" if pending_count > 0
38
- summary
31
+ dump_profile unless mute_profile_output?(summary.failure_count)
39
32
  end
40
33
 
41
- def stop
42
- super
34
+ def stop(notification)
43
35
  @output_hash[:examples] = examples.map do |example|
44
36
  format_example(example).tap do |hash|
45
37
  if e=example.exception
@@ -53,7 +45,7 @@ module RSpec
53
45
  end
54
46
  end
55
47
 
56
- def close
48
+ def close(notification)
57
49
  output.write @output_hash.to_json
58
50
  output.close if IO === output && output != $stdout
59
51
  end
@@ -64,6 +56,7 @@ module RSpec
64
56
  dump_profile_slowest_example_groups
65
57
  end
66
58
 
59
+ # @api private
67
60
  def dump_profile_slowest_examples
68
61
  @output_hash[:profile] = {}
69
62
  sorted_examples = slowest_examples
@@ -76,6 +69,7 @@ module RSpec
76
69
  @output_hash[:profile][:total] = sorted_examples[:total]
77
70
  end
78
71
 
72
+ # @api private
79
73
  def dump_profile_slowest_example_groups
80
74
  @output_hash[:profile] ||= {}
81
75
  @output_hash[:profile][:groups] = slowest_groups.map do |loc, hash|
@@ -84,13 +78,15 @@ module RSpec
84
78
  end
85
79
 
86
80
  private
81
+
87
82
  def format_example(example)
88
83
  {
89
84
  :description => example.description,
90
85
  :full_description => example.full_description,
91
86
  :status => example.execution_result[:status],
92
87
  :file_path => example.metadata[:file_path],
93
- :line_number => example.metadata[:line_number]
88
+ :line_number => example.metadata[:line_number],
89
+ :run_time => example.execution_result[:run_time]
94
90
  }
95
91
  end
96
92
  end
@@ -0,0 +1,227 @@
1
+ require 'rspec/core/formatters/helpers'
2
+ require 'stringio'
3
+
4
+ module RSpec
5
+ module Core
6
+ module Formatters
7
+
8
+ # The `LegacyFormatter` is used to wrap older RSpec 2.x style formatters
9
+ # for the new 3.x implementation. It takes care of registering all the
10
+ # old notifications and translating them to the older formatter.
11
+ #
12
+ # @see RSpec::Core::Formatters::BaseFormatter
13
+ class LegacyFormatter
14
+ NOTIFICATIONS = %W[start message example_group_started example_group_finished example_started
15
+ example_passed example_failed example_pending start_dump dump_pending
16
+ dump_failures dump_summary seed close stop deprecation deprecation_summary]
17
+
18
+ module LegacyInterface
19
+
20
+ def start(count)
21
+ super Notifications::CountNotification.new(count)
22
+ end
23
+
24
+ def example_group_started(group)
25
+ super Notifications::GroupNotification.new(group) if defined?(super)
26
+ end
27
+
28
+ def example_group_finished(group)
29
+ super Notifications::GroupNotification.new(group) if defined?(super)
30
+ end
31
+
32
+ def example_started(example)
33
+ super Notifications::ExampleNotification.new(example) if defined?(super)
34
+ end
35
+
36
+ def example_passed(example)
37
+ super Notifications::ExampleNotification.new(example) if defined?(super)
38
+ end
39
+
40
+ def example_pending(example)
41
+ super Notifications::ExampleNotification.new(example) if defined?(super)
42
+ end
43
+
44
+ def example_failed(example)
45
+ super Notifications::ExampleNotification.new(example) if defined?(super)
46
+ end
47
+
48
+ def message(message)
49
+ super Notifications::MessageNotification.new(message) if defined?(super)
50
+ end
51
+
52
+ attr_reader :duration, :example_count, :failure_count, :pending_count
53
+ def dump_summary(duration, examples, failures, pending)
54
+ @duration = duration
55
+ @example_count = examples
56
+ @failure_count = failures
57
+ @pending_count = pending
58
+ super Notifications::SummaryNotification.new(duration, examples, failures, pending) if defined?(super)
59
+ end
60
+
61
+ def seed(seed)
62
+ super Notifications::SeedNotification.new(seed, true) if defined?(super)
63
+ end
64
+
65
+
66
+ def start_dump
67
+ super(Notifications::NullNotification) if defined?(super)
68
+ end
69
+
70
+ def dump_failures
71
+ super(Notifications::NullNotification) if defined?(super)
72
+ end
73
+
74
+ def dump_pending
75
+ super(Notifications::NullNotification) if defined?(super)
76
+ end
77
+
78
+ def dump_profile
79
+ super(Notifications::NullNotification) if defined?(super)
80
+ end
81
+
82
+ def close
83
+ super(Notifications::NullNotification) if defined?(super)
84
+ end
85
+
86
+ def stop
87
+ super(Notifications::NullNotification) if defined?(super)
88
+ end
89
+ end
90
+
91
+ # @api private
92
+ attr_reader :formatter
93
+
94
+ # @api public
95
+ #
96
+ # @param formatter
97
+ def initialize(formatter_class, *args)
98
+ if formatter_class.ancestors.include?(BaseFormatter)
99
+ formatter_class.class_eval do
100
+ include LegacyInterface
101
+ end
102
+ end
103
+ @formatter = formatter_class.new(*args)
104
+ end
105
+
106
+ # @api public
107
+ #
108
+ # This method is invoked during the setup phase to register
109
+ # a formatters with the reporter
110
+ #
111
+ def notifications
112
+ @notifications ||= NOTIFICATIONS.select { |m| @formatter.respond_to? m }
113
+ end
114
+
115
+ # @api public
116
+ #
117
+ # @param example_count
118
+ def start(notification)
119
+ @formatter.start notification.count
120
+ end
121
+
122
+ # @api public
123
+ #
124
+ # @param example_group
125
+ def example_group_started(notification)
126
+ @formatter.example_group_started notification.group
127
+ end
128
+
129
+ # @api public
130
+ #
131
+ # @param example_group
132
+ def example_group_finished(notification)
133
+ @formatter.example_group_finished notification.group
134
+ end
135
+
136
+ # @api public
137
+ #
138
+ # @param example
139
+ def example_started(notification)
140
+ @formatter.example_started notification.example
141
+ end
142
+
143
+ # @api public
144
+ #
145
+ # @param example
146
+ def example_passed(notification)
147
+ @formatter.example_passed notification.example
148
+ end
149
+
150
+ # @api public
151
+ #
152
+ # @param example
153
+ def example_pending(notification)
154
+ @formatter.example_pending notification.example
155
+ end
156
+
157
+ # @api public
158
+ #
159
+ # @param example
160
+ def example_failed(notification)
161
+ @formatter.example_failed notification.example
162
+ end
163
+
164
+ # @api public
165
+ #
166
+ # @param message
167
+ def message(notification)
168
+ @formatter.message notification.message
169
+ end
170
+
171
+ # @api public
172
+ #
173
+ def stop(notification)
174
+ @formatter.stop
175
+ end
176
+
177
+ # @api public
178
+ #
179
+ def start_dump(notification)
180
+ @formatter.start_dump
181
+ end
182
+
183
+ # @api public
184
+ #
185
+ def dump_failures(notification)
186
+ @formatter.dump_failures
187
+ end
188
+
189
+ # @api public
190
+ #
191
+ # @param duration
192
+ # @param example_count
193
+ # @param failure_count
194
+ # @param pending_count
195
+ def dump_summary(summary)
196
+ @formatter.dump_summary summary.duration, summary.example_count, summary.failure_count, summary.pending_count
197
+ end
198
+
199
+ # @api public
200
+ #
201
+ def dump_pending(notification)
202
+ @formatter.dump_pending
203
+ end
204
+
205
+ # @api public
206
+ #
207
+ def dump_profile(notification)
208
+ @formatter.dump_profile
209
+ end
210
+
211
+ # @api public
212
+ #
213
+ # @param seed
214
+ def seed(notification)
215
+ @formatter.seed notification.seed
216
+ end
217
+
218
+ # @api public
219
+ #
220
+ def close(notification)
221
+ @formatter.close
222
+ end
223
+
224
+ end
225
+ end
226
+ end
227
+ end
@@ -2,31 +2,28 @@ require 'rspec/core/formatters/base_text_formatter'
2
2
  module RSpec
3
3
  module Core
4
4
  module Formatters
5
-
6
5
  class ProgressFormatter < BaseTextFormatter
6
+ Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
7
7
 
8
- def example_passed(example)
9
- super(example)
8
+ def example_passed(notification)
10
9
  output.print success_color('.')
11
10
  end
12
11
 
13
- def example_pending(example)
14
- super(example)
12
+ def example_pending(notification)
13
+ super
15
14
  output.print pending_color('*')
16
15
  end
17
16
 
18
- def example_failed(example)
19
- super(example)
17
+ def example_failed(notification)
18
+ super
20
19
  output.print failure_color('F')
21
20
  end
22
21
 
23
- def start_dump
24
- super()
22
+ def start_dump(notification)
25
23
  output.puts
26
24
  end
27
25
 
28
26
  end
29
-
30
27
  end
31
28
  end
32
29
  end
@@ -1,167 +1,6 @@
1
1
  module RSpec
2
2
  module Core
3
3
  module Hooks
4
- class Hook
5
- attr_reader :block, :options
6
-
7
- def initialize(block, options)
8
- @block = block
9
- @options = options
10
- end
11
-
12
- def options_apply?(example_or_group)
13
- example_or_group.all_apply?(options)
14
- end
15
- end
16
-
17
- class BeforeHook < Hook
18
- def run(example)
19
- example.instance_exec(example, &block)
20
- end
21
-
22
- def display_name
23
- "before hook"
24
- end
25
- end
26
-
27
- class AfterHook < Hook
28
- def run(example)
29
- example.instance_exec_with_rescue("in an after hook", &block)
30
- end
31
-
32
- def display_name
33
- "after hook"
34
- end
35
- end
36
-
37
- class AfterAllHook < Hook
38
- def run(example)
39
- example.instance_exec(example, &block)
40
- rescue Exception => e
41
- # TODO: come up with a better solution for this.
42
- RSpec.configuration.reporter.message <<-EOS
43
-
44
- An error occurred in an after(:all) hook.
45
- #{e.class}: #{e.message}
46
- occurred at #{e.backtrace.first}
47
-
48
- EOS
49
- end
50
-
51
- def display_name
52
- "after(:all) hook"
53
- end
54
- end
55
-
56
- class AroundHook < Hook
57
- def display_name
58
- "around hook"
59
- end
60
- end
61
-
62
- class BaseHookCollection
63
- Array.public_instance_methods(false).each do |name|
64
- define_method(name) { |*a, &b| hooks.__send__(name, *a, &b) }
65
- end
66
-
67
- attr_reader :hooks
68
- protected :hooks
69
-
70
- alias append push
71
- alias prepend unshift
72
-
73
- def initialize(hooks=[])
74
- @hooks = hooks
75
- end
76
- end
77
-
78
- class HookCollection < BaseHookCollection
79
- def for(example_or_group)
80
- self.class.
81
- new(hooks.select {|hook| hook.options_apply?(example_or_group)}).
82
- with(example_or_group)
83
- end
84
-
85
- def with(example)
86
- @example = example
87
- self
88
- end
89
-
90
- def run
91
- hooks.each {|h| h.run(@example)}
92
- end
93
- end
94
-
95
- class AroundHookCollection < BaseHookCollection
96
- def for(example, initial_procsy=nil)
97
- self.class.new(hooks.select {|hook| hook.options_apply?(example)}).
98
- with(example, initial_procsy)
99
- end
100
-
101
- def with(example, initial_procsy)
102
- @example = example
103
- @initial_procsy = initial_procsy
104
- self
105
- end
106
-
107
- def run
108
- hooks.reduce(@initial_procsy) do |procsy, around_hook|
109
- procsy.wrap do
110
- @example.instance_exec(procsy, &around_hook.block)
111
- end
112
- end.call
113
- end
114
- end
115
-
116
- class GroupHookCollection < BaseHookCollection
117
- def for(group)
118
- @group = group
119
- self
120
- end
121
-
122
- def run
123
- hooks.shift.run(@group) until hooks.empty?
124
- end
125
- end
126
-
127
- class HookCollections
128
- def initialize(data)
129
- @data = data
130
- end
131
-
132
- def [](key)
133
- @data[key]
134
- end
135
-
136
- def register_globals(host, globals)
137
- process(host, globals, :before, :each)
138
- process(host, globals, :after, :each)
139
- process(host, globals, :around, :each)
140
-
141
- process(host, globals, :before, :all)
142
- process(host, globals, :after, :all)
143
- end
144
-
145
- private
146
-
147
- def process(host, globals, position, scope)
148
- globals[position][scope].each do |hook|
149
- next unless scope == :each || hook.options_apply?(host)
150
- next if host.parent_groups.any? {|a| a.hooks[position][scope].include?(hook)}
151
- self[position][scope] << hook
152
- end
153
- end
154
- end
155
-
156
- # @private
157
- def hooks
158
- @hooks ||= HookCollections.new(
159
- :around => { :each => AroundHookCollection.new },
160
- :before => { :each => HookCollection.new, :all => HookCollection.new, :suite => HookCollection.new },
161
- :after => { :each => HookCollection.new, :all => HookCollection.new, :suite => HookCollection.new }
162
- )
163
- end
164
-
165
4
  # @api public
166
5
  # @overload before(&block)
167
6
  # @overload before(scope, &block)
@@ -329,7 +168,7 @@ EOS
329
168
  # end
330
169
  # end
331
170
  def before(*args, &block)
332
- register_hook :append, :before, *args, &block
171
+ hooks.register :append, :before, *args, &block
333
172
  end
334
173
 
335
174
  alias_method :append_before, :before
@@ -339,7 +178,7 @@ EOS
339
178
  #
340
179
  # See #before for scoping semantics.
341
180
  def prepend_before(*args, &block)
342
- register_hook :prepend, :before, *args, &block
181
+ hooks.register :prepend, :before, *args, &block
343
182
  end
344
183
 
345
184
  # @api public
@@ -391,7 +230,7 @@ EOS
391
230
  # Similarly, if more than one `after` is declared within any one scope,
392
231
  # they are run in reverse order of that in which they are declared.
393
232
  def after(*args, &block)
394
- register_hook :prepend, :after, *args, &block
233
+ hooks.register :prepend, :after, *args, &block
395
234
  end
396
235
 
397
236
  alias_method :prepend_after, :after
@@ -401,7 +240,7 @@ EOS
401
240
  #
402
241
  # See #after for scoping semantics.
403
242
  def append_after(*args, &block)
404
- register_hook :append, :after, *args, &block
243
+ hooks.register :append, :after, *args, &block
405
244
  end
406
245
 
407
246
  # @api public
@@ -449,85 +288,279 @@ EOS
449
288
  # around(:each) {|ex| FakeFS(&ex)}
450
289
  #
451
290
  def around(*args, &block)
452
- register_hook :prepend, :around, *args, &block
291
+ hooks.register :prepend, :around, *args, &block
292
+ end
293
+
294
+ # @api private
295
+ # Holds the various registered hooks.
296
+ def hooks
297
+ @hooks ||= HookCollections.new(self,
298
+ :around => { :each => AroundHookCollection.new },
299
+ :before => { :each => HookCollection.new, :all => HookCollection.new, :suite => HookCollection.new },
300
+ :after => { :each => HookCollection.new, :all => HookCollection.new, :suite => HookCollection.new }
301
+ )
453
302
  end
454
303
 
304
+ private
305
+
455
306
  # @private
456
- #
457
- # Runs all of the blocks stored with the hook in the context of the
458
- # example. If no example is provided, just calls the hook directly.
459
- def run_hook(hook, scope, example_or_group=ExampleGroup.new, initial_procsy=nil)
460
- return if RSpec.configuration.dry_run?
307
+ class Hook
308
+ attr_reader :block, :options
461
309
 
462
- find_hook(hook, scope, example_or_group, initial_procsy).run
310
+ def initialize(block, options)
311
+ @block = block
312
+ @options = options
313
+ end
314
+
315
+ def options_apply?(example_or_group)
316
+ example_or_group.all_apply?(options)
317
+ end
463
318
  end
464
319
 
465
320
  # @private
466
- def around_each_hooks_for(example, initial_procsy=nil)
467
- AroundHookCollection.new(FlatMap.flat_map(parent_groups) {|a| a.hooks[:around][:each]}).for(example, initial_procsy)
321
+ class BeforeHook < Hook
322
+ def run(example)
323
+ example.instance_exec(example, &block)
324
+ end
325
+
326
+ def display_name
327
+ "before hook"
328
+ end
468
329
  end
469
330
 
470
- private
331
+ # @private
332
+ class AfterHook < Hook
333
+ def run(example)
334
+ example.instance_exec_with_rescue("in an after hook", &block)
335
+ end
471
336
 
472
- SCOPES = [:each, :all, :suite]
337
+ def display_name
338
+ "after hook"
339
+ end
340
+ end
473
341
 
474
- HOOK_TYPES = {
475
- :before => Hash.new { BeforeHook },
476
- :after => Hash.new { AfterHook },
477
- :around => Hash.new { AroundHook }
478
- }
342
+ # @private
343
+ class AfterAllHook < Hook
344
+ def run(example)
345
+ example.instance_exec(example, &block)
346
+ rescue Exception => e
347
+ # TODO: come up with a better solution for this.
348
+ RSpec.configuration.reporter.message <<-EOS
479
349
 
480
- HOOK_TYPES[:after][:all] = AfterAllHook
350
+ An error occurred in an after(:all) hook.
351
+ #{e.class}: #{e.message}
352
+ occurred at #{e.backtrace.first}
481
353
 
482
- def before_all_hooks_for(group)
483
- GroupHookCollection.new(hooks[:before][:all]).for(group)
484
- end
354
+ EOS
355
+ end
485
356
 
486
- def after_all_hooks_for(group)
487
- GroupHookCollection.new(hooks[:after][:all]).for(group)
357
+ def display_name
358
+ "after(:all) hook"
359
+ end
488
360
  end
489
361
 
490
- def before_each_hooks_for(example)
491
- HookCollection.new(FlatMap.flat_map(parent_groups.reverse) {|a| a.hooks[:before][:each]}).for(example)
362
+ # @private
363
+ class AroundHook < Hook
364
+ def display_name
365
+ "around hook"
366
+ end
492
367
  end
493
368
 
494
- def after_each_hooks_for(example)
495
- HookCollection.new(FlatMap.flat_map(parent_groups) {|a| a.hooks[:after][:each]}).for(example)
369
+ # @private
370
+ class BaseHookCollection
371
+ Array.public_instance_methods(false).each do |name|
372
+ define_method(name) { |*a, &b| hooks.__send__(name, *a, &b) }
373
+ end
374
+
375
+ attr_reader :hooks
376
+ protected :hooks
377
+
378
+ alias append push
379
+ alias prepend unshift
380
+
381
+ def initialize(hooks=[])
382
+ @hooks = hooks
383
+ end
496
384
  end
497
385
 
498
- def register_hook prepend_or_append, hook, *args, &block
499
- scope, options = scope_and_options_from(*args)
500
- hooks[hook][scope].send(prepend_or_append, HOOK_TYPES[hook][scope].new(block, options))
386
+ # @private
387
+ class HookCollection < BaseHookCollection
388
+ def for(example_or_group)
389
+ self.class.
390
+ new(hooks.select {|hook| hook.options_apply?(example_or_group)}).
391
+ with(example_or_group)
392
+ end
393
+
394
+ def with(example)
395
+ @example = example
396
+ self
397
+ end
398
+
399
+ def run
400
+ hooks.each {|h| h.run(@example)}
401
+ end
501
402
  end
502
403
 
503
- def find_hook(hook, scope, example_or_group, initial_procsy)
504
- case [hook, scope]
505
- when [:before, :all]
506
- before_all_hooks_for(example_or_group)
507
- when [:after, :all]
508
- after_all_hooks_for(example_or_group)
509
- when [:around, :each]
510
- around_each_hooks_for(example_or_group, initial_procsy)
511
- when [:before, :each]
512
- before_each_hooks_for(example_or_group)
513
- when [:after, :each]
514
- after_each_hooks_for(example_or_group)
515
- when [:before, :suite], [:after, :suite]
516
- hooks[hook][:suite].with(example_or_group)
404
+ # @private
405
+ class AroundHookCollection < BaseHookCollection
406
+ def for(example, initial_procsy=nil)
407
+ self.class.new(hooks.select {|hook| hook.options_apply?(example)}).
408
+ with(example, initial_procsy)
409
+ end
410
+
411
+ def with(example, initial_procsy)
412
+ @example = example
413
+ @initial_procsy = initial_procsy
414
+ self
415
+ end
416
+
417
+ def run
418
+ hooks.inject(@initial_procsy) do |procsy, around_hook|
419
+ procsy.wrap do
420
+ @example.instance_exec(procsy, &around_hook.block)
421
+ end
422
+ end.call
517
423
  end
518
424
  end
519
425
 
520
- def scope_and_options_from(*args)
521
- return extract_scope_from(args), Metadata.build_hash_from(args)
426
+ # @private
427
+ class GroupHookCollection < BaseHookCollection
428
+ def for(group)
429
+ @group = group
430
+ self
431
+ end
432
+
433
+ def run
434
+ hooks.shift.run(@group) until hooks.empty?
435
+ end
522
436
  end
523
437
 
524
- def extract_scope_from(args)
525
- if SCOPES.include?(args.first)
526
- args.shift
527
- elsif args.any? { |a| a.is_a?(Symbol) }
528
- raise ArgumentError.new("You must explicitly give a scope (:each, :all, or :suite) when using symbols as metadata for a hook.")
529
- else
530
- :each
438
+ # @private
439
+ class HookCollections
440
+ def initialize(owner, data)
441
+ @owner = owner
442
+ @data = data
443
+ end
444
+
445
+ def [](key)
446
+ @data[key]
447
+ end
448
+
449
+ def register_globals(host, globals)
450
+ process(host, globals, :before, :each)
451
+ process(host, globals, :after, :each)
452
+ process(host, globals, :around, :each)
453
+
454
+ process(host, globals, :before, :all)
455
+ process(host, globals, :after, :all)
456
+ end
457
+
458
+ def around_each_hooks_for(example, initial_procsy=nil)
459
+ AroundHookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
460
+ a.hooks[:around][:each]
461
+ end).for(example, initial_procsy)
462
+ end
463
+
464
+ def register(prepend_or_append, hook, *args, &block)
465
+ scope, options = scope_and_options_from(*args)
466
+ self[hook][scope].__send__(prepend_or_append, HOOK_TYPES[hook][scope].new(block, options))
467
+ end
468
+
469
+ # @private
470
+ #
471
+ # Runs all of the blocks stored with the hook in the context of the
472
+ # example. If no example is provided, just calls the hook directly.
473
+ def run(hook, scope, example_or_group=ExampleGroup.new, initial_procsy=nil)
474
+ return if RSpec.configuration.dry_run?
475
+
476
+ find_hook(hook, scope, example_or_group, initial_procsy).run
477
+ end
478
+
479
+ SCOPES = [:each, :all, :suite]
480
+
481
+ SCOPE_ALIASES = {
482
+ :example => :each,
483
+ :context => :all,
484
+ }
485
+
486
+ HOOK_TYPES = {
487
+ :before => Hash.new { BeforeHook },
488
+ :after => Hash.new { AfterHook },
489
+ :around => Hash.new { AroundHook }
490
+ }
491
+
492
+ HOOK_TYPES[:after][:all] = AfterAllHook
493
+
494
+ private
495
+
496
+ def process(host, globals, position, scope)
497
+ globals[position][scope].each do |hook|
498
+ next unless scope == :each || hook.options_apply?(host)
499
+ next if host.parent_groups.any? {|a| a.hooks[position][scope].include?(hook)}
500
+ self[position][scope] << hook
501
+ end
502
+ end
503
+
504
+ def scope_and_options_from(*args)
505
+ return extract_scope_from(args), Metadata.build_hash_from(args)
506
+ end
507
+
508
+ def extract_scope_from(args)
509
+ if known_scope?(args.first)
510
+ normalized_scope_for(args.shift)
511
+ elsif args.any? { |a| a.is_a?(Symbol) }
512
+ 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."
513
+ raise ArgumentError.new error_message
514
+ else
515
+ :each
516
+ end
517
+ end
518
+
519
+ # @api private
520
+ def known_scope?(scope)
521
+ SCOPES.include?(scope) || SCOPE_ALIASES.keys.include?(scope)
522
+ end
523
+
524
+ # @api private
525
+ def normalized_scope_for(scope)
526
+ SCOPE_ALIASES[scope] || scope
527
+ end
528
+
529
+ def find_hook(hook, scope, example_or_group, initial_procsy)
530
+ case [hook, scope]
531
+ when [:before, :all]
532
+ before_all_hooks_for(example_or_group)
533
+ when [:after, :all]
534
+ after_all_hooks_for(example_or_group)
535
+ when [:around, :each]
536
+ around_each_hooks_for(example_or_group, initial_procsy)
537
+ when [:before, :each]
538
+ before_each_hooks_for(example_or_group)
539
+ when [:after, :each]
540
+ after_each_hooks_for(example_or_group)
541
+ when [:before, :suite], [:after, :suite]
542
+ self[hook][:suite].with(example_or_group)
543
+ end
544
+ end
545
+
546
+ def before_all_hooks_for(group)
547
+ GroupHookCollection.new(self[:before][:all]).for(group)
548
+ end
549
+
550
+ def after_all_hooks_for(group)
551
+ GroupHookCollection.new(self[:after][:all]).for(group)
552
+ end
553
+
554
+ def before_each_hooks_for(example)
555
+ HookCollection.new(FlatMap.flat_map(@owner.parent_groups.reverse) do |a|
556
+ a.hooks[:before][:each]
557
+ end).for(example)
558
+ end
559
+
560
+ def after_each_hooks_for(example)
561
+ HookCollection.new(FlatMap.flat_map(@owner.parent_groups) do |a|
562
+ a.hooks[:after][:each]
563
+ end).for(example)
531
564
  end
532
565
  end
533
566
  end