rspec-tracer 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24ed84e70e415d8eccd09ea63cee04cc318e4a167227dc9f937a3badb7229fb6
4
- data.tar.gz: fa6d30e91e776b35b935b5e0e420422700d552859d894d001e16fee2b93e0874
3
+ metadata.gz: 67309438f6000ac9913b4d3f48b3e7ba66c56e09674b2bf191b75d0d8134401c
4
+ data.tar.gz: e10e5253987938b6467920a7adda07dbbce152849373b1c8a4355e771007c196
5
5
  SHA512:
6
- metadata.gz: 4c3abb2234b5c02257f70c494775a4d01d5fe84dfeb9d61eba92f70064a649045607ac801b526ca8198b3defc75db8751db68597557ae379666f0c979236cbe0
7
- data.tar.gz: 23b9a629e8a8d93af51d74b2d426c6770db64ed9971c50fb269f96dd74abcb1badc520f02289725d96a97a7e5077210e6d54e3405da2510a799a4ec956cd81c9
6
+ metadata.gz: 80b3e75b07083b15e179fa4d108afbf31271ee15c3b87d48625ccfa481a6fa24a70114246fe9644b6d2d743732515029e9ec93e078a9207ed1c4c98ba61964ca
7
+ data.tar.gz: 8ac718e348b8cc6559b87b45d117cb20ddbca819687208124e1c8e7df557e97528d0da93823fe502e9d689646457037ce9be4c7529f3bdf853ecbcbb6f7a98e7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,82 @@
1
+ ## [1.0.5] - 2026-05-17
2
+
3
+ ### Fixed
4
+
5
+ - **`example_id` is now stable across runs and across no-op edits** —
6
+ `Example.from` previously fed `example.example_group.name` and
7
+ line numbers into the identity-hash payload. Two unrelated effects
8
+ shifted the resulting MD5 across runs that should have been
9
+ identical:
10
+
11
+ - `example_group.name` is RSpec's generated class name with a
12
+ load-order-dependent `_2` / `_3` suffix when two spec files share
13
+ a `describe` name (very common — e.g. `describe User do` in
14
+ `user_spec.rb` and `models/user_spec.rb`). The same example got
15
+ a different id depending on rspec's file load order, silently
16
+ thrashing the cache and breaking failed / pending always-re-run
17
+ guarantees.
18
+ - For unnamed examples (`it { }` / `specify { }` / `example { }`),
19
+ `example.description` falls back to RSpec's line-bearing
20
+ `"example at <path>:<line>"` string, so a no-op blank-line edit
21
+ above the example flipped its id and orphaned its cache entry.
22
+
23
+ The digest now uses `example_group.description` (the user's
24
+ string, not the generated class name), strips the trailing `:LINE`
25
+ from `shared_group_inclusion_backtrace` entries, and for unnamed
26
+ examples substitutes a line-independent positional discriminator
27
+ (the example's 0-based ordinal among the unnamed examples of its
28
+ group). `line_number` / `rerun_file_name` / `rerun_line_number`
29
+ still ride along in the stored payload for the reporter's location
30
+ columns but no longer enter the digest. Contract: *rename = new
31
+ identity; restructure = same identity.*
32
+
33
+ Affected since v1.0.0 (2021). One-time cold run on upgrade: every
34
+ cached `example_id` changes shape, so the first 1.0.5 run misses
35
+ the 1.0.4 cache uniformly and treats every example as no-cache;
36
+ the second run is warm again. Surfaced by the 2.0.0.pre.1 field
37
+ test against third-party Rails apps; ports
38
+ [#209](https://github.com/avmnu-sng/rspec-tracer/pull/209) and
39
+ [#211](https://github.com/avmnu-sng/rspec-tracer/pull/211) onto
40
+ this 1.0.x line. Closes
41
+ [#196](https://github.com/avmnu-sng/rspec-tracer/issues/196) and
42
+ [#210](https://github.com/avmnu-sng/rspec-tracer/issues/210).
43
+
44
+ - **`RSpecTracer.start` no longer crashes when the user pre-started
45
+ `::Coverage`** — users who want branch coverage typically call
46
+ `Coverage.start(lines: true, branches: true)` before loading the
47
+ tracer. On the previous code path, `setup_coverage` called bare
48
+ `::Coverage.start` unconditionally, which raised
49
+ `RuntimeError: coverage measurement is already setup` and crashed
50
+ the tracer init. `setup_coverage` now guards on `Coverage.running?`
51
+ (Ruby 2.7+) and rescues `RuntimeError` for older Rubies; the
52
+ tracer attaches to the already-running `::Coverage` instance
53
+ instead of raising. Ports
54
+ [#207](https://github.com/avmnu-sng/rspec-tracer/pull/207)'s
55
+ Coverage-guard half (the 2.0-only `coverage_modes` DSL is not
56
+ backported). Closes
57
+ [#195](https://github.com/avmnu-sng/rspec-tracer/issues/195).
58
+
59
+ - **`remote_cache` download and upload now log on success** — the
60
+ success paths on `RemoteCache::Cache#download` / `#upload` returned
61
+ silently, so a successful `rake rspec_tracer:remote_cache:download`
62
+ produced zero output and users couldn't tell from CI logs whether
63
+ the cache restored. Two `puts` lines now announce success:
64
+ `rspec-tracer remote_cache: restored cache from <sha>` after a
65
+ successful download, and `rspec-tracer remote_cache: uploaded cache
66
+ to <ref>` after a successful upload. Ports the basic-INFO portion of
67
+ [#201](https://github.com/avmnu-sng/rspec-tracer/pull/201) (the
68
+ cross-branch-fallback qualifier and `prune_all!` line are
69
+ 2.0-only — 1.x has neither a multi-tier cache nor a prune_all
70
+ path). Closes
71
+ [#188](https://github.com/avmnu-sng/rspec-tracer/issues/188).
72
+
73
+ ### Changed
74
+
75
+ - **Gemspec now requires MFA for publishing** — adds
76
+ `spec.metadata['rubygems_mfa_required'] = 'true'`. Pure packaging
77
+ metadata; no runtime impact for gem consumers. Ports
78
+ [#214](https://github.com/avmnu-sng/rspec-tracer/pull/214).
79
+
1
80
  ## [1.0.4] - 2026-05-06
2
81
 
3
82
  ### Fixed
@@ -4,16 +4,64 @@ module RSpecTracer
4
4
  module Example
5
5
  module_function
6
6
 
7
+ # Identity-keyed cache of `<example_group> => Array<unnamed sibling>`
8
+ # populated lazily by `unnamed_description`. A group with N unnamed
9
+ # examples computes the sibling list once per group rather than N
10
+ # times. Memory is bounded by the live group set, which RSpec
11
+ # retains for the run anyway.
12
+ @unnamed_siblings_cache = {}.compare_by_identity
13
+
7
14
  def from(example)
8
- data = {
9
- example_group: example.example_group.name,
15
+ location = example_location(example)
16
+ identity = {
17
+ example_group: example.example_group.description,
10
18
  description: example.description,
11
19
  full_description: example.full_description,
12
20
  shared_group: example.metadata[:shared_group_inclusion_backtrace]
13
- .map(&:formatted_inclusion_location)
14
- }.merge(example_location(example))
21
+ .map { |frame| frame.formatted_inclusion_location.sub(/:\d+\z/, '') },
22
+ file_name: location[:file_name]
23
+ }
24
+
25
+ identity
26
+ .merge(location)
27
+ .merge(example_id: Digest::MD5.hexdigest(digest_identity(example, identity).to_json))
28
+ end
29
+
30
+ # The Hash actually fed to the MD5. For a named example this is
31
+ # `identity` unchanged. For an unnamed example (`it { }` /
32
+ # `specify { }` / `example { }`) RSpec's `description` is the
33
+ # line-bearing `"example at <path>:<line>"` fallback, so
34
+ # `description` is swapped for a line-independent positional
35
+ # discriminator before hashing. The returned/stored payload still
36
+ # carries RSpec's `description` / `full_description` untouched —
37
+ # only the digest input differs.
38
+ def digest_identity(example, identity)
39
+ return identity unless unnamed?(example)
40
+
41
+ identity.merge(description: unnamed_description(example))
42
+ end
43
+
44
+ # RSpec's raw `metadata[:description]` is `""` for `it { }` /
45
+ # `specify { }` / `example { }`; the `description` *method* would
46
+ # instead return the line-bearing `"example at <path>:<line>"`
47
+ # fallback, so the raw metadata value is what cleanly tells named
48
+ # from unnamed.
49
+ def unnamed?(example)
50
+ example.metadata[:description].to_s.strip.empty?
51
+ end
52
+
53
+ # 0-based ordinal of the example among the *unnamed* examples of
54
+ # its group. Stable across blank-line / comment edits and across
55
+ # adding or renaming *named* siblings; changes only when the
56
+ # unnamed examples are reordered or one is inserted / removed
57
+ # ahead of it. The Ruby-inspect-style `#<...>` form makes spoofing
58
+ # by a real user description implausible.
59
+ def unnamed_description(example)
60
+ group = example.example_group
61
+ unnamed_siblings = @unnamed_siblings_cache[group] ||=
62
+ group.examples.select { |sibling| unnamed?(sibling) }
15
63
 
16
- data.merge(example_id: Digest::MD5.hexdigest(data.to_json))
64
+ "#<rspec-tracer unnamed example #{unnamed_siblings.index(example)}>"
17
65
  end
18
66
 
19
67
  def example_location(example)
@@ -53,6 +101,7 @@ module RSpecTracer
53
101
  RSpecTracer::SourceFile.file_name(file_path)
54
102
  end
55
103
 
56
- private_class_method :example_location, :example_rerun_location, :location_file_name
104
+ private_class_method :digest_identity, :unnamed?, :unnamed_description,
105
+ :example_location, :example_rerun_location, :location_file_name
57
106
  end
58
107
  end
@@ -19,6 +19,8 @@ module RSpecTracer
19
19
 
20
20
  @aws.download_file(@cache_sha, 'last_run.json')
21
21
  @aws.download_dir(@cache_sha, last_run_id)
22
+
23
+ puts "rspec-tracer remote_cache: restored cache from #{@cache_sha}"
22
24
  rescue StandardError => e
23
25
  puts "Error: #{e.message}"
24
26
  puts e.backtrace.first(5).join("\n")
@@ -32,6 +34,8 @@ module RSpecTracer
32
34
 
33
35
  write_branch_refs(file_name)
34
36
  @aws.upload_branch_refs(@repo.branch_name, file_name)
37
+
38
+ puts "rspec-tracer remote_cache: uploaded cache to #{@repo.branch_ref}"
35
39
  rescue StandardError => e
36
40
  puts "Error: #{e.message}"
37
41
  puts e.backtrace.first(5).join("\n")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpecTracer
4
- VERSION = '1.0.4'
4
+ VERSION = '1.0.5'
5
5
  end
data/lib/rspec_tracer.rb CHANGED
@@ -263,7 +263,15 @@ module RSpecTracer
263
263
 
264
264
  require 'coverage'
265
265
 
266
+ return if ::Coverage.respond_to?(:running?) && ::Coverage.running?
267
+
266
268
  ::Coverage.start
269
+ rescue RuntimeError
270
+ # Ruby < 2.7 has no Coverage.running? predicate; the rescue
271
+ # catches the "user pre-started Coverage themselves" path on
272
+ # those Rubies. Safe to absorb — the tracer attaches to the
273
+ # already-running ::Coverage instance.
274
+ nil
267
275
  end
268
276
 
269
277
  def setup_trace_point
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abhimanyu Singh
@@ -118,9 +118,10 @@ licenses:
118
118
  - MIT
119
119
  metadata:
120
120
  homepage_uri: https://github.com/avmnu-sng/rspec-tracer
121
- source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.0.4
121
+ source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.0.5
122
122
  changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
123
123
  bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
124
+ rubygems_mfa_required: 'true'
124
125
  rdoc_options: []
125
126
  require_paths:
126
127
  - lib