rspec-core 3.0.0.beta2 → 3.0.0.rc1

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 (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