rspec-core 3.0.0.beta2 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +1 -0
  5. data/Changelog.md +297 -57
  6. data/README.md +16 -13
  7. data/lib/rspec/core.rb +55 -84
  8. data/lib/rspec/core/backport_random.rb +35 -3
  9. data/lib/rspec/core/backtrace_formatter.rb +4 -13
  10. data/lib/rspec/core/configuration.rb +330 -114
  11. data/lib/rspec/core/configuration_options.rb +38 -22
  12. data/lib/rspec/core/drb.rb +111 -0
  13. data/lib/rspec/core/dsl.rb +8 -2
  14. data/lib/rspec/core/example.rb +203 -94
  15. data/lib/rspec/core/example_group.rb +344 -316
  16. data/lib/rspec/core/filter_manager.rb +135 -90
  17. data/lib/rspec/core/flat_map.rb +1 -0
  18. data/lib/rspec/core/formatters.rb +50 -14
  19. data/lib/rspec/core/formatters/base_formatter.rb +32 -138
  20. data/lib/rspec/core/formatters/base_text_formatter.rb +32 -253
  21. data/lib/rspec/core/formatters/console_codes.rb +65 -0
  22. data/lib/rspec/core/formatters/deprecation_formatter.rb +24 -15
  23. data/lib/rspec/core/formatters/documentation_formatter.rb +7 -10
  24. data/lib/rspec/core/formatters/helpers.rb +15 -9
  25. data/lib/rspec/core/formatters/html_formatter.rb +17 -16
  26. data/lib/rspec/core/formatters/html_printer.rb +1 -0
  27. data/lib/rspec/core/formatters/json_formatter.rb +18 -20
  28. data/lib/rspec/core/formatters/profile_formatter.rb +67 -0
  29. data/lib/rspec/core/formatters/progress_formatter.rb +6 -7
  30. data/lib/rspec/core/formatters/snippet_extractor.rb +8 -6
  31. data/lib/rspec/core/hooks.rb +131 -125
  32. data/lib/rspec/core/memoized_helpers.rb +31 -26
  33. data/lib/rspec/core/metadata.rb +277 -184
  34. data/lib/rspec/core/metadata_filter.rb +86 -0
  35. data/lib/rspec/core/minitest_assertions_adapter.rb +28 -0
  36. data/lib/rspec/core/mocking_adapters/flexmock.rb +1 -1
  37. data/lib/rspec/core/mocking_adapters/mocha.rb +1 -1
  38. data/lib/rspec/core/mocking_adapters/null.rb +1 -1
  39. data/lib/rspec/core/mocking_adapters/rr.rb +2 -1
  40. data/lib/rspec/core/mocking_adapters/rspec.rb +1 -1
  41. data/lib/rspec/core/notifications.rb +435 -24
  42. data/lib/rspec/core/option_parser.rb +16 -25
  43. data/lib/rspec/core/ordering.rb +3 -1
  44. data/lib/rspec/core/pending.rb +57 -33
  45. data/lib/rspec/core/project_initializer.rb +2 -0
  46. data/lib/rspec/core/project_initializer/spec_helper.rb +5 -4
  47. data/lib/rspec/core/rake_task.rb +45 -20
  48. data/lib/rspec/core/reporter.rb +50 -22
  49. data/lib/rspec/core/ruby_project.rb +1 -0
  50. data/lib/rspec/core/runner.rb +93 -39
  51. data/lib/rspec/core/shared_context.rb +7 -5
  52. data/lib/rspec/core/shared_example_group.rb +85 -77
  53. data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
  54. data/lib/rspec/core/version.rb +3 -1
  55. data/lib/rspec/core/warnings.rb +35 -17
  56. data/lib/rspec/core/world.rb +57 -5
  57. metadata +56 -369
  58. metadata.gz.sig +3 -3
  59. data/features/README.md +0 -13
  60. data/features/Upgrade.md +0 -352
  61. data/features/command_line/README.md +0 -25
  62. data/features/command_line/dry_run.feature +0 -29
  63. data/features/command_line/example_name_option.feature +0 -97
  64. data/features/command_line/exit_status.feature +0 -82
  65. data/features/command_line/fail_fast.feature +0 -26
  66. data/features/command_line/format_option.feature +0 -75
  67. data/features/command_line/init.feature +0 -57
  68. data/features/command_line/line_number_appended_to_path.feature +0 -140
  69. data/features/command_line/line_number_option.feature +0 -58
  70. data/features/command_line/order.feature +0 -25
  71. data/features/command_line/pattern_option.feature +0 -49
  72. data/features/command_line/rake_task.feature +0 -122
  73. data/features/command_line/randomization.feature +0 -63
  74. data/features/command_line/require_option.feature +0 -43
  75. data/features/command_line/ruby.feature +0 -23
  76. data/features/command_line/tag.feature +0 -98
  77. data/features/command_line/warnings_option.feature +0 -29
  78. data/features/configuration/alias_example_to.feature +0 -39
  79. data/features/configuration/backtrace_exclusion_patterns.feature +0 -105
  80. data/features/configuration/custom_settings.feature +0 -84
  81. data/features/configuration/default_path.feature +0 -38
  82. data/features/configuration/deprecation_stream.feature +0 -58
  83. data/features/configuration/enable_global_dsl.feature +0 -54
  84. data/features/configuration/fail_fast.feature +0 -77
  85. data/features/configuration/failure_exit_code.feature +0 -36
  86. data/features/configuration/order_and_seed.feature +0 -3
  87. data/features/configuration/output_stream.feature +0 -24
  88. data/features/configuration/overriding_global_ordering.feature +0 -93
  89. data/features/configuration/pattern.feature +0 -38
  90. data/features/configuration/profile.feature +0 -220
  91. data/features/configuration/read_options_from_file.feature +0 -90
  92. data/features/configuration/run_all_when_everything_filtered.feature +0 -76
  93. data/features/example_groups/aliasing.feature +0 -48
  94. data/features/example_groups/basic_structure.feature +0 -55
  95. data/features/example_groups/shared_context.feature +0 -74
  96. data/features/example_groups/shared_examples.feature +0 -286
  97. data/features/expectation_framework_integration/configure_expectation_framework.feature +0 -102
  98. data/features/filtering/exclusion_filters.feature +0 -135
  99. data/features/filtering/if_and_unless.feature +0 -138
  100. data/features/filtering/inclusion_filters.feature +0 -101
  101. data/features/formatters/configurable_colors.feature +0 -31
  102. data/features/formatters/custom_formatter.feature +0 -68
  103. data/features/formatters/json_formatter.feature +0 -30
  104. data/features/formatters/regression_tests.feature +0 -95
  105. data/features/formatters/text_formatter.feature +0 -46
  106. data/features/helper_methods/arbitrary_methods.feature +0 -40
  107. data/features/helper_methods/let.feature +0 -50
  108. data/features/helper_methods/modules.feature +0 -146
  109. data/features/hooks/around_hooks.feature +0 -344
  110. data/features/hooks/before_and_after_hooks.feature +0 -427
  111. data/features/hooks/filtering.feature +0 -232
  112. data/features/metadata/current_example.feature +0 -56
  113. data/features/metadata/described_class.feature +0 -17
  114. data/features/metadata/user_defined.feature +0 -100
  115. data/features/mock_framework_integration/use_any_framework.feature +0 -106
  116. data/features/mock_framework_integration/use_flexmock.feature +0 -94
  117. data/features/mock_framework_integration/use_mocha.feature +0 -95
  118. data/features/mock_framework_integration/use_rr.feature +0 -96
  119. data/features/mock_framework_integration/use_rspec.feature +0 -95
  120. data/features/pending_and_skipped_examples/README.md +0 -3
  121. data/features/pending_and_skipped_examples/pending_examples.feature +0 -118
  122. data/features/pending_and_skipped_examples/skipped_examples.feature +0 -106
  123. data/features/spec_files/arbitrary_file_suffix.feature +0 -13
  124. data/features/step_definitions/additional_cli_steps.rb +0 -83
  125. data/features/subject/explicit_subject.feature +0 -101
  126. data/features/subject/implicit_subject.feature +0 -63
  127. data/features/subject/one_liner_syntax.feature +0 -71
  128. data/features/support/env.rb +0 -21
  129. data/features/support/require_expect_syntax_in_aruba_specs.rb +0 -16
  130. data/features/support/rubinius.rb +0 -6
  131. data/lib/rspec/core/command_line.rb +0 -35
  132. data/lib/rspec/core/drb_command_line.rb +0 -26
  133. data/lib/rspec/core/drb_options.rb +0 -87
  134. data/lib/rspec/core/formatters/legacy_formatter.rb +0 -227
  135. data/lib/rspec/core/shared_example_group/collection.rb +0 -27
  136. data/spec/command_line/order_spec.rb +0 -211
  137. data/spec/rspec/core/backtrace_formatter_spec.rb +0 -230
  138. data/spec/rspec/core/command_line_spec.rb +0 -112
  139. data/spec/rspec/core/command_line_spec_output.txt +0 -0
  140. data/spec/rspec/core/configuration_options_spec.rb +0 -409
  141. data/spec/rspec/core/configuration_spec.rb +0 -1479
  142. data/spec/rspec/core/drb_command_line_spec.rb +0 -102
  143. data/spec/rspec/core/drb_options_spec.rb +0 -193
  144. data/spec/rspec/core/dsl_spec.rb +0 -88
  145. data/spec/rspec/core/example_group_spec.rb +0 -1533
  146. data/spec/rspec/core/example_spec.rb +0 -642
  147. data/spec/rspec/core/filter_manager_spec.rb +0 -229
  148. data/spec/rspec/core/formatters/base_formatter_spec.rb +0 -64
  149. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +0 -303
  150. data/spec/rspec/core/formatters/deprecation_formatter_spec.rb +0 -208
  151. data/spec/rspec/core/formatters/documentation_formatter_spec.rb +0 -75
  152. data/spec/rspec/core/formatters/helpers_spec.rb +0 -104
  153. data/spec/rspec/core/formatters/html_formatted-2.1.0.html +0 -392
  154. data/spec/rspec/core/formatters/html_formatted.html +0 -397
  155. data/spec/rspec/core/formatters/html_formatter_spec.rb +0 -122
  156. data/spec/rspec/core/formatters/json_formatter_spec.rb +0 -206
  157. data/spec/rspec/core/formatters/legacy_formatter_spec.rb +0 -137
  158. data/spec/rspec/core/formatters/progress_formatter_spec.rb +0 -43
  159. data/spec/rspec/core/formatters/snippet_extractor_spec.rb +0 -26
  160. data/spec/rspec/core/formatters_spec.rb +0 -120
  161. data/spec/rspec/core/hooks_filtering_spec.rb +0 -227
  162. data/spec/rspec/core/hooks_spec.rb +0 -294
  163. data/spec/rspec/core/memoized_helpers_spec.rb +0 -495
  164. data/spec/rspec/core/metadata_spec.rb +0 -491
  165. data/spec/rspec/core/option_parser_spec.rb +0 -262
  166. data/spec/rspec/core/ordering_spec.rb +0 -102
  167. data/spec/rspec/core/pending_example_spec.rb +0 -117
  168. data/spec/rspec/core/pending_spec.rb +0 -8
  169. data/spec/rspec/core/project_initializer_spec.rb +0 -73
  170. data/spec/rspec/core/rake_task_spec.rb +0 -146
  171. data/spec/rspec/core/random_spec.rb +0 -47
  172. data/spec/rspec/core/reporter_spec.rb +0 -155
  173. data/spec/rspec/core/resources/a_bar.rb +0 -0
  174. data/spec/rspec/core/resources/a_foo.rb +0 -0
  175. data/spec/rspec/core/resources/a_spec.rb +0 -1
  176. data/spec/rspec/core/resources/custom_example_group_runner.rb +0 -14
  177. data/spec/rspec/core/resources/formatter_specs.rb +0 -58
  178. data/spec/rspec/core/resources/utf8_encoded.rb +0 -8
  179. data/spec/rspec/core/rspec_matchers_spec.rb +0 -45
  180. data/spec/rspec/core/ruby_project_spec.rb +0 -26
  181. data/spec/rspec/core/runner_spec.rb +0 -151
  182. data/spec/rspec/core/shared_context_spec.rb +0 -102
  183. data/spec/rspec/core/shared_example_group/collection_spec.rb +0 -57
  184. data/spec/rspec/core/shared_example_group_spec.rb +0 -114
  185. data/spec/rspec/core/warnings_spec.rb +0 -29
  186. data/spec/rspec/core/world_spec.rb +0 -142
  187. data/spec/rspec/core_spec.rb +0 -91
  188. data/spec/spec_helper.rb +0 -160
  189. data/spec/support/config_options_helper.rb +0 -13
  190. data/spec/support/formatter_support.rb +0 -83
  191. data/spec/support/helper_methods.rb +0 -26
  192. data/spec/support/isolate_load_path_mutation.rb +0 -5
  193. data/spec/support/isolated_directory.rb +0 -10
  194. data/spec/support/isolated_home_directory.rb +0 -16
  195. data/spec/support/legacy_formatter_using_sub_classing_example.rb +0 -87
  196. data/spec/support/matchers.rb +0 -85
  197. data/spec/support/mathn_integration_support.rb +0 -12
  198. data/spec/support/old_style_formatter_example.rb +0 -69
  199. data/spec/support/shared_example_groups.rb +0 -13
  200. data/spec/support/spec_files.rb +0 -44
  201. data/spec/support/stderr_splitter.rb +0 -36
