rspec-core 2.11.1 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (222) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +3 -1
  5. data/Changelog.md +1814 -29
  6. data/{License.txt → LICENSE.md} +6 -4
  7. data/README.md +197 -48
  8. data/exe/rspec +2 -23
  9. data/lib/rspec/autorun.rb +1 -0
  10. data/lib/rspec/core/backtrace_formatter.rb +65 -0
  11. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  12. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  13. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  14. data/lib/rspec/core/bisect/server.rb +61 -0
  15. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  16. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  17. data/lib/rspec/core/bisect/utilities.rb +69 -0
  18. data/lib/rspec/core/configuration.rb +1846 -407
  19. data/lib/rspec/core/configuration_options.rb +154 -50
  20. data/lib/rspec/core/did_you_mean.rb +46 -0
  21. data/lib/rspec/core/drb.rb +120 -0
  22. data/lib/rspec/core/dsl.rb +90 -18
  23. data/lib/rspec/core/example.rb +488 -152
  24. data/lib/rspec/core/example_group.rb +733 -294
  25. data/lib/rspec/core/example_status_persister.rb +235 -0
  26. data/lib/rspec/core/filter_manager.rb +175 -147
  27. data/lib/rspec/core/flat_map.rb +20 -0
  28. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  29. data/lib/rspec/core/formatters/base_formatter.rb +32 -130
  30. data/lib/rspec/core/formatters/base_text_formatter.rb +62 -190
  31. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  32. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  33. data/lib/rspec/core/formatters/console_codes.rb +76 -0
  34. data/lib/rspec/core/formatters/deprecation_formatter.rb +223 -0
  35. data/lib/rspec/core/formatters/documentation_formatter.rb +62 -27
  36. data/lib/rspec/core/formatters/exception_presenter.rb +521 -0
  37. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  38. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  39. data/lib/rspec/core/formatters/helpers.rb +93 -14
  40. data/lib/rspec/core/formatters/html_formatter.rb +104 -415
  41. data/lib/rspec/core/formatters/html_printer.rb +414 -0
  42. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  43. data/lib/rspec/core/formatters/json_formatter.rb +102 -0
  44. data/lib/rspec/core/formatters/profile_formatter.rb +68 -0
  45. data/lib/rspec/core/formatters/progress_formatter.rb +12 -15
  46. data/lib/rspec/core/formatters/protocol.rb +182 -0
  47. data/lib/rspec/core/formatters/snippet_extractor.rb +115 -39
  48. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  49. data/lib/rspec/core/formatters.rb +279 -0
  50. data/lib/rspec/core/hooks.rb +451 -300
  51. data/lib/rspec/core/invocations.rb +87 -0
  52. data/lib/rspec/core/memoized_helpers.rb +580 -0
  53. data/lib/rspec/core/metadata.rb +395 -173
  54. data/lib/rspec/core/metadata_filter.rb +255 -0
  55. data/lib/rspec/core/minitest_assertions_adapter.rb +31 -0
  56. data/lib/rspec/core/mocking_adapters/flexmock.rb +31 -0
  57. data/lib/rspec/core/mocking_adapters/mocha.rb +57 -0
  58. data/lib/rspec/core/mocking_adapters/null.rb +14 -0
  59. data/lib/rspec/core/mocking_adapters/rr.rb +31 -0
  60. data/lib/rspec/core/mocking_adapters/rspec.rb +32 -0
  61. data/lib/rspec/core/notifications.rb +521 -0
  62. data/lib/rspec/core/option_parser.rb +208 -64
  63. data/lib/rspec/core/ordering.rb +169 -0
  64. data/lib/rspec/core/output_wrapper.rb +29 -0
  65. data/lib/rspec/core/pending.rb +115 -59
  66. data/lib/rspec/core/profiler.rb +34 -0
  67. data/lib/rspec/core/project_initializer/.rspec +1 -0
  68. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +98 -0
  69. data/lib/rspec/core/project_initializer.rb +26 -65
  70. data/lib/rspec/core/rake_task.rb +140 -131
  71. data/lib/rspec/core/reporter.rb +207 -44
  72. data/lib/rspec/core/ruby_project.rb +15 -6
  73. data/lib/rspec/core/runner.rb +180 -44
  74. data/lib/rspec/core/sandbox.rb +37 -0
  75. data/lib/rspec/core/set.rb +54 -0
  76. data/lib/rspec/core/shared_context.rb +25 -19
  77. data/lib/rspec/core/shared_example_group.rb +229 -54
  78. data/lib/rspec/core/shell_escape.rb +49 -0
  79. data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
  80. data/lib/rspec/core/version.rb +3 -1
  81. data/lib/rspec/core/warnings.rb +40 -0
  82. data/lib/rspec/core/world.rb +208 -49
  83. data/lib/rspec/core.rb +166 -80
  84. data.tar.gz.sig +0 -0
  85. metadata +230 -445
  86. metadata.gz.sig +0 -0
  87. data/exe/autospec +0 -13
  88. data/features/Autotest.md +0 -38
  89. data/features/README.md +0 -17
  90. data/features/Upgrade.md +0 -364
  91. data/features/command_line/README.md +0 -28
  92. data/features/command_line/example_name_option.feature +0 -101
  93. data/features/command_line/exit_status.feature +0 -83
  94. data/features/command_line/format_option.feature +0 -81
  95. data/features/command_line/init.feature +0 -18
  96. data/features/command_line/line_number_appended_to_path.feature +0 -140
  97. data/features/command_line/line_number_option.feature +0 -58
  98. data/features/command_line/order.feature +0 -29
  99. data/features/command_line/pattern_option.feature +0 -31
  100. data/features/command_line/rake_task.feature +0 -68
  101. data/features/command_line/ruby.feature +0 -22
  102. data/features/command_line/tag.feature +0 -91
  103. data/features/configuration/alias_example_to.feature +0 -48
  104. data/features/configuration/custom_settings.feature +0 -84
  105. data/features/configuration/default_path.feature +0 -38
  106. data/features/configuration/fail_fast.feature +0 -77
  107. data/features/configuration/read_options_from_file.feature +0 -87
  108. data/features/example_groups/basic_structure.feature +0 -55
  109. data/features/example_groups/shared_context.feature +0 -74
  110. data/features/example_groups/shared_examples.feature +0 -204
  111. data/features/expectation_framework_integration/configure_expectation_framework.feature +0 -102
  112. data/features/filtering/exclusion_filters.feature +0 -139
  113. data/features/filtering/if_and_unless.feature +0 -168
  114. data/features/filtering/inclusion_filters.feature +0 -105
  115. data/features/filtering/run_all_when_everything_filtered.feature +0 -46
  116. data/features/formatters/custom_formatter.feature +0 -36
  117. data/features/formatters/text_formatter.feature +0 -46
  118. data/features/helper_methods/arbitrary_methods.feature +0 -40
  119. data/features/helper_methods/let.feature +0 -50
  120. data/features/helper_methods/modules.feature +0 -149
  121. data/features/hooks/around_hooks.feature +0 -343
  122. data/features/hooks/before_and_after_hooks.feature +0 -423
  123. data/features/hooks/filtering.feature +0 -234
  124. data/features/metadata/current_example.feature +0 -17
  125. data/features/metadata/described_class.feature +0 -17
  126. data/features/metadata/user_defined.feature +0 -113
  127. data/features/mock_framework_integration/use_any_framework.feature +0 -106
  128. data/features/mock_framework_integration/use_flexmock.feature +0 -96
  129. data/features/mock_framework_integration/use_mocha.feature +0 -97
  130. data/features/mock_framework_integration/use_rr.feature +0 -98
  131. data/features/mock_framework_integration/use_rspec.feature +0 -97
  132. data/features/pending/pending_examples.feature +0 -229
  133. data/features/spec_files/arbitrary_file_suffix.feature +0 -13
  134. data/features/step_definitions/additional_cli_steps.rb +0 -30
  135. data/features/subject/attribute_of_subject.feature +0 -124
  136. data/features/subject/explicit_subject.feature +0 -82
  137. data/features/subject/implicit_receiver.feature +0 -29
  138. data/features/subject/implicit_subject.feature +0 -63
  139. data/features/support/env.rb +0 -12
  140. data/lib/autotest/discover.rb +0 -1
  141. data/lib/autotest/rspec2.rb +0 -73
  142. data/lib/rspec/core/backward_compatibility.rb +0 -65
  143. data/lib/rspec/core/command_line.rb +0 -36
  144. data/lib/rspec/core/deprecation.rb +0 -36
  145. data/lib/rspec/core/drb_command_line.rb +0 -26
  146. data/lib/rspec/core/drb_options.rb +0 -87
  147. data/lib/rspec/core/extensions/instance_eval_with_args.rb +0 -44
  148. data/lib/rspec/core/extensions/kernel.rb +0 -9
  149. data/lib/rspec/core/extensions/module_eval_with_args.rb +0 -38
  150. data/lib/rspec/core/extensions/ordered.rb +0 -21
  151. data/lib/rspec/core/extensions.rb +0 -4
  152. data/lib/rspec/core/formatters/text_mate_formatter.rb +0 -34
  153. data/lib/rspec/core/let.rb +0 -110
  154. data/lib/rspec/core/load_path.rb +0 -3
  155. data/lib/rspec/core/metadata_hash_builder.rb +0 -97
  156. data/lib/rspec/core/mocking/with_absolutely_nothing.rb +0 -11
  157. data/lib/rspec/core/mocking/with_flexmock.rb +0 -27
  158. data/lib/rspec/core/mocking/with_mocha.rb +0 -29
  159. data/lib/rspec/core/mocking/with_rr.rb +0 -27
  160. data/lib/rspec/core/mocking/with_rspec.rb +0 -23
  161. data/lib/rspec/core/subject.rb +0 -219
  162. data/spec/autotest/discover_spec.rb +0 -19
  163. data/spec/autotest/failed_results_re_spec.rb +0 -45
  164. data/spec/autotest/rspec_spec.rb +0 -123
  165. data/spec/command_line/order_spec.rb +0 -137
  166. data/spec/rspec/core/command_line_spec.rb +0 -108
  167. data/spec/rspec/core/command_line_spec_output.txt +0 -0
  168. data/spec/rspec/core/configuration_options_spec.rb +0 -377
  169. data/spec/rspec/core/configuration_spec.rb +0 -1196
  170. data/spec/rspec/core/deprecations_spec.rb +0 -66
  171. data/spec/rspec/core/drb_command_line_spec.rb +0 -108
  172. data/spec/rspec/core/drb_options_spec.rb +0 -180
  173. data/spec/rspec/core/dsl_spec.rb +0 -17
  174. data/spec/rspec/core/example_group_spec.rb +0 -1098
  175. data/spec/rspec/core/example_spec.rb +0 -370
  176. data/spec/rspec/core/filter_manager_spec.rb +0 -256
  177. data/spec/rspec/core/formatters/base_formatter_spec.rb +0 -80
  178. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +0 -363
  179. data/spec/rspec/core/formatters/documentation_formatter_spec.rb +0 -88
  180. data/spec/rspec/core/formatters/helpers_spec.rb +0 -66
  181. data/spec/rspec/core/formatters/html_formatted-1.8.7-jruby.html +0 -410
  182. data/spec/rspec/core/formatters/html_formatted-1.8.7.html +0 -409
  183. data/spec/rspec/core/formatters/html_formatted-1.9.2.html +0 -416
  184. data/spec/rspec/core/formatters/html_formatted-1.9.3.html +0 -416
  185. data/spec/rspec/core/formatters/html_formatter_spec.rb +0 -82
  186. data/spec/rspec/core/formatters/progress_formatter_spec.rb +0 -30
  187. data/spec/rspec/core/formatters/snippet_extractor_spec.rb +0 -18
  188. data/spec/rspec/core/formatters/text_mate_formatted-1.8.7-jruby.html +0 -410
  189. data/spec/rspec/core/formatters/text_mate_formatted-1.8.7.html +0 -409
  190. data/spec/rspec/core/formatters/text_mate_formatted-1.9.2.html +0 -416
  191. data/spec/rspec/core/formatters/text_mate_formatted-1.9.3.html +0 -416
  192. data/spec/rspec/core/formatters/text_mate_formatter_spec.rb +0 -83
  193. data/spec/rspec/core/hooks_filtering_spec.rb +0 -227
  194. data/spec/rspec/core/hooks_spec.rb +0 -250
  195. data/spec/rspec/core/kernel_extensions_spec.rb +0 -9
  196. data/spec/rspec/core/let_spec.rb +0 -55
  197. data/spec/rspec/core/metadata_spec.rb +0 -447
  198. data/spec/rspec/core/option_parser_spec.rb +0 -166
  199. data/spec/rspec/core/pending_example_spec.rb +0 -220
  200. data/spec/rspec/core/project_initializer_spec.rb +0 -130
  201. data/spec/rspec/core/rake_task_spec.rb +0 -138
  202. data/spec/rspec/core/reporter_spec.rb +0 -103
  203. data/spec/rspec/core/resources/a_bar.rb +0 -0
  204. data/spec/rspec/core/resources/a_foo.rb +0 -0
  205. data/spec/rspec/core/resources/a_spec.rb +0 -1
  206. data/spec/rspec/core/resources/custom_example_group_runner.rb +0 -14
  207. data/spec/rspec/core/resources/formatter_specs.rb +0 -60
  208. data/spec/rspec/core/resources/utf8_encoded.rb +0 -8
  209. data/spec/rspec/core/rspec_matchers_spec.rb +0 -45
  210. data/spec/rspec/core/ruby_project_spec.rb +0 -24
  211. data/spec/rspec/core/runner_spec.rb +0 -81
  212. data/spec/rspec/core/shared_context_spec.rb +0 -67
  213. data/spec/rspec/core/shared_example_group_spec.rb +0 -84
  214. data/spec/rspec/core/subject_spec.rb +0 -244
  215. data/spec/rspec/core/world_spec.rb +0 -144
  216. data/spec/rspec/core_spec.rb +0 -35
  217. data/spec/spec_helper.rb +0 -98
  218. data/spec/support/config_options_helper.rb +0 -24
  219. data/spec/support/helper_methods.rb +0 -5
  220. data/spec/support/matchers.rb +0 -65
  221. data/spec/support/shared_example_groups.rb +0 -41
  222. data/spec/support/spec_files.rb +0 -44
