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
  • the example body or its hooks (before, let) are edited - the file digest still 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.)

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 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.

Class Method Summary collapse

Class Method Details

.from(example) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rspec_tracer/example.rb', line 66

def 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