rspec-core 2.99.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +1 -0
  4. data/Changelog.md +339 -1
  5. data/License.txt +1 -0
  6. data/README.md +19 -37
  7. data/exe/rspec +2 -23
  8. data/lib/rspec/core/backport_random.rb +336 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +70 -0
  10. data/lib/rspec/core/configuration.rb +479 -577
  11. data/lib/rspec/core/configuration_options.rb +71 -54
  12. data/lib/rspec/core/drb.rb +111 -0
  13. data/lib/rspec/core/dsl.rb +86 -18
  14. data/lib/rspec/core/example.rb +267 -119
  15. data/lib/rspec/core/example_group.rb +381 -393
  16. data/lib/rspec/core/filter_manager.rb +133 -114
  17. data/lib/rspec/core/flat_map.rb +18 -0
  18. data/lib/rspec/core/formatters/base_formatter.rb +51 -125
  19. data/lib/rspec/core/formatters/base_text_formatter.rb +34 -286
  20. data/lib/rspec/core/formatters/console_codes.rb +24 -1
  21. data/lib/rspec/core/formatters/deprecation_formatter.rb +25 -26
  22. data/lib/rspec/core/formatters/documentation_formatter.rb +27 -25
  23. data/lib/rspec/core/formatters/helpers.rb +14 -36
  24. data/lib/rspec/core/formatters/html_formatter.rb +58 -65
  25. data/lib/rspec/core/formatters/html_printer.rb +12 -18
  26. data/lib/rspec/core/formatters/json_formatter.rb +54 -31
  27. data/lib/rspec/core/formatters/profile_formatter.rb +67 -0
  28. data/lib/rspec/core/formatters/progress_formatter.rb +11 -15
  29. data/lib/rspec/core/formatters/snippet_extractor.rb +22 -11
  30. data/lib/rspec/core/formatters.rb +90 -43
  31. data/lib/rspec/core/hooks.rb +325 -270
  32. data/lib/rspec/core/memoized_helpers.rb +45 -156
  33. data/lib/rspec/core/metadata.rb +284 -236
  34. data/lib/rspec/core/metadata_filter.rb +86 -0
  35. data/lib/rspec/core/mocking_adapters/flexmock.rb +29 -0
  36. data/lib/rspec/core/{mocking/with_mocha.rb → mocking_adapters/mocha.rb} +19 -16
  37. data/lib/rspec/core/mocking_adapters/null.rb +12 -0
  38. data/lib/rspec/core/mocking_adapters/rr.rb +29 -0
  39. data/lib/rspec/core/mocking_adapters/rspec.rb +30 -0
  40. data/lib/rspec/core/notifications.rb +511 -0
  41. data/lib/rspec/core/option_parser.rb +49 -74
  42. data/lib/rspec/core/ordering.rb +156 -0
  43. data/lib/rspec/core/pending.rb +94 -96
  44. data/lib/rspec/core/project_initializer/.rspec +3 -0
  45. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +78 -0
  46. data/lib/rspec/core/project_initializer.rb +24 -64
  47. data/lib/rspec/core/rake_task.rb +44 -130
  48. data/lib/rspec/core/reporter.rb +76 -59
  49. data/lib/rspec/core/ruby_project.rb +2 -1
  50. data/lib/rspec/core/runner.rb +117 -52
  51. data/lib/rspec/core/shared_context.rb +10 -10
  52. data/lib/rspec/core/shared_example_group.rb +108 -121
  53. data/lib/rspec/core/version.rb +3 -1
  54. data/lib/rspec/core/warnings.rb +40 -0
  55. data/lib/rspec/core/world.rb +66 -10
  56. data/lib/rspec/core.rb +64 -104
  57. data.tar.gz.sig +3 -0
  58. metadata +75 -322
  59. metadata.gz.sig +1 -0
  60. data/exe/autospec +0 -13
  61. data/features/Autotest.md +0 -38
  62. data/features/README.md +0 -17
  63. data/features/Upgrade.md +0 -364
  64. data/features/command_line/README.md +0 -28
  65. data/features/command_line/example_name_option.feature +0 -97
  66. data/features/command_line/exit_status.feature +0 -82
  67. data/features/command_line/format_option.feature +0 -75
  68. data/features/command_line/init.feature +0 -18
  69. data/features/command_line/line_number_appended_to_path.feature +0 -140
  70. data/features/command_line/line_number_option.feature +0 -53
  71. data/features/command_line/order.feature +0 -26
  72. data/features/command_line/pattern_option.feature +0 -49
  73. data/features/command_line/rake_task.feature +0 -122
  74. data/features/command_line/require_option.feature +0 -43
  75. data/features/command_line/ruby.feature +0 -22
  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 -48
  79. data/features/configuration/backtrace_clean_patterns.feature +0 -102
  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/fail_fast.feature +0 -77
  84. data/features/configuration/failure_exit_code.feature +0 -36
  85. data/features/configuration/order_and_seed.feature +0 -3
  86. data/features/configuration/output_stream.feature +0 -24
  87. data/features/configuration/pattern.feature +0 -38
  88. data/features/configuration/profile.feature +0 -220
  89. data/features/configuration/read_options_from_file.feature +0 -90
  90. data/features/configuration/run_all_when_everything_filtered.feature +0 -76
  91. data/features/configuration/show_failures_in_pending_blocks.feature +0 -61
  92. data/features/configuration/treat_symbols_as_metadata_keys_with_true_values.feature +0 -52
  93. data/features/example_groups/basic_structure.feature +0 -55
  94. data/features/example_groups/shared_context.feature +0 -74
  95. data/features/example_groups/shared_examples.feature +0 -294
  96. data/features/expectation_framework_integration/configure_expectation_framework.feature +0 -197
  97. data/features/filtering/exclusion_filters.feature +0 -138
  98. data/features/filtering/if_and_unless.feature +0 -168
  99. data/features/filtering/inclusion_filters.feature +0 -105
  100. data/features/formatters/configurable_colors.feature +0 -31
  101. data/features/formatters/custom_formatter.feature +0 -36
  102. data/features/formatters/json_formatter.feature +0 -30
  103. data/features/formatters/text_formatter.feature +0 -46
  104. data/features/helper_methods/arbitrary_methods.feature +0 -40
  105. data/features/helper_methods/let.feature +0 -50
  106. data/features/helper_methods/modules.feature +0 -149
  107. data/features/hooks/around_hooks.feature +0 -343
  108. data/features/hooks/before_and_after_hooks.feature +0 -427
  109. data/features/hooks/filtering.feature +0 -234
  110. data/features/metadata/current_example.feature +0 -56
  111. data/features/metadata/described_class.feature +0 -17
  112. data/features/metadata/user_defined.feature +0 -113
  113. data/features/mock_framework_integration/use_any_framework.feature +0 -106
  114. data/features/mock_framework_integration/use_flexmock.feature +0 -96
  115. data/features/mock_framework_integration/use_mocha.feature +0 -97
  116. data/features/mock_framework_integration/use_rr.feature +0 -98
  117. data/features/mock_framework_integration/use_rspec.feature +0 -97
  118. data/features/pending/pending_examples.feature +0 -229
  119. data/features/spec_files/arbitrary_file_suffix.feature +0 -13
  120. data/features/step_definitions/additional_cli_steps.rb +0 -49
  121. data/features/subject/attribute_of_subject.feature +0 -124
  122. data/features/subject/explicit_subject.feature +0 -101
  123. data/features/subject/implicit_subject.feature +0 -63
  124. data/features/subject/one_liner_syntax.feature +0 -71
  125. data/features/support/env.rb +0 -14
  126. data/features/support/rubinius.rb +0 -6
  127. data/lib/autotest/discover.rb +0 -10
  128. data/lib/autotest/rspec2.rb +0 -77
  129. data/lib/rspec/core/backtrace_cleaner.rb +0 -46
  130. data/lib/rspec/core/backward_compatibility.rb +0 -55
  131. data/lib/rspec/core/caller_filter.rb +0 -60
  132. data/lib/rspec/core/command_line.rb +0 -47
  133. data/lib/rspec/core/deprecated_mutable_array_proxy.rb +0 -32
  134. data/lib/rspec/core/deprecation.rb +0 -26
  135. data/lib/rspec/core/drb_command_line.rb +0 -26
  136. data/lib/rspec/core/drb_options.rb +0 -87
  137. data/lib/rspec/core/extensions/instance_eval_with_args.rb +0 -44
  138. data/lib/rspec/core/extensions/kernel.rb +0 -9
  139. data/lib/rspec/core/extensions/module_eval_with_args.rb +0 -38
  140. data/lib/rspec/core/extensions/ordered.rb +0 -27
  141. data/lib/rspec/core/formatters/text_mate_formatter.rb +0 -34
  142. data/lib/rspec/core/metadata_hash_builder.rb +0 -97
  143. data/lib/rspec/core/mocking/with_absolutely_nothing.rb +0 -11
  144. data/lib/rspec/core/mocking/with_flexmock.rb +0 -27
  145. data/lib/rspec/core/mocking/with_rr.rb +0 -27
  146. data/lib/rspec/core/mocking/with_rspec.rb +0 -27
  147. data/lib/rspec/core/shared_example_group/collection.rb +0 -45
  148. data/spec/autotest/discover_spec.rb +0 -49
  149. data/spec/autotest/failed_results_re_spec.rb +0 -45
  150. data/spec/autotest/rspec_spec.rb +0 -133
  151. data/spec/command_line/order_spec.rb +0 -204
  152. data/spec/rspec/core/backtrace_cleaner_spec.rb +0 -68
  153. data/spec/rspec/core/caller_filter_spec.rb +0 -58
  154. data/spec/rspec/core/command_line_spec.rb +0 -127
  155. data/spec/rspec/core/command_line_spec_output.txt +0 -0
  156. data/spec/rspec/core/configuration_options_spec.rb +0 -417
  157. data/spec/rspec/core/configuration_spec.rb +0 -2072
  158. data/spec/rspec/core/deprecation_spec.rb +0 -27
  159. data/spec/rspec/core/deprecations_spec.rb +0 -59
  160. data/spec/rspec/core/drb_command_line_spec.rb +0 -102
  161. data/spec/rspec/core/drb_options_spec.rb +0 -193
  162. data/spec/rspec/core/dsl_spec.rb +0 -25
  163. data/spec/rspec/core/example_group_spec.rb +0 -1337
  164. data/spec/rspec/core/example_spec.rb +0 -502
  165. data/spec/rspec/core/filter_manager_spec.rb +0 -246
  166. data/spec/rspec/core/formatters/base_formatter_spec.rb +0 -108
  167. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +0 -524
  168. data/spec/rspec/core/formatters/console_codes_spec.rb +0 -50
  169. data/spec/rspec/core/formatters/deprecation_formatter_spec.rb +0 -263
  170. data/spec/rspec/core/formatters/documentation_formatter_spec.rb +0 -89
  171. data/spec/rspec/core/formatters/helpers_spec.rb +0 -104
  172. data/spec/rspec/core/formatters/html_formatted.html +0 -397
  173. data/spec/rspec/core/formatters/html_formatter_spec.rb +0 -131
  174. data/spec/rspec/core/formatters/json_formatter_spec.rb +0 -115
  175. data/spec/rspec/core/formatters/progress_formatter_spec.rb +0 -30
  176. data/spec/rspec/core/formatters/snippet_extractor_spec.rb +0 -26
  177. data/spec/rspec/core/formatters/text_mate_formatted.html +0 -397
  178. data/spec/rspec/core/formatters/text_mate_formatter_spec.rb +0 -107
  179. data/spec/rspec/core/hooks_filtering_spec.rb +0 -227
  180. data/spec/rspec/core/hooks_spec.rb +0 -269
  181. data/spec/rspec/core/kernel_extensions_spec.rb +0 -9
  182. data/spec/rspec/core/memoized_helpers_spec.rb +0 -776
  183. data/spec/rspec/core/metadata_spec.rb +0 -514
  184. data/spec/rspec/core/option_parser_spec.rb +0 -297
  185. data/spec/rspec/core/pending_example_spec.rb +0 -260
  186. data/spec/rspec/core/pending_spec.rb +0 -27
  187. data/spec/rspec/core/project_initializer_spec.rb +0 -130
  188. data/spec/rspec/core/rake_task_spec.rb +0 -202
  189. data/spec/rspec/core/reporter_spec.rb +0 -167
  190. data/spec/rspec/core/resources/a_bar.rb +0 -0
  191. data/spec/rspec/core/resources/a_foo.rb +0 -0
  192. data/spec/rspec/core/resources/a_spec.rb +0 -1
  193. data/spec/rspec/core/resources/custom_example_group_runner.rb +0 -14
  194. data/spec/rspec/core/resources/formatter_specs.rb +0 -60
  195. data/spec/rspec/core/resources/utf8_encoded.rb +0 -8
  196. data/spec/rspec/core/rspec_matchers_spec.rb +0 -45
  197. data/spec/rspec/core/ruby_project_spec.rb +0 -26
  198. data/spec/rspec/core/runner_spec.rb +0 -82
  199. data/spec/rspec/core/shared_context_spec.rb +0 -114
  200. data/spec/rspec/core/shared_example_group/collection_spec.rb +0 -70
  201. data/spec/rspec/core/shared_example_group_spec.rb +0 -138
  202. data/spec/rspec/core/world_spec.rb +0 -142
  203. data/spec/rspec/core_spec.rb +0 -108
  204. data/spec/spec_helper.rb +0 -135
  205. data/spec/support/config_options_helper.rb +0 -15
  206. data/spec/support/helper_methods.rb +0 -55
  207. data/spec/support/in_sub_process.rb +0 -37
  208. data/spec/support/isolate_load_path_mutation.rb +0 -6
  209. data/spec/support/isolated_directory.rb +0 -10
  210. data/spec/support/isolated_home_directory.rb +0 -16
  211. data/spec/support/matchers.rb +0 -65
  212. data/spec/support/mathn_integration_support.rb +0 -12
  213. data/spec/support/sandboxed_mock_space.rb +0 -116
  214. data/spec/support/shared_example_groups.rb +0 -42
  215. data/spec/support/silence_dsl_deprecations.rb +0 -32
  216. data/spec/support/spec_files.rb +0 -44