@@ -7,7 +7,7 @@ module RSpec
7
7
  # In addition to metadata that is used internally, this also stores
8
8
  # user-supplied metadata, e.g.
9
9
  #
10
- # describe Something, :type => :ui do
10
+ # RSpec.describe Something, :type => :ui do
11
11
  # it "does something", :slow => true do
12
12
  # # ...
13
13
  # end
@@ -24,253 +24,475 @@ module RSpec
24
24
  # @see FilterManager
25
25
  # @see Configuration#filter_run_including
26
26
  # @see Configuration#filter_run_excluding
27
- class Metadata < Hash
27
+ module Metadata
28
+ # Matches strings either at the beginning of the input or prefixed with a
29
+ # whitespace, containing the current path, either postfixed with the
30
+ # separator, or at the end of the string. Match groups are the character
31
+ # before and the character after the string if any.
32
+ #
33
+ # http://rubular.com/r/fT0gmX6VJX
34
+ # http://rubular.com/r/duOrD4i3wb
35
+ # http://rubular.com/r/sbAMHFrOx1
36
+ def self.relative_path_regex
37
+ @relative_path_regex ||= /(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/
38
+ end
28
39
 
40
+ # @api private
41
+ #
42
+ # @param line [String] current code line
43
+ # @return [String] relative path to line
29
44
  def self.relative_path(line)
30
- line = line.sub(File.expand_path("."), ".")
31
- line = line.sub(/\A([^:]+:\d+)$/, '\\1')
32
- return nil if line == '-e:1'
45
+ line = line.sub(relative_path_regex, "\\1.\\2".freeze)
46
+ line = line.sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
47
+ return nil if line == '-e:1'.freeze
33
48
  line
49
+ rescue SecurityError
50
+ # :nocov:
51
+ nil
52
+ # :nocov:
34
53
  end
35
54
 
36
55
  # @private
37
- module MetadataHash
38
-
39
- # @private
40
- # Supports lazy evaluation of some values. Extended by
41
- # ExampleMetadataHash and GroupMetadataHash, which get mixed in to
42
- # Metadata for ExampleGroups and Examples (respectively).
43
- def [](key)
44
- return super if has_key?(key)
45
- case key
46
- when :location
47
- store(:location, location)
48
- when :file_path, :line_number
49
- file_path, line_number = file_and_line_number
50
- store(:file_path, file_path)
51
- store(:line_number, line_number)
52
- super
53
- when :execution_result
54
- store(:execution_result, {})
55
- when :describes, :described_class
56
- klass = described_class
57
- store(:described_class, klass)
58
- # TODO (2011-11-07 DC) deprecate :describes as a key
59
- store(:describes, klass)
60
- when :full_description
61
- store(:full_description, full_description)
62
- when :description
63
- store(:description, build_description_from(*self[:description_args]))
64
- else
65
- super
66
- end
56
+ # Iteratively walks up from the given metadata through all
57
+ # example group ancestors, yielding each metadata hash along the way.
58
+ def self.ascending(metadata)
59
+ yield metadata
60
+ return unless (group_metadata = metadata.fetch(:example_group) { metadata[:parent_example_group] })
61
+
62
+ loop do
63
+ yield group_metadata
64
+ break unless (group_metadata = group_metadata[:parent_example_group])
67
65
  end
66
+ end
68
67
 
69
- private
68
+ # @private
69
+ # Returns an enumerator that iteratively walks up the given metadata through all
70
+ # example group ancestors, yielding each metadata hash along the way.
71
+ def self.ascend(metadata)
72
+ enum_for(:ascending, metadata)
73
+ end
70
74
 
71
- def location
72
- "#{self[:file_path]}:#{self[:line_number]}"
75
+ # @private
76
+ # Used internally to build a hash from an args array.
77
+ # Symbols are converted into hash keys with a value of `true`.
78
+ # This is done to support simple tagging using a symbol, rather
79
+ # than needing to do `:symbol => true`.
80
+ def self.build_hash_from(args, warn_about_example_group_filtering=false)
81
+ hash = args.last.is_a?(Hash) ? args.pop : {}
82
+
83
+ hash[args.pop] = true while args.last.is_a?(Symbol)
84
+
85
+ if warn_about_example_group_filtering && hash.key?(:example_group)
86
+ RSpec.deprecate("Filtering by an `:example_group` subhash",
87
+ :replacement => "the subhash to filter directly")
73
88
  end
74
89
 
75
- def file_and_line_number
76
- first_caller_from_outside_rspec =~ /(.+?):(\d+)(|:\d+)/
77
- return [Metadata::relative_path($1), $2.to_i]
90
+ hash
91
+ end
92
+
93
+ # @private
94
+ def self.deep_hash_dup(object)
95
+ return object.dup if Array === object
96
+ return object unless Hash === object
97
+
98
+ object.inject(object.dup) do |duplicate, (key, value)|
99
+ duplicate[key] = deep_hash_dup(value)
100
+ duplicate
78
101
  end
102
+ end
103
+
104
+ # @private
105
+ def self.id_from(metadata)
106
+ "#{metadata[:rerun_file_path]}[#{metadata[:scoped_id]}]"
107
+ end
79
108
 
80
- def first_caller_from_outside_rspec
81
- self[:caller].detect {|l| l !~ /\/lib\/rspec\/core/}
109
+ # @private
110
+ def self.location_tuple_from(metadata)
111
+ [metadata[:absolute_file_path], metadata[:line_number]]
112
+ end
113
+
114
+ # @private
115
+ # Used internally to populate metadata hashes with computed keys
116
+ # managed by RSpec.
117
+ class HashPopulator
118
+ attr_reader :metadata, :user_metadata, :description_args, :block
119
+
120
+ def initialize(metadata, user_metadata, index_provider, description_args, block)
121
+ @metadata = metadata
122
+ @user_metadata = user_metadata
123
+ @index_provider = index_provider
124
+ @description_args = description_args
125
+ @block = block
126
+ end
127
+
128
+ def populate
129
+ ensure_valid_user_keys
130
+
131
+ metadata[:block] = block
132
+ metadata[:description_args] = description_args
133
+ metadata[:description] = build_description_from(*metadata[:description_args])
134
+ metadata[:full_description] = full_description
135
+ metadata[:described_class] = described_class
136
+
137
+ populate_location_attributes
138
+ metadata.update(user_metadata)
82
139
  end
83
140
 
84
- def build_description_from(*parts)
85
- parts.map {|p| p.to_s}.inject do |desc, p|
86
- p =~ /^(#|::|\.)/ ? "#{desc}#{p}" : "#{desc} #{p}"
87
- end || ""
141
+ private
142
+
143
+ def populate_location_attributes
144
+ backtrace = user_metadata.delete(:caller)
145
+
146
+ file_path, line_number = if backtrace
147
+ file_path_and_line_number_from(backtrace)
148
+ elsif block.respond_to?(:source_location)
149
+ block.source_location
150
+ else
151
+ file_path_and_line_number_from(caller)
152
+ end
153
+
154
+ relative_file_path = Metadata.relative_path(file_path)
155
+ absolute_file_path = File.expand_path(relative_file_path)
156
+ metadata[:file_path] = relative_file_path
157
+ metadata[:line_number] = line_number.to_i
158
+ metadata[:location] = "#{relative_file_path}:#{line_number}"
159
+ metadata[:absolute_file_path] = absolute_file_path
160
+ metadata[:rerun_file_path] ||= relative_file_path
161
+ metadata[:scoped_id] = build_scoped_id_for(absolute_file_path)
162
+ end
163
+
164
+ def file_path_and_line_number_from(backtrace)
165
+ first_caller_from_outside_rspec = backtrace.find { |l| l !~ CallerFilter::LIB_REGEX }
166
+ first_caller_from_outside_rspec ||= backtrace.first
167
+ /(.+?):(\d+)(?:|:\d+)/.match(first_caller_from_outside_rspec).captures
168
+ end
169
+
170
+ def description_separator(parent_part, child_part)
171
+ if parent_part.is_a?(Module) && /^(?:#|::|\.)/.match(child_part.to_s)
172
+ ''.freeze
173
+ else
174
+ ' '.freeze
175
+ end
176
+ end
177
+
178
+ def build_description_from(parent_description=nil, my_description=nil)
179
+ return parent_description.to_s unless my_description
180
+ return my_description.to_s if parent_description.to_s == ''
181
+ separator = description_separator(parent_description, my_description)
182
+ (parent_description.to_s + separator) << my_description.to_s
183
+ end
184
+
185
+ def build_scoped_id_for(file_path)
186
+ index = @index_provider.call(file_path).to_s
187
+ parent_scoped_id = metadata.fetch(:scoped_id) { return index }
188
+ "#{parent_scoped_id}:#{index}"
189
+ end
190
+
191
+ def ensure_valid_user_keys
192
+ RESERVED_KEYS.each do |key|
193
+ next unless user_metadata.key?(key)
194
+ raise <<-EOM.gsub(/^\s+\|/, '')
195
+ |#{"*" * 50}
196
+ |:#{key} is not allowed
197
+ |
198
+ |RSpec reserves some hash keys for its own internal use,
199
+ |including :#{key}, which is used on:
200
+ |
201
+ | #{CallerFilter.first_non_rspec_line}.
202
+ |
203
+ |Here are all of RSpec's reserved hash keys:
204
+ |
205
+ | #{RESERVED_KEYS.join("\n ")}
206
+ |#{"*" * 50}
207
+ EOM
208
+ end
88
209
  end
89
210
  end
90
211
 
91
- # Mixed in to Metadata for an Example (extends MetadataHash) to support
92
- # lazy evaluation of some values.
93
- module ExampleMetadataHash
94
- include MetadataHash
212
+ # @private
213
+ class ExampleHash < HashPopulator
214
+ def self.create(group_metadata, user_metadata, index_provider, description, block)
215
+ example_metadata = group_metadata.dup
216
+ group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash|
217
+ hash[:parent_example_group]
218
+ end)
219
+ group_metadata.update(example_metadata)
220
+
221
+ example_metadata[:execution_result] = Example::ExecutionResult.new
222
+ example_metadata[:example_group] = group_metadata
223
+ example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace
224
+ example_metadata.delete(:parent_example_group)
225
+
226
+ description_args = description.nil? ? [] : [description]
227
+ hash = new(example_metadata, user_metadata, index_provider, description_args, block)
228
+ hash.populate
229
+ hash.metadata
230
+ end
231
+
232
+ private
95
233
 
96
234
  def described_class
97
- self[:example_group].described_class
235
+ metadata[:example_group][:described_class]
98
236
  end
99
237
 
100
238
  def full_description
101
- build_description_from(self[:example_group][:full_description], *self[:description_args])
239
+ build_description_from(
240
+ metadata[:example_group][:full_description],
241
+ metadata[:description]
242
+ )
102
243
  end
103
244
  end
104
245
 
105
- # Mixed in to Metadata for an ExampleGroup (extends MetadataHash) to
106
- # support lazy evaluation of some values.
107
- module GroupMetadataHash
108
- include MetadataHash
246
+ # @private
247
+ class ExampleGroupHash < HashPopulator
248
+ def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block)
249
+ group_metadata = hash_with_backwards_compatibility_default_proc
109
250
 
110
- def described_class
111
- container_stack.each do |g|
112
- return g[:described_class] if g.has_key?(:described_class)
113
- return g[:describes] if g.has_key?(:describes)
251
+ if parent_group_metadata
252
+ group_metadata.update(parent_group_metadata)
253
+ group_metadata[:parent_example_group] = parent_group_metadata
114
254
  end
115
255
 
116
- container_stack.reverse.each do |g|
117
- candidate = g[:description_args].first
118
- return candidate unless String === candidate || Symbol === candidate
256
+ hash = new(group_metadata, user_metadata, example_group_index, args, block)
257
+ hash.populate
258
+ hash.metadata
259
+ end
260
+
261
+ def self.hash_with_backwards_compatibility_default_proc
262
+ Hash.new(&backwards_compatibility_default_proc { |hash| hash })
263
+ end
264
+
265
+ def self.backwards_compatibility_default_proc(&example_group_selector)
266
+ Proc.new do |hash, key|
267
+ case key
268
+ when :example_group
269
+ # We commonly get here when rspec-core is applying a previously
270
+ # configured filter rule, such as when a gem configures:
271
+ #
272
+ # RSpec.configure do |c|
273
+ # c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ }
274
+ # end
275
+ #
276
+ # It's confusing for a user to get a deprecation at this point in
277
+ # the code, so instead we issue a deprecation from the config APIs
278
+ # that take a metadata hash, and MetadataFilter sets this thread
279
+ # local to silence the warning here since it would be so
280
+ # confusing.
281
+ unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations]
282
+ RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
283
+ :replacement => "the example group's hash directly for the " \
284
+ "computed keys and `:parent_example_group` to access the parent " \
285
+ "example group metadata")
286
+ end
287
+
288
+ group_hash = example_group_selector.call(hash)
289
+ LegacyExampleGroupHash.new(group_hash) if group_hash
290
+ when :example_group_block
291
+ RSpec.deprecate("`metadata[:example_group_block]`",
292
+ :replacement => "`metadata[:block]`")
293
+ hash[:block]
294
+ when :describes
295
+ RSpec.deprecate("`metadata[:describes]`",
296
+ :replacement => "`metadata[:described_class]`")
297
+ hash[:described_class]
298
+ end
119
299
  end
