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

Commit ebf9c80

Browse files
committed
Add config.define_derived_metadata.
Fixes #969 and supercedes #1089.
1 parent 6b67068 commit ebf9c80

File tree

4 files changed

+132
-0
lines changed

4 files changed

+132
-0
lines changed

Changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ Enhancements:
9797
* Add `disable_monkey_patching!` config option that disables all monkey
9898
patching from whatever pieces of RSpec you use. (Alexey Fedorov)
9999
* Add `Pathname` support for setting all output streams. (Aaron Kromer)
100+
* Add `config.define_derived_metadata`, which can be used to apply
101+
additional metadata to all groups or examples that match a given
102+
filter. (Myron Marston)
100103

101104
Bug Fixes:
102105

lib/rspec/core/configuration.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ def initialize
300300
@profile_examples = false
301301
@requires = []
302302
@libs = []
303+
@derived_metadata_blocks = []
303304
end
304305

305306
# @private
@@ -1209,6 +1210,32 @@ def disable_monkey_patching!
12091210
# @private
12101211
attr_accessor :disable_monkey_patching
12111212

1213+
# Defines a callback that can assign derived metadata values.
1214+
#
1215+
# @param filters [Array<Symbol>, Hash] metadata filters that determine which example
1216+
# or group metadata hashes the callback will be triggered for. If none are given,
1217+
# the callback will be run against the metadata hashes of all groups and examples.
1218+
# @yieldparam metadata [Hash] original metadata hash from an example or group. Mutate this in
1219+
# your block as needed.
1220+
#
1221+
# @example
1222+
# RSpec.configure do |config|
1223+
# # Tag all groups and examples in the spec/unit directory with :type => :unit
1224+
# config.define_derived_metadata(:file_path => %r{/spec/unit/}) do |metadata|
1225+
# metadata[:type] = :unit
1226+
# end
1227+
# end
1228+
def define_derived_metadata(*filters, &block)
1229+
@derived_metadata_blocks << [Metadata.build_hash_from(filters), block]
1230+
end
1231+
1232+
# @private
1233+
def apply_derived_metadata_to(metadata)
1234+
@derived_metadata_blocks.each do |filter, block|
1235+
block.call(metadata) if filter.empty? || MetadataFilter.any_apply?(filter, metadata)
1236+
end
1237+
end
1238+
12121239
private
12131240

12141241
def get_files_to_run(paths)

lib/rspec/core/metadata.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def populate
8484

8585
populate_location_attributes
8686
metadata.update(user_metadata)
87+
RSpec.configuration.apply_derived_metadata_to(metadata)
8788
end
8889

8990
private

spec/rspec/core/configuration_spec.rb

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,107 @@ def metadata_hash(*args)
10771077
end
10781078
end
10791079

1080+
describe "#define_derived_metadata" do
1081+
it 'allows the provided block to mutate example group metadata' do
1082+
RSpec.configuration.define_derived_metadata do |metadata|
1083+
metadata[:reverse_description] = metadata[:description].reverse
1084+
end
1085+
1086+
group = RSpec.describe("My group")
1087+
expect(group.metadata).to include(:description => "My group", :reverse_description => "puorg yM")
1088+
end
1089+
1090+
it 'allows the provided block to mutate example metadata' do
1091+
RSpec.configuration.define_derived_metadata do |metadata|
1092+
metadata[:reverse_description] = metadata[:description].reverse
1093+
end
1094+
1095+
ex = RSpec.describe("My group").example("foo")
1096+
expect(ex.metadata).to include(:description => "foo", :reverse_description => "oof")
1097+
end
1098+
1099+
it 'allows multiple configured blocks to be applied, in order of definition' do
1100+
RSpec.configure do |c|
1101+
c.define_derived_metadata { |m| m[:b1_desc] = m[:description] + " (block 1)" }
1102+
c.define_derived_metadata { |m| m[:b2_desc] = m[:b1_desc] + " (block 2)" }
1103+
end
1104+
1105+
group = RSpec.describe("bar")
1106+
expect(group.metadata).to include(:b1_desc => "bar (block 1)", :b2_desc => "bar (block 1) (block 2)")
1107+
end
1108+
1109+
it "derives metadata before the group or example blocks are eval'd so their logic can depend on the derived metadata" do
1110+
RSpec.configure do |c|
1111+
c.define_derived_metadata(:foo) do |metadata|
1112+
metadata[:bar] = "bar"
1113+
end
1114+
end
1115+
1116+
group_bar_value = example_bar_value = nil
1117+
1118+
RSpec.describe "Group", :foo do
1119+
group_bar_value = metadata[:bar]
1120+
example_bar_value = example("ex", :foo).metadata[:bar]
1121+
end
1122+
1123+
expect(group_bar_value).to eq("bar")
1124+
expect(example_bar_value).to eq("bar")
1125+
end
1126+
1127+
context "when passed a metadata filter" do
1128+
it 'only applies to the groups and examples that match that filter' do
1129+
RSpec.configure do |c|
1130+
c.define_derived_metadata(:apply => true) do |metadata|
1131+
metadata[:reverse_description] = metadata[:description].reverse
1132+
end
1133+
end
1134+
1135+
g1 = RSpec.describe("G1", :apply)
1136+
g2 = RSpec.describe("G2")
1137+
e1 = g1.example("E1")
1138+
e2 = g2.example("E2", :apply)
1139+
e3 = g2.example("E3")
1140+
1141+
expect(g1.metadata).to include(:reverse_description => "1G")
1142+
expect(g2.metadata).not_to include(:reverse_description)
1143+
1144+
expect(e1.metadata).to include(:reverse_description => "1E")
1145+
expect(e2.metadata).to include(:reverse_description => "2E")
1146+
expect(e3.metadata).not_to include(:reverse_description)
1147+
end
1148+
1149+
it 'applies if any of multiple filters apply (to align with module inclusion semantics)' do
1150+
RSpec.configure do |c|
1151+
c.define_derived_metadata(:a => 1, :b => 2) do |metadata|
1152+
metadata[:reverse_description] = metadata[:description].reverse
1153+
end
1154+
end
1155+
1156+
g1 = RSpec.describe("G1", :a => 1)
1157+
g2 = RSpec.describe("G2", :b => 2)
1158+
g3 = RSpec.describe("G3", :c => 3)
1159+
1160+
expect(g1.metadata).to include(:reverse_description => "1G")
1161+
expect(g2.metadata).to include(:reverse_description => "2G")
1162+
expect(g3.metadata).not_to include(:reverse_description)
1163+
end
1164+
1165+
it 'allows a metadata filter to be passed as a raw symbol' do
1166+
RSpec.configure do |c|
1167+
c.define_derived_metadata(:apply) do |metadata|
1168+
metadata[:reverse_description] = metadata[:description].reverse
1169+
end
1170+
end
1171+
1172+
g1 = RSpec.describe("G1", :apply)
1173+
g2 = RSpec.describe("G2")
1174+
1175+
expect(g1.metadata).to include(:reverse_description => "1G")
1176+
expect(g2.metadata).not_to include(:reverse_description)
1177+
end
1178+
end
1179+
end
1180+
10801181
describe "#add_setting" do
10811182
describe "with no modifiers" do
10821183
context "with no additional options" do

0 commit comments

Comments
 (0)