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 |