@@ -1,5 +1,9 @@
1
1
  module RSpec
2
2
  module Core
3
+ # This module is included in {ExampleGroup}, making the methods
4
+ # available to be called from within example blocks.
5
+ #
6
+ # @see ClassMethods
3
7
  module MemoizedHelpers
4
8
  # @note `subject` was contributed by Joe Ferris to support the one-liner
5
9
  # syntax embraced by shoulda matchers:
@@ -41,13 +45,13 @@ module RSpec
41
45
  # end
42
46
  #
43
47
  # @note Because `subject` is designed to create state that is reset between
44
- # each example, and `before(:all)` is designed to setup state that is
48
+ # each example, and `before(:context)` is designed to setup state that is
45
49
  # shared across _all_ examples in an example group, `subject` is _not_
46
- # intended to be used in a `before(:all)` hook. RSpec 2.13.1 prints
47
- # a warning when you reference a `subject` from `before(:all)` and we plan
48
- # to have it raise an error in RSpec 3.
50
+ # intended to be used in a `before(:context)` hook.
49
51
  #
50
52
  # @see #should
53
+ # @see #should_not
54
+ # @see #is_expected
51
55
  def subject
52
56
  __memoized.fetch(:subject) do
53
57
  __memoized[:subject] = begin
@@ -115,7 +119,7 @@ module RSpec
115
119
  expect(subject)