300
+ end
120
301
 
121
- nil
302
+ private
303
+
304
+ def described_class
305
+ candidate = metadata[:description_args].first
306
+ return candidate unless NilClass === candidate || String === candidate
307
+ parent_group = metadata[:parent_example_group]
308
+ parent_group && parent_group[:described_class]
122
309
  end
123
310
 
124
311
  def full_description
125
- build_description_from(*container_stack.reverse.map {|a| a[:description_args]}.flatten)
126
- end
312
+ description = metadata[:description]
313
+ parent_example_group = metadata[:parent_example_group]
314
+ return description unless parent_example_group
127
315
 
128
- def container_stack
129
- @container_stack ||= begin
130
- groups = [group = self]
131
- while group.has_key?(:example_group)
132
- groups << group[:example_group]
133
- group = group[:example_group]
134
- end
135
- groups
136
- end
316
+ parent_description = parent_example_group[:full_description]
317
+ separator = description_separator(parent_example_group[:description_args].last,
318
+ metadata[:description_args].first)
319
+
320
+ parent_description + separator + description
137
321
  end
138
322
  end
139
323
 
140
- def initialize(parent_group_metadata=nil)
141
- if parent_group_metadata
142
- update(parent_group_metadata)
143
- store(:example_group, {:example_group => parent_group_metadata[:example_group].extend(GroupMetadataHash)}.extend(GroupMetadataHash))
144
- else
145
- store(:example_group, {}.extend(GroupMetadataHash))
146
- end
324
+ # @private
325
+ RESERVED_KEYS = [
326
+ :description,
327
+ :description_args,
328
+ :described_class,
329
+ :example_group,
330
+ :parent_example_group,
331
+ :execution_result,
332
+ :last_run_status,
333
+ :file_path,
334
+ :absolute_file_path,
335
+ :rerun_file_path,
336
+ :full_description,
337
+ :line_number,
338
+ :location,
339
+ :scoped_id,
340
+ :block,
341
+ :shared_group_inclusion_backtrace
342
+ ]
343
+ end
147
344
 