@@ -24,11 +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
- class << self
29
- attr_accessor :line_number_filter_deprecation_issued
30
- end
31
-
27
+ module Metadata
28
+ # @api private
29
+ #
30
+ # @param line [String] current code line
31
+ # @return [String] relative path to line
32
32
  def self.relative_path(line)
33
33
  line = line.sub(File.expand_path("."), ".")
34
34
  line = line.sub(/\A([^:]+:\d+)$/, '\\1')
@@ -39,323 +39,371 @@ module RSpec
39
39
  end
40
40
 
41
41
  # @private
42
- module MetadataHash
43
-
44
- # @private
45
- # Supports lazy evaluation of some values. Extended by
46
- # ExampleMetadataHash and GroupMetadataHash, which get mixed in to
47
- # Metadata for ExampleGroups and Examples (respectively).
48
- def [](key)
49
- store_computed(key) unless has_key?(key)
50
- super
42
+ # Used internally to build a hash from an args array.
43
+ # Symbols are converted into hash keys with a value of `true`.
44
+ # This is done to support simple tagging using a symbol, rather
45
+ # than needing to do `:symbol => true`.
46
+ def self.build_hash_from(args)
47
+ hash = args.last.is_a?(Hash) ? args.pop : {}
48
+
49
+ while args.last.is_a?(Symbol)
50
+ hash[args.pop] = true
51
51
  end