116
120
  end
117
121
 
118
- private
122
+ private
119
123
 
120
124
  # @private
121
125
  def __memoized
@@ -123,14 +127,14 @@ module RSpec
123
127
  end
124
128
 
125
129
  # Used internally to customize the behavior of the
126
- # memoized hash when used in a `before(:all)` hook.
130
+ # memoized hash when used in a `before(:context)` hook.
127
131
  #
128
132
  # @private
129
- class AllHookMemoizedHash
130
- def self.isolate_for_all_hook(example_group_instance)
133
+ class ContextHookMemoizedHash
134
+ def self.isolate_for_context_hook(example_group_instance)
131
135
  hash = self
132
136
 
133
- example_group_instance.instance_eval do
137
+ example_group_instance.instance_exec do
134
138
  @__memoized = hash
135
139
 
136
140
  begin
@@ -159,9 +163,10 @@ is reset between each example, while #{hook_expression} exists to
159
163
  EOS
160
164
  end
161
165
 
166
+ # @private
162
167
  class Before < self
163
168
  def self.hook_expression
164
- "`before(:all)`"
169
+ "`before(:context)`"
165
170
  end
166
171
 
167
172
  def self.article
@@ -173,9 +178,10 @@ EOS
173
178
  end
174
179
  end
175
180
 