148
- yield self if block_given?
345
+ # Mixin that makes the including class imitate a hash for backwards
346
+ # compatibility. The including class should use `attr_accessor` to
347
+ # declare attributes.
348
+ # @private
349
+ module HashImitatable
350
+ def self.included(klass)
351
+ klass.extend ClassMethods
149
352
  end
150
353
 
151
- # @private
152
- def process(*args)
153
- user_metadata = args.last.is_a?(Hash) ? args.pop : {}
154
- ensure_valid_keys(user_metadata)
354
+ def to_h
355
+ hash = extra_hash_attributes.dup
155
356
 
156
- self[:example_group].store(:description_args, args)
157
- self[:example_group].store(:caller, user_metadata.delete(:caller) || caller)
357
+ self.class.hash_attribute_names.each do |name|
358
+ hash[name] = __send__(name)
359
+ end
158
360
 
159
- update(user_metadata)
361
+ hash
160
362
  end
161
363
 
162
- # @private
163
- def for_example(description, user_metadata)
164
- dup.extend(ExampleMetadataHash).configure_for_example(description, user_metadata)
165
- end
364
+ (Hash.public_instance_methods - Object.public_instance_methods).each do |method_name|
365
+ next if [:[], :[]=, :to_h].include?(method_name.to_sym)
166
366
 
