Module: RSpecTracer::Example
- Defined in:
- lib/rspec_tracer/example.rb
Overview
Builds the identity-hash payload (:example_id-keyed Hash) that
RSpec::RunnerHook attaches to every example pre-run.
Identity stability contract
example_id is the MD5 of a stable subset of the payload:
example_group (the describe block's description string),
description, full_description, shared_group (inclusion
locations with the trailing line number stripped), and
file_name. The contract, in one line: rename = new identity;
restructure = same identity.
Identity is PRESERVED when:
- blank lines or comments are added/removed around the example
- examples are reordered within a describe block (named examples only - see "Unnamed examples" below)
- a sibling describe / example in the same file is renamed
- metadata changes (
skip:, tags,tracks: { ... }) - the spec-file digest still triggers the re-run and status history is kept - the example body or its hooks (
before,let) are edited - again, the file digest triggers the re-run
Identity CHANGES (one cold "No cache" run) when:
- the file is renamed or moved
- the
describe/it/ shared-example name is changed - the example moves to a different describe block
Unnamed examples (it { }, specify { }, example { })
An example with no description string has no stable name to hash,
and identity must be computed pre-run (for the filter decision),
before RSpec generates a matcher-derived description. The only
line-independent signal RSpec exposes pre-run is position, so an
unnamed example's identity is derived from its ordinal among the
unnamed examples of its group. (RSpec's description for an
unnamed example is the line-bearing "example at <path>:<line>"
fallback, which would otherwise leak the line number straight
back into the digest - issue #210.)
For unnamed examples the contract above is amended: identity is
still PRESERVED across blank-line / comment edits, sibling
renames, and adding or removing named siblings - but it CHANGES
when the unnamed examples are reordered, or one is inserted or
removed ahead of it. Give an example an explicit description
(it 'does X' do) for a fully reorder-stable identity.
line_number / rerun_file_name / rerun_line_number stay in
the returned Hash for the reporter + explain location columns,
but are DELIBERATELY EXCLUDED from the digest - a no-op edit that
shifts line numbers must not invalidate the cache. example_group
uses example_group.description (the user's string) rather than
example_group.name: RSpec's generated class name carries a
load-order-dependent _2 / _3 suffix when two files share a
describe name, which would otherwise flip the id across runs.
Helpers are def self.x + private_class_method so mutant
attributes mutations through the singleton call path.
Class Method Summary collapse
-
.from(example) ⇒ Object
private
Builds the identity payload for one RSpec example.
Class Method Details
.from(example) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Builds the identity payload for one RSpec example. The MD5 is
taken over the stability-contract subset (see digest_identity
for the named-vs-unnamed split); line_number / rerun_* ride
along in the returned Hash for the reporters but never enter the
digest. See the module comment for the full stability contract.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/rspec_tracer/example.rb', line 76 def self.from(example) location = example_location(example) identity = { example_group: example.example_group.description, description: example.description, full_description: example.full_description, shared_group: example.[:shared_group_inclusion_backtrace] .map { |frame| frame.formatted_inclusion_location.sub(/:\d+\z/, '') }, file_name: location[:file_name] } identity .merge(location) .merge(example_id: Digest::MD5.hexdigest(digest_identity(example, identity).to_json)) end |