181
+ # @private
176
182
  class After < self
177
183
  def self.hook_expression
178
- "`after(:all)`"
184
+ "`after(:context)`"
179
185
  end
180
186
 
181
187
  def self.article
@@ -188,10 +194,9 @@ EOS
188
194
  end
189
195
  end
190
196
 
191
- def self.included(mod)
192
- mod.extend(ClassMethods)
193
- end
194
-
197
+ # This module is extended onto {ExampleGroup}, making the methods
198
+ # available to be called from within example group blocks.
199
+ # You can think of them as being analagous to class macros.
195
200
  module ClassMethods
196
201
  # Generates a method whose return value is memoized after the first
197
202
  # call. Useful for reducing duplication between examples that assign
@@ -206,11 +211,9 @@ EOS
206
211
  # though we have yet to see this in practice. You've been warned.
207
212
  #
208
213
  # @note Because `let` is designed to create state that is reset between
209
- # each example, and `before(:all)` is designed to setup state that is
214
+ # each example, and `before(:context)` is designed to setup state that is
210
215
  # shared across _all_ examples in an example group, `let` is _not_
211
- # intended to be used in a `before(:all)` hook. RSpec 2.13.1 prints
212
- # a warning when you reference a `let` from `before(:all)` and we plan
213
- # to have it raise an error in RSpec 3.
216
+ # intended to be used in a `before(:context)` hook.
214
217
  #
215
218
  # @example
216
219
  #
@@ -265,7 +268,7 @@ EOS
265
268
  # end
266
269
  #
267
270
  # describe Thing do
268
- # after(:each) { Thing.reset_count }
271
+ # after(:example) { Thing.reset_count }
269
272
  #
270
273
  # context "using let" do
271
274
  # let(:thing) { Thing.new }
@@ -307,7 +310,7 @@ EOS
307
310
  # implicitly in one-liners and explicitly using an intention revealing
308
311
  # name.
309
312
  #
310
- # @param [String,Symbol] name used to define an accessor with an
313
+ # @param name [String,Symbol] used to define an accessor with an
311
314
  # intention revealing name
312
315
  # @param block defines the value to be returned by `subject` in examples
313
316
  #
@@ -328,6 +331,8 @@ EOS
328
331
  # end
329
332
  #
330
333
  # @see MemoizedHelpers#should
334
+ # @see MemoizedHelpers#should_not
335
+ # @see MemoizedHelpers#is_expected
331
336
  def subject(name=nil, &block)
332
337
  if name
333
338
  let(name, &block)
@@ -366,7 +371,7 @@ EOS
366
371
  # end
367
372
  #
368
373
  # describe Thing do
369
- # after(:each) { Thing.reset_count }
374
+ # after(:example) { Thing.reset_count }
370
375
  #
371
376
  # context "using subject" do
372
377
  # subject { Thing.new }
@@ -400,7 +405,7 @@ EOS
400
405
  end
401
406
  end
402
407
 
403
- # @api private
408
+ # @private
404
409
  #
405
410
  # Gets the LetDefinitions module. The module is mixed into
406
411
  # the example group and is used to hold all let definitions.
@@ -425,13 +430,13 @@ EOS
425
430
  end
426
431
  end
427
432
 
428
- # @api private
433
+ # @private
429
434
  def self.define_helpers_on(example_group)
430
435
  example_group.__send__(:include, module_for(example_group))
431
436
  end
432
437
 
433
438
  if Module.method(:const_defined?).arity == 1 # for 1.8
434
- # @api private
439
+ # @private
435
440
  #
436
441
  # Gets the named constant or yields.
437
442
  # On 1.8, const_defined? / const_get do not take into
@@ -444,7 +449,7 @@ EOS
444
449
  end
445
450
  end
446
451
  else
447
- # @api private
452
+ # @private
448
453
  #
