Skip to content

Commit fcd41f6

Browse files
committed
feat: add example.id attribute to RSpec instrumentation (#4)
This PR adds RSpec's `example.id` as a span attribute to provide unique identification for each test example. The existing `rspec.example.location` attribute cannot uniquely identify dynamically generated examples (e.g., examples created within loops or using data tables) because multiple examples share the same source file location. RSpec's `example.id` provides a stable, unique identifier across test runs even for these dynamic cases. Example format: `./spec/foo_spec.rb[1:2:3]` This enables better correlation and tracking of individual test executions in observability platforms. --- Upstream PR: open-telemetry#1713
1 parent ef8a2fe commit fcd41f6

File tree

2 files changed

+31
-0
lines changed

2 files changed

+31
-0
lines changed

instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def example_group_finished(notification)
5858
def example_started(notification)
5959
example = notification.example
6060
attributes = {
61+
'rspec.example.id' => example.id.to_s,
6162
'rspec.example.location' => example.location.to_s,
6263
'rspec.example.full_description' => example.full_description.to_s,
6364
'rspec.example.described_class' => example.metadata[:described_class].to_s

instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ def run_example(...)
131131
_(subject.attributes['rspec.example.location']).must_match %r{\./test/opentelemetry/instrumentation/rspec/formatter_test.rb:\d+}
132132
end
133133

134+
it 'has an id attribute' do
135+
_(subject.attributes['rspec.example.id']).must_match %r{\./test/opentelemetry/instrumentation/rspec/formatter_test.rb\[\d+:\d+\]}
136+
end
137+
134138
it 'records when the example passes' do
135139
_(subject.attributes['rspec.example.result']).must_equal 'passed'
136140
end
@@ -346,6 +350,32 @@ def run_example(...)
346350
_(subject.attributes['rspec.example.result']).must_equal 'passed'
347351
end
348352
end
353+
354+
describe 'dynamic examples with same location' do
355+
it 'have unique example.id attributes' do
356+
spans = run_rspec_with_tracing do
357+
RSpec.describe('dynamic examples') do
358+
[1, 2, 3].each do |num|
359+
example("example #{num}") { expect(num).to be_positive }
360+
end
361+
end
362+
end
363+
364+
example_spans = spans.select { |span| span.name.start_with?('example ') }
365+
_(example_spans.size).must_equal 3
366+
367+
# All examples have the same location (same line in the loop)
368+
locations = example_spans.map { |span| span.attributes['rspec.example.location'] }
369+
_(locations.uniq.size).must_equal 1
370+
371+
# But each has a unique id
372+
ids = example_spans.map { |span| span.attributes['rspec.example.id'] }
373+
_(ids.uniq.size).must_equal 3
374+
ids.each do |id|
375+
_(id).must_match %r{\./test/opentelemetry/instrumentation/rspec/formatter_test.rb\[\d+:\d+\]}
376+
end
377+
end
378+
end
349379
end
350380

351381
describe 'using a custom tracer provider' do

0 commit comments

Comments
 (0)