167
- # @private
168
- def any_apply?(filters)
169
- filters.any? {|k,v| filter_applies?(k,v)}
170
- end
367
+ define_method(method_name) do |*args, &block|
368
+ issue_deprecation(method_name, *args)
171
369
 
172
- # @private
173
- def all_apply?(filters)
174
- filters.all? {|k,v| filter_applies?(k,v)}
175
- end
370
+ hash = hash_for_delegation
371
+ self.class.hash_attribute_names.each do |name|
372
+ hash.delete(name) unless instance_variable_defined?(:"@#{name}")
373
+ end
176
374
 
177
- # @private
178
- def filter_applies?(key, value, metadata=self)
179
- return metadata.filter_applies_to_any_value?(key, value) if Array === metadata[key] && !(Proc === value)
180
- return metadata.line_number_filter_applies?(value) if key == :line_numbers
181
- return metadata.location_filter_applies?(value) if key == :locations
182
- return metadata.filters_apply?(key, value) if Hash === value
183
-
184
- return false unless metadata.has_key?(key)
185
-
186
- case value
187
- when Regexp
188
- metadata[key] =~ value
189
- when Proc
190
- case value.arity
191
- when 0 then value.call
192
- when 2 then value.call(metadata[key], metadata)
193
- else value.call(metadata[key])
375
+ hash.__send__(method_name, *args, &block).tap do
376
+ # apply mutations back to the object
377
+ hash.each do |name, value|
378
+ if directly_supports_attribute?(name)
379
+ set_value(name, value)
380
+ else
381
+ extra_hash_attributes[name] = value
382
+ end
383
+ end
194
384
  end