449
454
  # Gets the named constant or yields.
450
455
  # On 1.9, const_defined? / const_get take into account the
@@ -24,8 +24,11 @@ module RSpec
24
24
  # @see FilterManager
25
25
  # @see Configuration#filter_run_including
26
26
  # @see Configuration#filter_run_excluding
27
- class Metadata < Hash
28
-
27
+ module Metadata
28
+ # @api private
29
+ #
30
+ # @param line [String] current code line
31
+ # @return [String] relative path to line
29
32
  def self.relative_path(line)
30
33
  line = line.sub(File.expand_path("."), ".")
31
34
  line = line.sub(/\A([^:]+:\d+)$/, '\\1')
@@ -50,264 +53,354 @@ module RSpec
50
53
  hash
51
54
  end
52
55
 
53
- module MetadataHash
56
+ # @private
57
+ def self.backtrace_from(block)
58
+ return caller unless block.respond_to?(:source_location)
59
+ [block.source_location.join(':')]
60
+ end
54
61
 
55
- # @private
56
- # Supports lazy evaluation of some values. Extended by
57
- # ExampleMetadataHash and GroupMetadataHash, which get mixed in to
58
- # Metadata for ExampleGroups and Examples (respectively).
59
- def [](key)
60
- store_computed(key) unless has_key?(key)
61
- super
62
+ # @private
63
+ # Used internally to populate metadata hashes with computed keys
64
+ # managed by RSpec.
65
+ class HashPopulator
66
+ attr_reader :metadata, :user_metadata, :description_args, :block
67
+
68
+ def initialize(metadata, user_metadata, description_args, block)
69
+ @metadata = metadata
70
+ @user_metadata = user_metadata
71
+ @description_args = description_args
72
+ @block = block
62
73
  end
63
74
 
64
- def fetch(key, *args)
65
- store_computed(key) unless has_key?(key)
66
- super
75
+ def populate
76
+ ensure_valid_user_keys
77
+
78
+ metadata[:execution_result] = Example::ExecutionResult.new
79
+ metadata[:block] = block
80
+ metadata[:description_args] = description_args
81
+ metadata[:description] = build_description_from(*metadata[:description_args])
82
+ metadata[:full_description] = full_description
83
+ metadata[:described_class] = described_class
84
+
85
+ populate_location_attributes
86
+ metadata.update(user_metadata)
87
+ RSpec.configuration.apply_derived_metadata_to(metadata)
67
88
  end
68
89
 
69
- private
70
-
71
- def store_computed(key)
72
- case key
73
- when :location
74
- store(:location, location)
75
- when :file_path, :line_number
76
- file_path, line_number = file_and_line_number
77
- store(:file_path, file_path)
78
- store(:line_number, line_number)
79
- when :execution_result
80
- store(:execution_result, {})
81
- when :describes, :described_class
82
- klass = described_class
83
- store(:described_class, klass)
84
- # TODO (2011-11-07 DC) deprecate :describes as a key
85
- store(:describes, klass)
86
- when :full_description
87
- store(:full_description, full_description)
88
- when :description
89
- store(:description, build_description_from(*self[:description_args]))
90
- when :description_args
91
- store(:description_args, [])
90
+ private
91
+
92
+ def populate_location_attributes
93
+ file_path, line_number = if backtrace = user_metadata.delete(:caller)
94
+ file_path_and_line_number_from(backtrace)
95
+ elsif block.respond_to?(:source_location)
96
+ block.source_location
97
+ else
98
+ file_path_and_line_number_from(caller)
92
99
  end
93
- end
94
100
 
95
- def location
96
- "#{self[:file_path]}:#{self[:line_number]}"
101
+ file_path = Metadata.relative_path(file_path)
102
+ metadata[:file_path] = file_path
103
+ metadata[:line_number] = line_number.to_i
104
+ metadata[:location] = "#{file_path}:#{line_number}"
97
105
  end
98
106
 
99
- def file_and_line_number
100
- first_caller_from_outside_rspec =~ /(.+?):(\d+)(|:\d+)/
101
- return [Metadata::relative_path($1), $2.to_i]
107
+ def file_path_and_line_number_from(backtrace)
108
+ first_caller_from_outside_rspec = backtrace.detect { |l| l !~ CallerFilter::LIB_REGEX }
109
+ first_caller_from_outside_rspec ||= backtrace.first
110
+ /(.+?):(\d+)(?:|:\d+)/.match(first_caller_from_outside_rspec).captures
102
111
  end
103
112
 
