Skip to content

Commit

Permalink
Provide a safe, correct way to update metadata after its creation.
Browse files Browse the repository at this point in the history
- Takes care of preserving metadata inheritance.
- Takes care of re-applying filtered config items
  like module inclusions and hooks.

This is necessary for #1790.
  • Loading branch information
myronmarston committed Jun 5, 2016
1 parent 0217ed4 commit 6f856b7
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
7 changes: 7 additions & 0 deletions lib/rspec/core/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ def duplicate_with(metadata_overrides={})
new_metadata, new_metadata[:block])
end

# @private
def update_inherited_metadata(updates)
metadata.update(updates) do |_key, existing_example_value, _new_inherited_value|
existing_example_value
end
end

# @attr_reader
#
# Returns the first exception raised in the context of running this
Expand Down
11 changes: 11 additions & 0 deletions lib/rspec/core/example_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,17 @@ class << self; self; end
# :nocov:
end

# @private
def self.update_inherited_metadata(updates)
metadata.update(updates) do |_key, existing_group_value, _new_inherited_value|
existing_group_value
end

RSpec.configuration.configure_group(self)
examples.each { |ex| ex.update_inherited_metadata(updates) }
children.each { |group| group.update_inherited_metadata(updates) }
end

# Raised when an RSpec API is called in the wrong scope, such as `before`
# being called from within an example rather than from within an example
# group block.
Expand Down
90 changes: 90 additions & 0 deletions spec/rspec/core/example_group_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,96 @@ def extract_execution_results(group)
end
end

describe "#update_inherited_metadata" do
it "updates the group metadata with the provided hash" do
group = RSpec.describe

expect(group.metadata).not_to include(:foo => 1, :bar => 2)
group.update_inherited_metadata(:foo => 1, :bar => 2)
expect(group.metadata).to include(:foo => 1, :bar => 2)
end

it "does not overwrite existing metadata since group metadata takes precedence over inherited metadata" do
group = RSpec.describe("group", :foo => 1)

expect {
group.update_inherited_metadata(:foo => 2)
}.not_to change { group.metadata[:foo] }.from(1)
end

it "does not replace the existing metadata object with a new one or change its default proc" do
group = RSpec.describe

expect {
group.update_inherited_metadata(:foo => 1)
}.to avoid_changing { group.metadata.__id__ }.and avoid_changing { group.metadata.default_proc }
end

it "propogates metadata updates to previously declared child examples" do
group = RSpec.describe
example = group.example

expect {
group.update_inherited_metadata(:foo => 1)
}.to change { example.metadata[:foo] }.from(nil).to(1)
end

it "propogates metadata updates to previously declared child group" do
group = RSpec.describe
child_group = group.describe

expect {
group.update_inherited_metadata(:foo => 1)
}.to change { child_group.metadata[:foo] }.from(nil).to(1)
end

it "applies new metadata-based config items based on the update" do
extension = Module.new do
def extension_method; 17; end
end

sequence = []
extension_checks = []
RSpec.configure do |c|
c.before(:example, :foo => true) { sequence << :global_before_hook }
c.after(:example, :foo => true) { sequence << :global_after_hook }
c.extend extension, :foo => true
end

describe_successfully do
example { sequence << :example_1 }

extension_checks << begin
self.extension_method
rescue NoMethodError
:method_not_defined
end

context "nested group before update" do
example { sequence << :nested_example }
end

update_inherited_metadata(:foo => true)

extension_checks << begin
self.extension_method
rescue NoMethodError
:method_not_defined
end

example { sequence << :example_2 }
end

expect(sequence).to eq [
:global_before_hook, :example_1, :global_after_hook,
:global_before_hook, :example_2, :global_after_hook,
:global_before_hook, :nested_example, :global_after_hook,
]

expect(extension_checks).to eq [:method_not_defined, 17]
end
end

%w[include_examples include_context].each do |name|
describe "##{name}" do
let(:group) { RSpec.describe }
Expand Down
42 changes: 42 additions & 0 deletions spec/rspec/core/example_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,48 @@ def metadata_hash(*args)
end
end

describe "#update_inherited_metadata" do
it "updates the example metadata with the provided hash" do
example = RSpec.describe.example

expect(example.metadata).not_to include(:foo => 1, :bar => 2)
example.update_inherited_metadata(:foo => 1, :bar => 2)
expect(example.metadata).to include(:foo => 1, :bar => 2)
end

it "does not overwrite existing metadata since example metadata takes precedence over inherited metadata" do
example = RSpec.describe.example("ex", :foo => 1)

expect {
example.update_inherited_metadata(:foo => 2)
}.not_to change { example.metadata[:foo] }.from(1)
end

it "does not replace the existing metadata object with a new one or change its default proc" do
example = RSpec.describe.example

expect {
example.update_inherited_metadata(:foo => 1)
}.to avoid_changing { example.metadata.__id__ }.and avoid_changing { example.metadata.default_proc }
end

it "applies new metadata-based config items based on the update" do
sequence = []
RSpec.configure do |c|
c.before(:example, :foo => true) { sequence << :global_before_hook }
c.after(:example, :foo => true) { sequence << :global_after_hook }
end

describe_successfully do
it "gets the before hook due to the update" do
sequence << :example
end.update_inherited_metadata(:foo => true)
end

expect(sequence).to eq [:global_before_hook, :example, :global_after_hook]
end
end

describe '#duplicate_with' do
it 'successfully duplicates an example' do
example = example_group.example { raise 'first' }
Expand Down

0 comments on commit 6f856b7

Please sign in to comment.