385
+ end
386
+ end
387
+
388
+ def [](key)
389
+ issue_deprecation(:[], key)
390
+
391
+ if directly_supports_attribute?(key)
392
+ get_value(key)
195
393
  else
196
- metadata[key].to_s == value.to_s
394
+ extra_hash_attributes[key]
197
395
  end
198
396
  end
199
397
 
200
- # @private
201
- def filters_apply?(key, value)
202
- value.all? {|k, v| filter_applies?(k, v, self[key])}
398
+ def []=(key, value)
399
+ issue_deprecation(:[]=, key, value)
400
+
401
+ if directly_supports_attribute?(key)
402
+ set_value(key, value)
403
+ else
404
+ extra_hash_attributes[key] = value
405
+ end
203
406
  end
204
407
 
205
- # @private
206
- def filter_applies_to_any_value?(key, value)
207
- self[key].any? {|v| filter_applies?(key, v, {key => value})}
408
+ private
409
+
410
+ def extra_hash_attributes
411
+ @extra_hash_attributes ||= {}
208
412
  end
209
413
 
210
- # @private
211
- def location_filter_applies?(locations)
212
- # it ignores location filters for other files
213
- line_number = example_group_declaration_line(locations)
214
- line_number ? line_number_filter_applies?(line_number) : true
414
+ def directly_supports_attribute?(name)
415
+ self.class.hash_attribute_names.include?(name)
215
416
  end