104
- def first_caller_from_outside_rspec
105
- self[:caller].detect {|l| l !~ /\/lib\/rspec\/core/}
113
+ def description_separator(parent_part, child_part)
114
+ if parent_part.is_a?(Module) && child_part =~ /^(#|::|\.)/
115
+ ''
116
+ else
117
+ ' '
118
+ end
106
119
  end
107
120
 
108
- def method_description_after_module?(parent_part, child_part)
109
- return false unless parent_part.is_a?(Module)
110
- child_part =~ /^(#|::|\.)/
121
+ def build_description_from(parent_description=nil, my_description=nil)
122
+ return parent_description.to_s unless my_description
123
+ separator = description_separator(parent_description, my_description)
124
+ parent_description.to_s + separator + my_description
111
125
  end
112
126
 
113
- def build_description_from(first_part = '', *parts)
114
- description, _ = parts.inject([first_part.to_s, first_part]) do |(desc, last_part), this_part|
115
- this_part = this_part.to_s
116
- this_part = (' ' + this_part) unless method_description_after_module?(last_part, this_part)
117
- [(desc + this_part), this_part]
127
+ def ensure_valid_user_keys
128
+ RESERVED_KEYS.each do |key|
129
+ if user_metadata.has_key?(key)
130
+ raise <<-EOM.gsub(/^\s+\|/, '')
131
+ |#{"*"*50}
132
+ |:#{key} is not allowed
133
+ |
134
+ |RSpec reserves some hash keys for its own internal use,
135
+ |including :#{key}, which is used on:
136
+ |
137
+ | #{CallerFilter.first_non_rspec_line}.
138
+ |
139
+ |Here are all of RSpec's reserved hash keys:
140
+ |
141
+ | #{RESERVED_KEYS.join("\n ")}
142
+ |#{"*"*50}
143
+ EOM
144
+ end
118
145
  end
119
-
120
- description
121
146
  end
122
147
  end
123
148
 
124
- # Mixed in to Metadata for an Example (extends MetadataHash) to support
125
- # lazy evaluation of some values.
126
- module ExampleMetadataHash
127
- include MetadataHash
149
+ # @private
150
+ class ExampleHash < HashPopulator
151
+ def self.create(group_metadata, user_metadata, description, block)
152
+ example_metadata = group_metadata.dup
153
+ group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash|
154
+ hash[:parent_example_group]
155
+ end)
156
+ group_metadata.update(example_metadata)
157
+
158
+ example_metadata[:example_group] = group_metadata
159
+ example_metadata.delete(:parent_example_group)
160
+
161
+ hash = new(example_metadata, user_metadata, [description].compact, block)
162
+ hash.populate
163
+ hash.metadata
164
+ end
165
+
166
+ private
128
167
 
129
168
  def described_class
130
- self[:example_group].described_class
169
+ metadata[:example_group][:described_class]
131
170
  end
132
171
 
133
172
  def full_description
134
- build_description_from(self[:example_group][:full_description], *self[:description_args])
173
+ build_description_from(
174
+ metadata[:example_group][:full_description],
175
+ metadata[:description]
176
+ )
135
177
  end
136
178
  end
137
179
 
138
- # Mixed in to Metadata for an ExampleGroup (extends MetadataHash) to
139
- # support lazy evaluation of some values.
140
- module GroupMetadataHash
141
- include MetadataHash
180
+ # @private
181
+ class ExampleGroupHash < HashPopulator
182
+ def self.create(parent_group_metadata, user_metadata, *args, &block)
183
+ group_metadata = hash_with_backwards_compatibility_default_proc
184
+ group_metadata.update(parent_group_metadata)
185
+ group_metadata[:parent_example_group] = parent_group_metadata
186
+
187
+ hash = new(group_metadata, user_metadata, args, block)
188
+ hash.populate
189
+ hash.metadata
190
+ end
142
191
 
143
- def described_class
144
- container_stack.each do |g|
145
- [:described_class, :describes].each do |key|
146
- if g.has_key?(key)
147
- value = g[key]
148
- return value unless value.nil?
149
- end
192
+ def self.hash_with_backwards_compatibility_default_proc
193
+ Hash.new(&backwards_compatibility_default_proc { |hash| hash })
194
+ end
195
+
196
+ def self.backwards_compatibility_default_proc(&example_group_selector)
197
+ Proc.new do |hash, key|
198
+ case key
199
+ when :example_group
200
+ RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
201
+ :replacement => "the example group's hash directly for the " +
202
+ "computed keys and `:parent_example_group` to access the parent " +
203
+ "example group metadata")
204
+ LegacyExampleGroupHash.new(example_group_selector.call(hash))
205
+ when :example_group_block
206
+ RSpec.deprecate("`metadata[:example_group_block]`",
207
+ :replacement => "`metadata[:block]`")
208
+ hash[:block]
209
+ when :describes
210
+ RSpec.deprecate("`metadata[:describes]`",
211
+ :replacement => "`metadata[:described_class]`")
212
+ hash[:described_class]
150
213
  end
151
214
  end
215
+ end
152
216
 
153
- container_stack.reverse.each do |g|
154
- candidate = g[:description_args].first
155
- return candidate unless String === candidate || Symbol === candidate
156
- end
217
+ private
157
218
 
158
- nil
219
+ def described_class
220
+ candidate = metadata[:description_args].first
221
+ return candidate unless NilClass === candidate || String === candidate
222
+ parent_group = metadata[:parent_example_group]
223
+ parent_group && parent_group[:described_class]
159
224
  end
160
225
 
161
226
  def full_description