52
52
 
53
- def fetch(key, *args)
54
- store_computed(key) unless has_key?(key)
55
- super
53
+ hash
54
+ end
55
+
56
+ # @private
57
+ def self.backtrace_from(block)
58
+ return caller unless block.respond_to?(:source_location)
59
+ [block.source_location.join(':')]
60
+ end
61
+
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
56
73
  end
57
74
 
58
- private
59
-
60
- def store_computed(key)
61
- case key
62
- when :location
63
- store(:location, location)
64
- when :file_path, :line_number
65
- file_path, line_number = file_and_line_number
66
- store(:file_path, file_path)
67
- store(:line_number, line_number)
68
- when :execution_result
69
- store(:execution_result, {})
70
- when :describes, :described_class
71
- klass = described_class
72
- store(:described_class, klass)
73
- # TODO (2011-11-07 DC) deprecate :describes as a key
74
- store(:describes, klass)
75
- when :full_description
76
- store(:full_description, full_description)
77
- when :description
78
- store(:description, build_description_from(*self[:description_args]))
79
- when :description_args
80
- store(:description_args, [])
81
- end
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)
82
88
  end
83
89
 
84
- def location
85
- "#{self[:file_path]}:#{self[:line_number]}"
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)
99
+ end
100
+
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}"
86
105
  end