216
417
 
217
- # @private
218
- def line_number_filter_applies?(line_numbers)
219
- preceding_declaration_lines = line_numbers.map {|n| RSpec.world.preceding_declaration_line(n)}
220
- !(relevant_line_numbers & preceding_declaration_lines).empty?
418
+ def get_value(name)
419
+ __send__(name)
221
420
  end
222
421
 
223
- protected
422
+ def set_value(name, value)
423
+ __send__(:"#{name}=", value)
424
+ end
224
425
 
225
- def configure_for_example(description, user_metadata)
226
- store(:description_args, [description])
227
- store(:caller, user_metadata.delete(:caller) || caller)
228
- update(user_metadata)
426
+ def hash_for_delegation
427
+ to_h
229
428
  end
230
429
 
231
- private
430
+ def issue_deprecation(_method_name, *_args)
431
+ # no-op by default: subclasses can override
432
+ end
232
433
 
233
- RESERVED_KEYS = [
234
- :description,
235
- :example_group,
236
- :execution_result,
237
- :file_path,
238
- :full_description,
239
- :line_number,
240
- :location
241
- ]
434
+ # @private
435
+ module ClassMethods
436
+ def hash_attribute_names
437
+ @hash_attribute_names ||= []
438
+ end
242
439
 
243
- def ensure_valid_keys(user_metadata)
244
- RESERVED_KEYS.each do |key|
245
- if user_metadata.has_key?(key)
246
- raise <<-EOM
247
- #{"*"*50}
248
- :#{key} is not allowed
440
+ def attr_accessor(*names)
441
+ hash_attribute_names.concat(names)
442
+ super
443
+ end
444
+ end
445
+ end
249
446
 