162
- build_description_from(*FlatMap.flat_map(container_stack.reverse) {|a| a[:description_args]})
163
- end
227
+ description = metadata[:description]
228
+ parent_example_group = metadata[:parent_example_group]
229
+ parent_description = parent_example_group[:full_description]
230
+
231
+ return description unless parent_description
164
232
 
165
- def container_stack
166
- @container_stack ||= begin
167
- groups = [group = self]
168
- while group.has_key?(:example_group)
169
- groups << group[:example_group]
170
- group = group[:example_group]
171
- end
172
- groups
173
- end
233
+ separator = description_separator(parent_example_group[:description_args].last,
234
+ metadata[:description_args].first)
235
+
236
+ parent_description + separator + description
174
237
  end
175
238
  end
176
239
 
177
- def initialize(parent_group_metadata=nil)
178
- if parent_group_metadata
179
- update(parent_group_metadata)
180
- store(:example_group, {:example_group => parent_group_metadata[:example_group].extend(GroupMetadataHash)}.extend(GroupMetadataHash))
181
- else
182
- store(:example_group, {}.extend(GroupMetadataHash))
183
- end
240
+ # @private
241
+ RESERVED_KEYS = [
242
+ :description,
243
+ :example_group,
244
+ :parent_example_group,
245
+ :execution_result,
246
+ :file_path,
247
+ :full_description,
248
+ :line_number,
249
+ :location,
250
+ :block
251
+ ]
252
+ end
184
253
 
185
- yield self if block_given?
254
+ # Mixin that makes the including class imitate a hash for backwards
255
+ # compatibility. The including class should use `attr_accessor` to
256
+ # declare attributes.
257
+ # @private
258
+ module HashImitatable
259
+ def self.included(klass)
260
+ klass.extend ClassMethods
186
261
  end
187
262
 
188
- # @private
189
- def process(*args)
190
- user_metadata = args.last.is_a?(Hash) ? args.pop : {}
191
- ensure_valid_keys(user_metadata)
263
+ def to_h
264
+ hash = extra_hash_attributes.dup
192
265
 
193
- self[:example_group].store(:description_args, args)
194
- self[:example_group].store(:caller, user_metadata.delete(:caller) || caller)
266
+ self.class.hash_attribute_names.each do |name|
267
+ hash[name] = __send__(name)
268
+ end
195
269
 
196
- update(user_metadata)
270
+ hash
197
271
  end
198
272
 
199
- # @private
200
- def for_example(description, user_metadata)
201
- dup.extend(ExampleMetadataHash).configure_for_example(description, user_metadata)
202
- end
273
+ (Hash.public_instance_methods - Object.public_instance_methods).each do |method_name|
274
+ next if [:[], :[]=, :to_h].include?(method_name.to_sym)
203
275
 
204
- # @private
205
- def any_apply?(filters)
206
- filters.any? {|k,v| filter_applies?(k,v)}
207
- end
276
+ define_method(method_name) do |*args, &block|
277
+ issue_deprecation(method_name, *args)
208
278
 
209
- # @private
210
- def all_apply?(filters)
211
- filters.all? {|k,v| filter_applies?(k,v)}
212
- end
279
+ hash = hash_for_delegation
280
+ self.class.hash_attribute_names.each do |name|
281
+ hash.delete(name) unless instance_variable_defined?(:"@#{name}")
282
+ end
213
283
 
214
- # @private
215
- def filter_applies?(key, value, metadata=self)
216
- return metadata.filter_applies_to_any_value?(key, value) if Array === metadata[key] && !(Proc === value)
217
- return metadata.line_number_filter_applies?(value) if key == :line_numbers
218
- return metadata.location_filter_applies?(value) if key == :locations
219
- return metadata.filters_apply?(key, value) if Hash === value
220
-
221
- return false unless metadata.has_key?(key)
222
-
223
- case value
224
- when Regexp
225
- metadata[key] =~ value
226
- when Proc
227
- case value.arity
228
- when 0 then value.call
229
- when 2 then value.call(metadata[key], metadata)
230
- else value.call(metadata[key])
284
+ hash.__send__(method_name, *args, &block).tap do
285
+ # apply mutations back to the object
286
+ hash.each do |name, value|
287
+ if directly_supports_attribute?(name)
288
+ set_value(name, value)
289
+ else
290
+ extra_hash_attributes[name] = value
291
+ end
292
+ end
231
293
  end
294
+ end
295
+ end
296
+
297
+ def [](key)
298
+ issue_deprecation(:[], key)
299
+
300
+ if directly_supports_attribute?(key)
301
+ get_value(key)
232
302
  else
233
- metadata[key].to_s == value.to_s
303
+ extra_hash_attributes[key]
234
304
  end
235
305
  end
236
306
 
237
- # @private
238
- def filters_apply?(key, value)
239
- value.all? {|k, v| filter_applies?(k, v, self[key])}
307
+ def []=(key, value)
308
+ issue_deprecation(:[]=, key, value)
309
+
310
+ if directly_supports_attribute?(key)
311
+ set_value(key, value)
312
+ else
313
+ extra_hash_attributes[key] = value
314
+ end
240
315
  end
