Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 6f856b7

Browse files
committed
Provide a safe, correct way to update metadata after its creation.
- Takes care of preserving metadata inheritance. - Takes care of re-applying filtered config items like module inclusions and hooks. This is necessary for #1790.
1 parent 0217ed4 commit 6f856b7

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-0
lines changed

lib/rspec/core/example.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ def duplicate_with(metadata_overrides={})
142142
new_metadata, new_metadata[:block])
143143
end
144144

145+
# @private
146+
def update_inherited_metadata(updates)
147+
metadata.update(updates) do |_key, existing_example_value, _new_inherited_value|
148+
existing_example_value
149+
end
150+
end
151+
145152
# @attr_reader
146153
#
147154
# Returns the first exception raised in the context of running this

lib/rspec/core/example_group.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,17 @@ class << self; self; end
689689
# :nocov:
690690
end
691691

692+
# @private
693+
def self.update_inherited_metadata(updates)
694+
metadata.update(updates) do |_key, existing_group_value, _new_inherited_value|
695+
existing_group_value
696+
end
697+
698+
RSpec.configuration.configure_group(self)
699+
examples.each { |ex| ex.update_inherited_metadata(updates) }
700+
children.each { |group| group.update_inherited_metadata(updates) }
701+
end
702+
692703
# Raised when an RSpec API is called in the wrong scope, such as `before`
693704
# being called from within an example rather than from within an example
694705
# group block.

spec/rspec/core/example_group_spec.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,6 +1533,96 @@ def extract_execution_results(group)
15331533
end
15341534
end
15351535

1536+
describe "#update_inherited_metadata" do
1537+
it "updates the group metadata with the provided hash" do
1538+
group = RSpec.describe
1539+
1540+
expect(group.metadata).not_to include(:foo => 1, :bar => 2)
1541+
group.update_inherited_metadata(:foo => 1, :bar => 2)
1542+
expect(group.metadata).to include(:foo => 1, :bar => 2)
1543+
end
1544+
1545+
it "does not overwrite existing metadata since group metadata takes precedence over inherited metadata" do
1546+
group = RSpec.describe("group", :foo => 1)
1547+
1548+
expect {
1549+
group.update_inherited_metadata(:foo => 2)
1550+
}.not_to change { group.metadata[:foo] }.from(1)
1551+
end
1552+
1553+
it "does not replace the existing metadata object with a new one or change its default proc" do
1554+
group = RSpec.describe
1555+
1556+
expect {
1557+
group.update_inherited_metadata(:foo => 1)
1558+
}.to avoid_changing { group.metadata.__id__ }.and avoid_changing { group.metadata.default_proc }
1559+
end
1560+
1561+
it "propogates metadata updates to previously declared child examples" do
1562+
group = RSpec.describe
1563+
example = group.example
1564+
1565+
expect {
1566+
group.update_inherited_metadata(:foo => 1)
1567+
}.to change { example.metadata[:foo] }.from(nil).to(1)
1568+
end
1569+
1570+
it "propogates metadata updates to previously declared child group" do
1571+
group = RSpec.describe
1572+
child_group = group.describe
1573+
1574+
expect {
1575+
group.update_inherited_metadata(:foo => 1)
1576+
}.to change { child_group.metadata[:foo] }.from(nil).to(1)
1577+
end
1578+
1579+
it "applies new metadata-based config items based on the update" do
1580+
extension = Module.new do
1581+
def extension_method; 17; end
1582+
end
1583+
1584+
sequence = []
1585+
extension_checks = []
1586+
RSpec.configure do |c|
1587+
c.before(:example, :foo => true) { sequence << :global_before_hook }
1588+
c.after(:example, :foo => true) { sequence << :global_after_hook }
1589+
c.extend extension, :foo => true
1590+
end
1591+
1592+
describe_successfully do
1593+
example { sequence << :example_1 }
1594+
1595+
extension_checks << begin
1596+
self.extension_method
1597+
rescue NoMethodError
1598+
:method_not_defined
1599+
end
1600+
1601+
context "nested group before update" do
1602+
example { sequence << :nested_example }
1603+
end
1604+
1605+
update_inherited_metadata(:foo => true)
1606+
1607+
extension_checks << begin
1608+
self.extension_method
1609+
rescue NoMethodError
1610+
:method_not_defined
1611+
end
1612+
1613+
example { sequence << :example_2 }
1614+
end
1615+
1616+
expect(sequence).to eq [
1617+
:global_before_hook, :example_1, :global_after_hook,
1618+
:global_before_hook, :example_2, :global_after_hook,
1619+
:global_before_hook, :nested_example, :global_after_hook,
1620+
]
1621+
1622+
expect(extension_checks).to eq [:method_not_defined, 17]
1623+
end
1624+
end
1625+
15361626
%w[include_examples include_context].each do |name|
15371627
describe "##{name}" do
15381628
let(:group) { RSpec.describe }

spec/rspec/core/example_spec.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,48 @@ def metadata_hash(*args)
4040
end
4141
end
4242

43+
describe "#update_inherited_metadata" do
44+
it "updates the example metadata with the provided hash" do
45+
example = RSpec.describe.example
46+
47+
expect(example.metadata).not_to include(:foo => 1, :bar => 2)
48+
example.update_inherited_metadata(:foo => 1, :bar => 2)
49+
expect(example.metadata).to include(:foo => 1, :bar => 2)
50+
end
51+
52+
it "does not overwrite existing metadata since example metadata takes precedence over inherited metadata" do
53+
example = RSpec.describe.example("ex", :foo => 1)
54+
55+
expect {
56+
example.update_inherited_metadata(:foo => 2)
57+
}.not_to change { example.metadata[:foo] }.from(1)
58+
end
59+
60+
it "does not replace the existing metadata object with a new one or change its default proc" do
61+
example = RSpec.describe.example
62+
63+
expect {
64+
example.update_inherited_metadata(:foo => 1)
65+
}.to avoid_changing { example.metadata.__id__ }.and avoid_changing { example.metadata.default_proc }
66+
end
67+
68+
it "applies new metadata-based config items based on the update" do
69+
sequence = []
70+
RSpec.configure do |c|
71+
c.before(:example, :foo => true) { sequence << :global_before_hook }
72+
c.after(:example, :foo => true) { sequence << :global_after_hook }
73+
end
74+
75+
describe_successfully do
76+
it "gets the before hook due to the update" do
77+
sequence << :example
78+
end.update_inherited_metadata(:foo => true)
79+
end
80+
81+
expect(sequence).to eq [:global_before_hook, :example, :global_after_hook]
82+
end
83+
end
84+
4385
describe '#duplicate_with' do
4486
it 'successfully duplicates an example' do
4587
example = example_group.example { raise 'first' }

0 commit comments

Comments
 (0)