250
- RSpec reserves some hash keys for its own internal use,
251
- including :#{key}, which is used on:
447
+ # @private
448
+ # Together with the example group metadata hash default block,
449
+ # provides backwards compatibility for the old `:example_group`
450
+ # key. In RSpec 2.x, the computed keys of a group's metadata
451
+ # were exposed from a nested subhash keyed by `[:example_group]`, and
452
+ # then the parent group's metadata was exposed by sub-subhash
453
+ # keyed by `[:example_group][:example_group]`.
454
+ #
455
+ # In RSpec 3, we reorganized this to that the computed keys are
456
+ # exposed directly of the group metadata hash (no nesting), and
457
+ # `:parent_example_group` returns the parent group's metadata.
458
+ #
459
+ # Maintaining backwards compatibility was difficult: we wanted
460
+ # `:example_group` to return an object that:
461
+ #
462
+ # * Exposes the top-level metadata keys that used to be nested
463
+ # under `:example_group`.
464
+ # * Supports mutation (rspec-rails, for example, assigns
465
+ # `metadata[:example_group][:described_class]` when you use
466
+ # anonymous controller specs) such that changes are written
467
+ # back to the top-level metadata hash.
468
+ # * Exposes the parent group metadata as
469
+ # `[:example_group][:example_group]`.
470
+ class LegacyExampleGroupHash
471
+ include HashImitatable
472
+
473
+ def initialize(metadata)
474
+ @metadata = metadata
475
+ parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group]
476
+ self[:example_group] = parent_group_metadata if parent_group_metadata
477
+ end
252
478
 
253
- #{caller(0)[4]}.
479
+ def to_h
480
+ super.merge(@metadata)
481
+ end
254
482
 
255
- Here are all of RSpec's reserved hash keys:
483
+ private
256
484
 
257
- #{RESERVED_KEYS.join("\n ")}
258
- #{"*"*50}
259
- EOM
260
- end
261
- end
485
+ def directly_supports_attribute?(name)
486
+ name != :example_group
262
487
  end
263
488
 
264
- def example_group_declaration_line(locations)
265
- locations[File.expand_path(self[:example_group][:file_path])] if self[:example_group]
489
+ def get_value(name)
490
+ @metadata[name]
266
491
  end
267
492
 
268
- # TODO - make this a method on metadata - the problem is
269
- # metadata[:example_group] is not always a kind of GroupMetadataHash.
270
- def relevant_line_numbers(metadata=self)
271
- [metadata[:line_number]] + (metadata[:example_group] ? relevant_line_numbers(metadata[:example_group]) : [])
493
+ def set_value(name, value)
494
+ @metadata[name] = value
272
495
  end
273
-
274
496
  end
275
497
  end
276
498
  end