241
316
 
242
- # @private
243
- def filter_applies_to_any_value?(key, value)
244
- self[key].any? {|v| filter_applies?(key, v, {key => value})}
317
+ private
318
+
319
+ def extra_hash_attributes
320
+ @extra_hash_attributes ||= {}
245
321
  end
246
322
 
247
- # @private
248
- def location_filter_applies?(locations)
249
- # it ignores location filters for other files
250
- line_number = example_group_declaration_line(locations)
251
- line_number ? line_number_filter_applies?(line_number) : true
323
+ def directly_supports_attribute?(name)
324
+ self.class.hash_attribute_names.include?(name)
252
325
  end
253
326
 
254
- # @private
255
- def line_number_filter_applies?(line_numbers)
256
- preceding_declaration_lines = line_numbers.map {|n| RSpec.world.preceding_declaration_line(n)}
257
- !(relevant_line_numbers & preceding_declaration_lines).empty?
327
+ def get_value(name)
328
+ __send__(name)
258
329
  end
259
330
 
260
- protected
331
+ def set_value(name, value)
332
+ __send__(:"#{name}=", value)
333
+ end
261
334
 
262
- def configure_for_example(description, user_metadata)
263
- store(:description_args, [description]) if description
264
- store(:caller, user_metadata.delete(:caller) || caller)
265
- update(user_metadata)
335
+ def hash_for_delegation
336
+ to_h
266
337
  end
267
338
 
268
- private
339
+ def issue_deprecation(method_name, *args)
340
+ # no-op by default: subclasses can override
341
+ end
269
342
 
270
- RESERVED_KEYS = [
271
- :description,
272
- :example_group,
273
- :execution_result,
274
- :file_path,
275
- :full_description,
276
- :line_number,
277
- :location
278
- ]
343
+ # @private
344
+ module ClassMethods
345
+ def hash_attribute_names
346
+ @hash_attribute_names ||= []
347
+ end
279
348
 
280
- def ensure_valid_keys(user_metadata)
281
- RESERVED_KEYS.each do |key|
282
- if user_metadata.has_key?(key)
283
- raise <<-EOM
284
- #{"*"*50}
285
- :#{key} is not allowed
349
+ def attr_accessor(*names)
350
+ hash_attribute_names.concat(names)
351
+ super
352
+ end
353
+ end
354
+ end
286
355
 
287
- RSpec reserves some hash keys for its own internal use,
288
- including :#{key}, which is used on:
356
+ # @private
357
+ # Together with the example group metadata hash default block,
358
+ # provides backwards compatibility for the old `:example_group`
359
+ # key. In RSpec 2.x, the computed keys of a group's metadata
360
+ # were exposed from a nested subhash keyed by `[:example_group]`, and
361
+ # then the parent group's metadata was exposed by sub-subhash
362
+ # keyed by `[:example_group][:example_group]`.
363
+ #
364
+ # In RSpec 3, we reorganized this to that the computed keys are
365
+ # exposed directly of the group metadata hash (no nesting), and
366
+ # `:parent_example_group` returns the parent group's metadata.
367
+ #
368
+ # Maintaining backwards compatibility was difficult: we wanted
369
+ # `:example_group` to return an object that:
370
+ #
371
+ # * Exposes the top-level metadata keys that used to be nested
372
+ # under `:example_group`.
373
+ # * Supports mutation (rspec-rails, for example, assigns
374
+ # `metadata[:example_group][:described_class]` when you use
375
+ # anonymous controller specs) such that changes are written
376
+ # back to the top-level metadata hash.
377
+ # * Exposes the parent group metadata as `[:example_group][:example_group]`.
378
+ class LegacyExampleGroupHash
379
+ include HashImitatable
380
+
381
+ def initialize(metadata)
382
+ @metadata = metadata
383
+ parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group]
384
+ self[:example_group] = parent_group_metadata if parent_group_metadata
385
+ end
289
386
 
290
- #{CallerFilter.first_non_rspec_line}.
387
+ def to_h
388
+ super.merge(@metadata)
389
+ end
291
390
 
292
- Here are all of RSpec's reserved hash keys:
391
+ private
293
392
 
294
- #{RESERVED_KEYS.join("\n ")}
295
- #{"*"*50}
296
- EOM
297
- end
298
- end
393
+ def directly_supports_attribute?(name)
394
+ name != :example_group
299
395
  end
300
396
 
301
- def example_group_declaration_line(locations)
302
- locations[File.expand_path(self[:example_group][:file_path])] if self[:example_group]
397
+ def get_value(name)
398
+ @metadata[name]
303
399
  end
304
400
 
305
- # TODO - make this a method on metadata - the problem is
306
- # metadata[:example_group] is not always a kind of GroupMetadataHash.
307
- def relevant_line_numbers(metadata=self)
308
- [metadata[:line_number]] + (metadata[:example_group] ? relevant_line_numbers(metadata[:example_group]) : [])
401
+ def set_value(name, value)
402
+ @metadata[name] = value
309
403
  end
310
-
311
404
  end
312
405
  end
313
406
  end