diff --git a/Changelog.md b/Changelog.md index ccc67fe2c6..63876dfe01 100644 --- a/Changelog.md +++ b/Changelog.md @@ -13,6 +13,8 @@ Breaking Changes: * Raise on usage of metadata on suite-level scopes. (Phil Pirozhkov, #2849) * Raise an error when `fail_fast` is configured with an unsupported value. (Phil Pirozhkov, #2849) +* Remove deprecated access to an example group's metadata through the example. + (Phil Pirozhkov, #2851) Enhancements: diff --git a/benchmarks/module_inclusion_filtering.rb b/benchmarks/module_inclusion_filtering.rb index eaeb5f01c6..8bd71109c9 100644 --- a/benchmarks/module_inclusion_filtering.rb +++ b/benchmarks/module_inclusion_filtering.rb @@ -14,7 +14,7 @@ def initialize(*args) end def include(mod, *filters) - meta = RSpec::Core::Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + meta = RSpec::Core::Metadata.build_hash_from(filters) @include_extend_or_prepend_modules << [:include, mod, meta] super end diff --git a/lib/rspec/core/configuration.rb b/lib/rspec/core/configuration.rb index 51feccadb0..d4478780bb 100644 --- a/lib/rspec/core/configuration.rb +++ b/lib/rspec/core/configuration.rb @@ -1163,7 +1163,7 @@ def alias_it_behaves_like_to(new_name, report_label='') # # filter_run_including :foo # same as filter_run_including :foo => true def filter_run_including(*args) - meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + meta = Metadata.build_hash_from(args) filter_manager.include_with_low_priority meta static_config_filter_manager.include_with_low_priority Metadata.deep_hash_dup(meta) end @@ -1192,7 +1192,7 @@ def filter_run_when_matching(*args) # This overrides any inclusion filters/tags set on the command line or in # configuration files. def inclusion_filter=(filter) - meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering) + meta = Metadata.build_hash_from([filter]) filter_manager.include_only meta end @@ -1237,7 +1237,7 @@ def inclusion_filter # # filter_run_excluding :foo # same as filter_run_excluding :foo => true def filter_run_excluding(*args) - meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + meta = Metadata.build_hash_from(args) filter_manager.exclude_with_low_priority meta static_config_filter_manager.exclude_with_low_priority Metadata.deep_hash_dup(meta) end @@ -1250,7 +1250,7 @@ def filter_run_excluding(*args) # This overrides any exclusion filters/tags set on the command line or in # configuration files. def exclusion_filter=(filter) - meta = Metadata.build_hash_from([filter], :warn_about_example_group_filtering) + meta = Metadata.build_hash_from([filter]) filter_manager.exclude_only meta end @@ -1732,7 +1732,7 @@ def raise_errors_for_deprecations! # end # end def define_derived_metadata(*filters, &block) - meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + meta = Metadata.build_hash_from(filters) @derived_metadata_blocks.append(block, meta) end @@ -1755,7 +1755,7 @@ def define_derived_metadata(*filters, &block) # end # end def when_first_matching_example_defined(*filters) - specified_meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + specified_meta = Metadata.build_hash_from(filters) callback = lambda do |example_or_group_meta| # Example groups do not have `:example_group` metadata @@ -2194,7 +2194,7 @@ def define_mixed_in_module(mod, filters, mod_list, config_method, &block) raise TypeError, "`RSpec.configuration.#{config_method}` expects a module but got: #{mod.inspect}" end - meta = Metadata.build_hash_from(filters, :warn_about_example_group_filtering) + meta = Metadata.build_hash_from(filters) mod_list.append(mod, meta) on_existing_matching_groups(meta, &block) end diff --git a/lib/rspec/core/hooks.rb b/lib/rspec/core/hooks.rb index 461c48e165..f8bad18f51 100644 --- a/lib/rspec/core/hooks.rb +++ b/lib/rspec/core/hooks.rb @@ -571,7 +571,7 @@ def process(host, parent_groups, globals, position, scope) def scope_and_options_from(*args) return :suite if args.first == :suite scope = extract_scope_from(args) - meta = Metadata.build_hash_from(args, :warn_about_example_group_filtering) + meta = Metadata.build_hash_from(args) return scope, meta end diff --git a/lib/rspec/core/metadata.rb b/lib/rspec/core/metadata.rb index 5e0d7e2bdb..7ff8f6c561 100644 --- a/lib/rspec/core/metadata.rb +++ b/lib/rspec/core/metadata.rb @@ -77,16 +77,11 @@ def self.ascend(metadata) # Symbols are converted into hash keys with a value of `true`. # This is done to support simple tagging using a symbol, rather # than needing to do `:symbol => true`. - def self.build_hash_from(args, warn_about_example_group_filtering=false) + def self.build_hash_from(args) hash = args.last.is_a?(Hash) ? args.pop : {} hash[args.pop] = true while args.last.is_a?(Symbol) - if warn_about_example_group_filtering && hash.key?(:example_group) - RSpec.deprecate("Filtering by an `:example_group` subhash", - :replacement => "the subhash to filter directly") - end - hash end @@ -213,11 +208,6 @@ def ensure_valid_user_keys class ExampleHash < HashPopulator def self.create(group_metadata, user_metadata, index_provider, description, block) example_metadata = group_metadata.dup - group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash| - hash[:parent_example_group] - end) - group_metadata.update(example_metadata) - example_metadata[:execution_result] = Example::ExecutionResult.new example_metadata[:example_group] = group_metadata example_metadata[:shared_group_inclusion_backtrace] = SharedExampleGroupInclusionStackFrame.current_backtrace @@ -246,59 +236,18 @@ def full_description # @private class ExampleGroupHash < HashPopulator def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block) - group_metadata = hash_with_backwards_compatibility_default_proc - - if parent_group_metadata - group_metadata.update(parent_group_metadata) - group_metadata[:parent_example_group] = parent_group_metadata - end + group_metadata = + if parent_group_metadata + { **parent_group_metadata, :parent_example_group => parent_group_metadata } + else + {} + end hash = new(group_metadata, user_metadata, example_group_index, args, block) hash.populate hash.metadata end - def self.hash_with_backwards_compatibility_default_proc - Hash.new(&backwards_compatibility_default_proc { |hash| hash }) - end - - def self.backwards_compatibility_default_proc(&example_group_selector) - Proc.new do |hash, key| - case key - when :example_group - # We commonly get here when rspec-core is applying a previously - # configured filter rule, such as when a gem configures: - # - # RSpec.configure do |c| - # c.include MyGemHelpers, :example_group => { :file_path => /spec\/my_gem_specs/ } - # end - # - # It's confusing for a user to get a deprecation at this point in - # the code, so instead we issue a deprecation from the config APIs - # that take a metadata hash, and MetadataFilter sets this thread - # local to silence the warning here since it would be so - # confusing. - unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] - RSpec.deprecate("The `:example_group` key in an example group's metadata hash", - :replacement => "the example group's hash directly for the " \ - "computed keys and `:parent_example_group` to access the parent " \ - "example group metadata") - end - - group_hash = example_group_selector.call(hash) - LegacyExampleGroupHash.new(group_hash) if group_hash - when :example_group_block - RSpec.deprecate("`metadata[:example_group_block]`", - :replacement => "`metadata[:block]`") - hash[:block] - when :describes - RSpec.deprecate("`metadata[:describes]`", - :replacement => "`metadata[:described_class]`") - hash[:described_class] - end - end - end - private def described_class @@ -443,56 +392,5 @@ def attr_accessor(*names) end end end - - # @private - # Together with the example group metadata hash default block, - # provides backwards compatibility for the old `:example_group` - # key. In RSpec 2.x, the computed keys of a group's metadata - # were exposed from a nested subhash keyed by `[:example_group]`, and - # then the parent group's metadata was exposed by sub-subhash - # keyed by `[:example_group][:example_group]`. - # - # In RSpec 3, we reorganized this to that the computed keys are - # exposed directly of the group metadata hash (no nesting), and - # `:parent_example_group` returns the parent group's metadata. - # - # Maintaining backwards compatibility was difficult: we wanted - # `:example_group` to return an object that: - # - # * Exposes the top-level metadata keys that used to be nested - # under `:example_group`. - # * Supports mutation (rspec-rails, for example, assigns - # `metadata[:example_group][:described_class]` when you use - # anonymous controller specs) such that changes are written - # back to the top-level metadata hash. - # * Exposes the parent group metadata as - # `[:example_group][:example_group]`. - class LegacyExampleGroupHash - include HashImitatable - - def initialize(metadata) - @metadata = metadata - parent_group_metadata = metadata.fetch(:parent_example_group) { {} }[:example_group] - self[:example_group] = parent_group_metadata if parent_group_metadata - end - - def to_h - super.merge(@metadata) - end - - private - - def directly_supports_attribute?(name) - name != :example_group - end - - def get_value(name) - @metadata[name] - end - - def set_value(name, value) - @metadata[name] = value - end - end end end diff --git a/lib/rspec/core/metadata_filter.rb b/lib/rspec/core/metadata_filter.rb index dfb0a05d1d..423e2c0c31 100644 --- a/lib/rspec/core/metadata_filter.rb +++ b/lib/rspec/core/metadata_filter.rb @@ -14,27 +14,17 @@ def apply?(predicate, filters, metadata) # @private def filter_applies?(key, filter_value, metadata) - silence_metadata_example_group_deprecations do - return location_filter_applies?(filter_value, metadata) if key == :locations - return id_filter_applies?(filter_value, metadata) if key == :ids - return filters_apply?(key, filter_value, metadata) if Hash === filter_value + return location_filter_applies?(filter_value, metadata) if key == :locations + return id_filter_applies?(filter_value, metadata) if key == :ids + return filters_apply?(key, filter_value, metadata) if Hash === filter_value - meta_value = metadata.fetch(key) { return false } + meta_value = metadata.fetch(key) { return false } - return true if TrueClass === filter_value && meta_value - return proc_filter_applies?(key, filter_value, metadata) if Proc === filter_value - return filter_applies_to_any_value?(key, filter_value, metadata) if Array === meta_value + return true if TrueClass === filter_value && meta_value + return proc_filter_applies?(key, filter_value, metadata) if Proc === filter_value + return filter_applies_to_any_value?(key, filter_value, metadata) if Array === meta_value - filter_value === meta_value || filter_value.to_s == meta_value.to_s - end - end - - # @private - def silence_metadata_example_group_deprecations - RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] = true - yield - ensure - RSpec::Support.thread_local_data.delete(:silence_metadata_example_group_deprecations) + filter_value === meta_value || filter_value.to_s == meta_value.to_s end private @@ -72,7 +62,7 @@ def proc_filter_applies?(key, proc, metadata) def filters_apply?(key, value, metadata) subhash = metadata[key] - return false unless Hash === subhash || HashImitatable === subhash + return false unless Hash === subhash value.all? { |k, v| filter_applies?(k, v, subhash) } end end @@ -201,21 +191,10 @@ def handle_mutation(metadata) @memoized_lookups.clear end + # Ruby 2.3 and 2.4 do not have `Hash#slice` def applicable_metadata_from(metadata) - MetadataFilter.silence_metadata_example_group_deprecations do - @applicable_keys.inject({}) do |hash, key| - # :example_group is treated special here because... - # - In RSpec 2, example groups had an `:example_group` key - # - In RSpec 3, that key is deprecated (it was confusing!). - # - The key is not technically present in an example group metadata hash - # (and thus would fail the `metadata.key?(key)` check) but a value - # is provided when accessed via the hash's `default_proc` - # - Thus, for backwards compatibility, we have to explicitly check - # for `:example_group` here if it is one of the keys being used to - # filter. - hash[key] = metadata[key] if metadata.key?(key) || key == :example_group - hash - end + @applicable_keys.each_with_object({}) do |key, hash| + hash[key] = metadata[key] if metadata.key?(key) end end diff --git a/spec/rspec/core/configuration_spec.rb b/spec/rspec/core/configuration_spec.rb index 77d641fd22..f98c10e972 100644 --- a/spec/rspec/core/configuration_spec.rb +++ b/spec/rspec/core/configuration_spec.rb @@ -11,14 +11,6 @@ module RSpec::Core before { config.world = RSpec.world } - shared_examples_for "warning of deprecated `:example_group` during filtering configuration" do |method, *args| - it "issues a deprecation warning when filtering by `:example_group`" do - args << { :example_group => { :file_location => /spec\/unit/ } } - expect_deprecation_with_call_site(__FILE__, __LINE__ + 1, /:example_group/) - config.__send__(method, *args) - end - end - describe '#on_example_group_definition' do before do RSpec.configure do |c| @@ -1043,8 +1035,6 @@ def file_at(relative_path) end describe "#include" do - include_examples "warning of deprecated `:example_group` during filtering configuration", :include, Enumerable - module InstanceLevelMethods def you_call_this_a_blt? "egad man, where's the mayo?!?!?" @@ -1092,16 +1082,6 @@ def metadata_hash(*args) expect(group.new.you_call_this_a_blt?).to eq("egad man, where's the mayo?!?!?") end - it "includes in example groups that match a deprecated `:example_group` filter" do - RSpec.configure do |c| - c.include(InstanceLevelMethods, :example_group => { :file_path => /./ }) - end - - group = RSpec.describe('does like, stuff and junk') - expect(group).not_to respond_to(:you_call_this_a_blt?) - expect(group.new.you_call_this_a_blt?).to eq("egad man, where's the mayo?!?!?") - end - it "includes the given module into each existing matching example group" do matching_group = RSpec.describe('does like, stuff and junk', :magic_key => :include) { } non_matching_group = RSpec.describe @@ -1187,8 +1167,6 @@ def self.included(klass) end describe "#extend" do - include_examples "warning of deprecated `:example_group` during filtering configuration", :extend, Enumerable - module ThatThingISentYou def that_thing end @@ -1226,8 +1204,6 @@ def metadata_hash(*args) end describe "#prepend" do - include_examples "warning of deprecated `:example_group` during filtering configuration", :prepend, Enumerable - module SomeRandomMod def foo "foobar" @@ -1672,8 +1648,6 @@ def metadata_hash(*args) end end - include_examples "warning of deprecated `:example_group` during filtering configuration", :filter_run_including - it "sets the filter with a hash" do config.filter_run_including :foo => true expect(inclusion_filter).to eq( {:foo => true} ) @@ -1699,8 +1673,6 @@ def metadata_hash(*args) end end - include_examples "warning of deprecated `:example_group` during filtering configuration", :filter_run_excluding - it "sets the filter" do config.filter_run_excluding :foo => true expect(exclusion_filter).to eq( {:foo => true} ) @@ -1737,8 +1709,6 @@ def metadata_hash(*args) config.send("#{type}=", {:want => :this}) expect(send(type)).to eq( {:want => :this} ) end - - include_examples "warning of deprecated `:example_group` during filtering configuration", :"#{type}=" end end it_behaves_like "a spec filter", :inclusion_filter @@ -1848,8 +1818,6 @@ def exclude?(line) end describe "#define_derived_metadata" do - include_examples "warning of deprecated `:example_group` during filtering configuration", :define_derived_metadata - it 'allows the provided block to mutate example group metadata' do RSpec.configuration.define_derived_metadata do |metadata| metadata[:reverse_description] = metadata[:description].reverse @@ -2031,8 +1999,6 @@ def exclude?(line) end describe "#when_first_matching_example_defined" do - include_examples "warning of deprecated `:example_group` during filtering configuration", :when_first_matching_example_defined - it "runs the block when the first matching example is defined" do sequence = [] RSpec.configuration.when_first_matching_example_defined(:foo) do @@ -2703,10 +2669,6 @@ def strategy.order(list) end end - describe "hooks" do - include_examples "warning of deprecated `:example_group` during filtering configuration", :before, :each - end - describe '#threadsafe', :threadsafe => true do it 'defaults to false' do expect(config.threadsafe).to eq true diff --git a/spec/rspec/core/metadata_spec.rb b/spec/rspec/core/metadata_spec.rb index bf72dcf62f..82a7e192bf 100644 --- a/spec/rspec/core/metadata_spec.rb +++ b/spec/rspec/core/metadata_spec.rb @@ -136,7 +136,6 @@ def metadata_for(*args) a[:description] = "new description" - pending "Cannot maintain this and provide full `:example_group` backwards compatibility (see GH #1490):(" expect(b[:description]).to eq("new description") end @@ -686,189 +685,14 @@ def value_for(*args) expect(meta).not_to include(:parent_example_group) end - describe "backwards compatibility" do - before { allow_deprecation } + it "doesn't provide example group metadata via `:example_group` key" do + group_metadata = nil - describe ":example_group" do - it 'issues a deprecation warning when the `:example_group` key is accessed' do - expect_deprecation_with_call_site(__FILE__, __LINE__ + 2, /:example_group/) - RSpec.describe(Object, "group") do - metadata[:example_group] - end - end - - it 'does not issue a deprecation warning when :example_group is accessed while applying configured filterings' do - RSpec.configuration.include Module.new, :example_group => { :file_path => /.*/ } - expect_no_deprecation - RSpec.describe(Object, "group") - end - - it 'can still access the example group attributes via [:example_group]' do - meta = nil - RSpec.describe(Object, "group") { meta = metadata } - - expect(meta[:example_group][:line_number]).to eq(__LINE__ - 2) - expect(meta[:example_group][:description]).to eq("Object group") - end - - it 'can access the parent example group attributes via [:example_group][:example_group]' do - child = nil - parent_line = __LINE__ + 1 - RSpec.describe(Object, "group", :foo => 3) do - describe("nested") { child = metadata } - end - - expect(child[:example_group][:example_group].to_h).to include( - :foo => 3, - :description => "Object group", - :line_number => parent_line - ) - end - - it "works properly with deep nesting" do - inner_metadata = nil - - RSpec.describe "Level 1" do - describe "Level 2" do - describe "Level 3" do - inner_metadata = example("Level 4").metadata - end - end - end - - expect(inner_metadata[:description]).to eq("Level 4") - expect(inner_metadata[:example_group][:description]).to eq("Level 3") - expect(inner_metadata[:example_group][:example_group][:description]).to eq("Level 2") - expect(inner_metadata[:example_group][:example_group][:example_group][:description]).to eq("Level 1") - expect(inner_metadata[:example_group][:example_group][:example_group][:example_group]).to be_nil - end - - it "works properly with shallow nesting" do - inner_metadata = nil - - RSpec.describe "Level 1" do - inner_metadata = example("Level 2").metadata - end - - expect(inner_metadata[:description]).to eq("Level 2") - expect(inner_metadata[:example_group][:description]).to eq("Level 1") - expect(inner_metadata[:example_group][:example_group]).to be_nil - end - - it 'allows integration libraries like VCR to infer a fixture name from the example description by walking up nesting structure' do - fixture_name_for = lambda do |meta| - description = meta[:description] - - if example_group = meta[:example_group] - [fixture_name_for[example_group], description].join('/') - else - description - end - end - - ex = inferred_fixture_name = nil - - RSpec.configure do |config| - config.before(:example, :infer_fixture) { |e| inferred_fixture_name = fixture_name_for[e.metadata] } - end - - RSpec.describe "Group", :infer_fixture do - ex = example("ex") { } - end.run - - raise ex.execution_result.exception if ex.execution_result.exception - - expect(inferred_fixture_name).to eq("Group/ex") - end - - it 'can mutate attributes when accessing them via [:example_group]' do - meta = nil - - RSpec.describe(String) do - describe "sub context" do - meta = metadata - end - end - - expect { - meta[:example_group][:described_class] = Hash - }.to change { meta[:described_class] }.from(String).to(Hash) - end - - it 'can still be filtered via a nested key under [:example_group] as before' do - meta = nil - - line = __LINE__ + 1 - RSpec.describe("group") { meta = metadata } - - applies = MetadataFilter.apply?( - :any?, - { :example_group => { :line_number => line } }, - meta - ) - - expect(applies).to be true - end - end - - describe ":example_group_block" do - it 'returns the block' do - meta = nil - - RSpec.describe "group" do - meta = metadata - end - - expect(meta[:example_group_block]).to be_a(Proc).and eq(meta[:block]) - end - - it 'issues a deprecation warning' do - expect_deprecation_with_call_site(__FILE__, __LINE__ + 2, /:example_group_block/) - RSpec.describe "group" do - metadata[:example_group_block] - end - end + RSpec.describe(Object, "group") do + group_metadata = metadata[:example_group] end - describe ":describes" do - context "on an example group metadata hash" do - it 'returns the described_class' do - meta = nil - - RSpec.describe Hash do - meta = metadata - end - - expect(meta[:describes]).to be(Hash).and eq(meta[:described_class]) - end - - it 'issues a deprecation warning' do - expect_deprecation_with_call_site(__FILE__, __LINE__ + 2, /:describes/) - RSpec.describe "group" do - metadata[:describes] - end - end - end - - context "an an example metadata hash" do - it 'returns the described_class' do - meta = nil - - RSpec.describe Hash do - meta = example("ex").metadata - end - - expect(meta[:describes]).to be(Hash).and eq(meta[:described_class]) - end - - it 'issues a deprecation warning' do - expect_deprecation_with_call_site(__FILE__, __LINE__ + 2, /:describes/) - RSpec.describe "group" do - example("ex").metadata[:describes] - end - end - end - end + expect(group_metadata).to be nil end end end