87
106
 
88
- def file_and_line_number
89
- first_caller_from_outside_rspec =~ /(.+?):(\d+)(|:\d+)/
90
- 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
91
111
  end
92
112
 
93
- def first_caller_from_outside_rspec
94
- 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
95
119
  end
96
120
 
97
- def method_description_after_module?(parent_part, child_part)
98
- return false unless parent_part.is_a?(Module)
99
- 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
100
125
  end
101
126
 
102
- def build_description_from(first_part = '', *parts)
103
- description, _ = parts.inject([first_part.to_s, first_part]) do |(desc, last_part), this_part|
104
- this_part = this_part.to_s
105
- this_part = (' ' + this_part) unless method_description_after_module?(last_part, this_part)
106
- [(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
107
145
  end
108
-
109
- description
110
146
  end
111
147
  end
112
148
 
113
- # Mixed in to Metadata for an Example (extends MetadataHash) to support
114
- # lazy evaluation of some values.
115
- module ExampleMetadataHash
116
- 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
117
167
 
118
168
  def described_class
119
- self[:example_group].described_class
169
+ metadata[:example_group][:described_class]
120
170
  end
121
171
 
122
172
  def full_description
123
- 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
+ )
124
177
  end
125
178
  end
126
179
 
127
- # Mixed in to Metadata for an ExampleGroup (extends MetadataHash) to
128
- # support lazy evaluation of some values.
129
- module GroupMetadataHash
130
- 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
131
184
 
132
- def described_class
133
- warn_about_first_description_arg_behavioral_change_in_rspec_3
134
-
135
- value_for_rspec_2 = described_class_for_rspec_2
136
- value_for_rspec_3 = described_class_for_rspec_3
137
-
138
- if value_for_rspec_2 != value_for_rspec_3
139
- RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''))
140
- |The semantics of `described_class` in a nested `describe <SomeClass>`
141
- |example group are changing in RSpec 3. In RSpec 2.x, `described_class`
142
- |would return the outermost described class (#{value_for_rspec_2.inspect}).
143
- |In RSpec 3, it will return the innermost described class (#{value_for_rspec_3.inspect}).
144
- |In general, we recommend not describing multiple classes or objects in a
145
- |nested manner as it creates confusion.
146
- |
147
- |To make your code compatible with RSpec 3, change from `described_class` to a reference
148
- |to `#{value_for_rspec_3.inspect}`, or change the arg of the inner `describe` to a string.
149
- |(Called from #{CallerFilter.first_non_rspec_line})
150
- EOS
185
+ if parent_group_metadata
186
+ group_metadata.update(parent_group_metadata)
187
+ group_metadata[:parent_example_group] = parent_group_metadata
151
188
  end
152
189
 
153
- value_for_rspec_2
190
+ hash = new(group_metadata, user_metadata, args, block)
191
+ hash.populate
192
+ hash.metadata
154
193
  end
155
194
 
156
- def described_class_for_rspec_2
157
- container_stack.each do |g|
158
- [:described_class, :describes].each do |key|
159
- if g.has_key?(key)
160
- value = g[key]
161
- return value unless value.nil?
162
- end
163
- end
164
- end
165
-
166
- container_stack.reverse.each do |g|
167
- candidate = g[:description_args].first
168
- return candidate unless String === candidate || Symbol === candidate
169
- end
170
-
171
- nil
195
+ def self.hash_with_backwards_compatibility_default_proc
196
+ Hash.new(&backwards_compatibility_default_proc { |hash| hash })
172
197
  end
173
198
 
174
- def described_class_for_rspec_3
175
- container_stack.each do |g|
176
- [:described_class, :describes].each do |key|
177
- if g.has_key?(key)
178
- value = g[key]
179
- return value unless value.nil?
180
- end
199
+ def self.backwards_compatibility_default_proc(&example_group_selector)
200
+ Proc.new do |hash, key|
201
+ case key
202
+ when :example_group
203
+ RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
204
+ :replacement => "the example group's hash directly for the " +
205
+ "computed keys and `:parent_example_group` to access the parent " +
206
+ "example group metadata")
207
+ group_hash = example_group_selector.call(hash)
208
+ LegacyExampleGroupHash.new(group_hash) if group_hash
209
+ when :example_group_block
210
+ RSpec.deprecate("`metadata[:example_group_block]`",
211
+ :replacement => "`metadata[:block]`")
212
+ hash[:block]
213
+ when :describes
214
+ RSpec.deprecate("`metadata[:describes]`",
215
+ :replacement => "`metadata[:described_class]`")
216
+ hash[:described_class]
181
217
  end
182
-
183
- candidate = g[:description_args].first
184
- return candidate unless NilClass === candidate || String === candidate || Symbol === candidate
185
218
  end
186
-
187
- nil
188
219
  end
189
220
 
190
- def warn_about_first_description_arg_behavioral_change_in_rspec_3
191
- return unless behavior_change = self[:description_arg_behavior_changing_in_rspec_3]
192
- RSpec.warn_deprecation(behavior_change.warning)
193
- end
221
+ private
194
222
 
195
- def first_description_arg
196
- self[:description_args].first
223
+ def described_class
224
+ candidate = metadata[:description_args].first
225
+ return candidate unless NilClass === candidate || String === candidate
226
+ parent_group = metadata[:parent_example_group]
227
+ parent_group && parent_group[:described_class]
197
228
  end
198
229
 
199
230
  def full_description
200
- build_description_from(*container_stack.reverse.map {|a| a[:description_args]}.flatten)
201
- end
231
+ description = metadata[:description]
232
+ parent_example_group = metadata[:parent_example_group]
233
+ return description unless parent_example_group
202
234
 
203
- def container_stack
204
- @container_stack ||= begin
205
- groups = [group = self]
206
- while group.has_key?(:example_group)
207
- groups << group[:example_group]
208
- group = group[:example_group]
209
- end
210
- groups
211
- end
212
- end
213
- end
235
+ parent_description = parent_example_group[:full_description]
236
+ separator = description_separator(parent_example_group[:description_args].last,
237
+ metadata[:description_args].first)
214
238
 
215
- def initialize(parent_group_metadata=nil)
216
- if parent_group_metadata
217
- update(parent_group_metadata)
218
- store(:example_group, {:example_group => parent_group_metadata[:example_group].extend(GroupMetadataHash)}.extend(GroupMetadataHash))
219
- else
220
- store(:example_group, {}.extend(GroupMetadataHash))
239
+ parent_description + separator + description
221
240
  end
222
-
223
- yield self if block_given?
224
241
  end
225
242
 
226
243
  # @private
227
- def process(*args)
228
- user_metadata = args.last.is_a?(Hash) ? args.pop : {}
229
- ensure_valid_keys(user_metadata)
230
-
231
- self[:example_group].store(:description_args, args)
232
- self[:example_group].store(:caller, user_metadata.delete(:caller) || caller)
233
- self[:example_group][:description_arg_behavior_changing_in_rspec_3] = user_metadata.delete(:description_arg_behavior_changing_in_rspec_3)
244
+ RESERVED_KEYS = [
245
+ :description,
246
+ :example_group,
247
+ :parent_example_group,
248
+ :execution_result,
249
+ :file_path,
250
+ :full_description,
251
+ :line_number,
252
+ :location,
253
+ :block
254
+ ]
255
+ end
234
256
 
235
- update(user_metadata)
257
+ # Mixin that makes the including class imitate a hash for backwards
258
+ # compatibility. The including class should use `attr_accessor` to
259
+ # declare attributes.
260
+ # @private
261
+ module HashImitatable
262
+ def self.included(klass)
263
+ klass.extend ClassMethods
236
264
  end
237
265
 
238
- # @private
239
- def for_example(description, user_metadata)
240
- dup.extend(ExampleMetadataHash).configure_for_example(description, user_metadata)
241
- end
266
+ def to_h
267
+ hash = extra_hash_attributes.dup
242
268
 
243
- # @private
244
- def any_apply?(filters)
245
- filters.any? {|k,v| filter_applies?(k,v)}
246
- end
269
+ self.class.hash_attribute_names.each do |name|
270
+ hash[name] = __send__(name)
271
+ end
247
272
 
248
- # @private
249
- def all_apply?(filters)
250
- filters.all? {|k,v| filter_applies?(k,v)}
273
+ hash
251
274
  end
252
275
 
253
- # @private
254
- def filter_applies?(key, value, metadata=self)
255
- return metadata.filter_applies_to_any_value?(key, value) if Array === metadata[key] && !(Proc === value)
276
+ (Hash.public_instance_methods - Object.public_instance_methods).each do |method_name|
277
+ next if [:[], :[]=, :to_h].include?(method_name.to_sym)
256
278
 
257
- if key == :line_numbers
258
- unless Metadata.line_number_filter_deprecation_issued
259
- RSpec.deprecate("Filtering by `:line_numbers`",
260
- :replacement => "filtering by `:locations`")
279
+ define_method(method_name) do |*args, &block|
280
+ issue_deprecation(method_name, *args)
281
+
282
+ hash = hash_for_delegation
283
+ self.class.hash_attribute_names.each do |name|
284
+ hash.delete(name) unless instance_variable_defined?(:"@#{name}")
261
285
  end
262
286
 
263
- return metadata.line_number_filter_applies?(value)
287
+ hash.__send__(method_name, *args, &block).tap do
288
+ # apply mutations back to the object
289
+ hash.each do |name, value|
290
+ if directly_supports_attribute?(name)
291
+ set_value(name, value)
292
+ else
293
+ extra_hash_attributes[name] = value
294
+ end
295
+ end
296
+ end
264
297
  end
298
+ end
265
299
 
266
- return metadata.location_filter_applies?(value) if key == :locations
267
- return metadata.filters_apply?(key, value) if Hash === value
268
-
269
- return false unless metadata.has_key?(key)
300
+ def [](key)
301
+ issue_deprecation(:[], key)
270
302
 
271
- case value
272
- when Regexp
273
- metadata[key] =~ value
274
- when Proc
275
- case value.arity
276
- when 0 then value.call
277
- when 2 then value.call(metadata[key], metadata)
278
- else value.call(metadata[key])
279
- end
303
+ if directly_supports_attribute?(key)
304
+ get_value(key)
280
305
  else
281
- metadata[key].to_s == value.to_s
306
+ extra_hash_attributes[key]
282
307
  end
283
308
  end
284
309
 
285
- # @private
286
- def filters_apply?(key, value)
287
- value.all? {|k, v| filter_applies?(k, v, self[key])}
310
+ def []=(key, value)
311
+ issue_deprecation(:[]=, key, value)
312
+
313
+ if directly_supports_attribute?(key)
314
+ set_value(key, value)
315
+ else
316
+ extra_hash_attributes[key] = value
317
+ end
288
318
  end
289
319
 
290
- # @private
291
- def filter_applies_to_any_value?(key, value)
292
- self[key].any? {|v| filter_applies?(key, v, {key => value})}
320
+ private
321
+
322
+ def extra_hash_attributes
323
+ @extra_hash_attributes ||= {}
293
324
  end
294
325
 
295
- # @private
296
- def location_filter_applies?(locations)
297
- # it ignores location filters for other files
298
- line_number = example_group_declaration_line(locations)
299
- line_number ? line_number_filter_applies?(line_number) : true
326
+ def directly_supports_attribute?(name)
327
+ self.class.hash_attribute_names.include?(name)
300
328
  end
301
329
 
302
- # @private
303
- def line_number_filter_applies?(line_numbers)
304
- preceding_declaration_lines = line_numbers.map {|n| RSpec.world.preceding_declaration_line(n)}
305
- !(relevant_line_numbers & preceding_declaration_lines).empty?
330
+ def get_value(name)
331
+ __send__(name)
306
332
  end
307
333
 
308
- protected
334
+ def set_value(name, value)
335
+ __send__(:"#{name}=", value)
336
+ end
309
337
 
310
- def configure_for_example(description, user_metadata)
311
- store(:description_args, [description]) if description
312
- store(:caller, user_metadata.delete(:caller) || caller)
313
- update(user_metadata)
338
+ def hash_for_delegation
339
+ to_h
314
340
  end
315
341
 
316
- private
342
+ def issue_deprecation(method_name, *args)
343
+ # no-op by default: subclasses can override
344
+ end
317
345
 
318
- RESERVED_KEYS = [
319
- :description,
320
- :example_group,
321
- :execution_result,
322
- :file_path,
323
- :full_description,
324
- :line_number,
325
- :location
326
- ]
346
+ # @private
347
+ module ClassMethods
348
+ def hash_attribute_names
349
+ @hash_attribute_names ||= []
350
+ end
327
351
 
328
- def ensure_valid_keys(user_metadata)
329
- RESERVED_KEYS.each do |key|
330
- if user_metadata.has_key?(key)
331
- raise <<-EOM
332
- #{"*"*50}
333
- :#{key} is not allowed
352
+ def attr_accessor(*names)
353
+ hash_attribute_names.concat(names)
354
+ super
355
+ end
356
+ end
357
+ end
334
358
 
335
- RSpec reserves some hash keys for its own internal use,
336
- including :#{key}, which is used on:
359
+ # @private
360
+ # Together with the example group metadata hash default block,
361
+ # provides backwards compatibility for the old `:example_group`
362
+ # key. In RSpec 2.x, the computed keys of a group's metadata
363
+ # were exposed from a nested subhash keyed by `[:example_group]`, and
364
+ # then the parent group's metadata was exposed by sub-subhash
365
+ # keyed by `[:example_group][:example_group]`.
366
+ #
367
+ # In RSpec 3, we reorganized this to that the computed keys are
368
+ # exposed directly of the group metadata hash (no nesting), and
369
+ # `:parent_example_group` returns the parent group's metadata.
370
+ #
371
+ # Maintaining backwards compatibility was difficult: we wanted
372
+ # `:example_group` to return an object that:
373
+ #
374
+ # * Exposes the top-level metadata keys that used to be nested
375
+ # under `:example_group`.
376
+ # * Supports mutation (rspec-rails, for example, assigns
377
+ # `metadata[:example_group][:described_class]` when you use
378
+ # anonymous controller specs) such that changes are written
379
+ # back to the top-level metadata hash.
380
+ # * Exposes the parent group metadata as `[:example_group][:example_group]`.
381
+ class LegacyExampleGroupHash
382
+ include HashImitatable
383
+
384
+ def initialize(metadata)
385
+ @metadata = metadata
386
+ parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group]
387
+ self[:example_group] = parent_group_metadata if parent_group_metadata
388
+ end
337
389
 
338
- #{CallerFilter.first_non_rspec_line}.
390
+ def to_h
391
+ super.merge(@metadata)
392
+ end
339
393
 
340
- Here are all of RSpec's reserved hash keys:
394
+ private
341
395
 
342
- #{RESERVED_KEYS.join("\n ")}
343
- #{"*"*50}
344
- EOM
345
- end
346
- end
396
+ def directly_supports_attribute?(name)
397
+ name != :example_group
347
398
  end
348
399
 
349
- def example_group_declaration_line(locations)
350
- locations[File.expand_path(self[:example_group][:file_path])] if self[:example_group]
400
+ def get_value(name)
401
+ @metadata[name]
351
402
  end
352
403
 
353
- # TODO - make this a method on metadata - the problem is
354
- # metadata[:example_group] is not always a kind of GroupMetadataHash.
355
- def relevant_line_numbers(metadata=self)
356
- [metadata[:line_number]] + (metadata[:example_group] ? relevant_line_numbers(metadata[:example_group]) : [])
404
+ def set_value(name, value)
405
+ @metadata[name] = value
357
406
  end
358
-
359
407
  end
360
408
  end
361
409
  end