rspec-tracer 1.2.2 → 2.0.0.pre.1

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.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +197 -45
  3. data/README.md +439 -429
  4. data/bin/rspec-tracer +15 -0
  5. data/lib/rspec_tracer/cache/Rakefile +43 -0
  6. data/lib/rspec_tracer/cli/cache_clear.rb +98 -0
  7. data/lib/rspec_tracer/cli/cache_info.rb +103 -0
  8. data/lib/rspec_tracer/cli/doctor.rb +275 -0
  9. data/lib/rspec_tracer/cli/explain.rb +148 -0
  10. data/lib/rspec_tracer/cli/report_open.rb +82 -0
  11. data/lib/rspec_tracer/cli.rb +116 -0
  12. data/lib/rspec_tracer/configuration.rb +1100 -3
  13. data/lib/rspec_tracer/engine.rb +1076 -0
  14. data/lib/rspec_tracer/example.rb +21 -6
  15. data/lib/rspec_tracer/filter.rb +35 -0
  16. data/lib/rspec_tracer/line_stub.rb +61 -0
  17. data/lib/rspec_tracer/load_config.rb +2 -2
  18. data/lib/rspec_tracer/logger.rb +15 -0
  19. data/lib/rspec_tracer/rails/README.md +78 -0
  20. data/lib/rspec_tracer/rails/i18n_tracking.rb +137 -0
  21. data/lib/rspec_tracer/rails/notifications.rb +263 -0
  22. data/lib/rspec_tracer/rails/preset.rb +94 -0
  23. data/lib/rspec_tracer/rails/railtie.rb +22 -0
  24. data/lib/rspec_tracer/rails.rb +15 -0
  25. data/lib/rspec_tracer/remote_cache/README.md +140 -0
  26. data/lib/rspec_tracer/remote_cache/Rakefile +35 -11
  27. data/lib/rspec_tracer/remote_cache/archive.rb +137 -0
  28. data/lib/rspec_tracer/remote_cache/backend.rb +73 -0
  29. data/lib/rspec_tracer/remote_cache/git_ancestry.rb +241 -0
  30. data/lib/rspec_tracer/remote_cache/local_fs_backend.rb +439 -0
  31. data/lib/rspec_tracer/remote_cache/redis_backend.rb +554 -0
  32. data/lib/rspec_tracer/remote_cache/s3_backend.rb +712 -0
  33. data/lib/rspec_tracer/remote_cache/user_tasks.rb +397 -0
  34. data/lib/rspec_tracer/remote_cache/validator.rb +40 -62
  35. data/lib/rspec_tracer/remote_cache.rb +22 -0
  36. data/lib/rspec_tracer/reporters/README.md +103 -0
  37. data/lib/rspec_tracer/reporters/base.rb +87 -0
  38. data/lib/rspec_tracer/reporters/coverage_json_reporter.rb +338 -0
  39. data/lib/rspec_tracer/reporters/html/.gitignore +19 -0
  40. data/lib/rspec_tracer/reporters/html/.prettierignore +4 -0
  41. data/lib/rspec_tracer/reporters/html/.prettierrc.json +9 -0
  42. data/lib/rspec_tracer/reporters/html/README.md +80 -0
  43. data/lib/rspec_tracer/reporters/html/dist/assets/index.css +2 -0
  44. data/lib/rspec_tracer/reporters/html/dist/assets/index.js +1 -0
  45. data/lib/rspec_tracer/reporters/html/dist/index.html +24 -0
  46. data/lib/rspec_tracer/reporters/html/eslint.config.js +62 -0
  47. data/lib/rspec_tracer/reporters/html/package-lock.json +4941 -0
  48. data/lib/rspec_tracer/reporters/html/package.json +29 -0
  49. data/lib/rspec_tracer/reporters/html/src/app.jsx +130 -0
  50. data/lib/rspec_tracer/reporters/html/src/components/AllExamples.jsx +86 -0
  51. data/lib/rspec_tracer/reporters/html/src/components/DuplicateExamples.jsx +68 -0
  52. data/lib/rspec_tracer/reporters/html/src/components/ExamplesDependency.jsx +78 -0
  53. data/lib/rspec_tracer/reporters/html/src/components/FilesDependency.jsx +72 -0
  54. data/lib/rspec_tracer/reporters/html/src/components/FlakyExamples.jsx +42 -0
  55. data/lib/rspec_tracer/reporters/html/src/components/ReportTable.jsx +131 -0
  56. data/lib/rspec_tracer/reporters/html/src/components/SearchBar.jsx +19 -0
  57. data/lib/rspec_tracer/reporters/html/src/index.html +23 -0
  58. data/lib/rspec_tracer/reporters/html/src/main.jsx +37 -0
  59. data/lib/rspec_tracer/reporters/html/src/styles.css +434 -0
  60. data/lib/rspec_tracer/reporters/html/vite.config.js +42 -0
  61. data/lib/rspec_tracer/reporters/html_reporter.rb +266 -0
  62. data/lib/rspec_tracer/reporters/json_reporter.rb +88 -0
  63. data/lib/rspec_tracer/reporters/payload_builder.rb +235 -0
  64. data/lib/rspec_tracer/reporters/registry.rb +120 -0
  65. data/lib/rspec_tracer/reporters/terminal_reporter.rb +264 -0
  66. data/lib/rspec_tracer/rspec/README.md +73 -0
  67. data/lib/rspec_tracer/rspec/installation.rb +97 -0
  68. data/lib/rspec_tracer/rspec/metadata.rb +96 -0
  69. data/lib/rspec_tracer/rspec/parallel_tests.rb +459 -0
  70. data/lib/rspec_tracer/rspec/reporter_hook.rb +84 -0
  71. data/lib/rspec_tracer/rspec/runner_hook.rb +178 -0
  72. data/lib/rspec_tracer/source_file.rb +24 -7
  73. data/lib/rspec_tracer/storage/README.md +35 -0
  74. data/lib/rspec_tracer/storage/backend.rb +68 -0
  75. data/lib/rspec_tracer/storage/json_backend.rb +866 -0
  76. data/lib/rspec_tracer/storage/lazy_snapshot.rb +65 -0
  77. data/lib/rspec_tracer/storage/schema.rb +43 -0
  78. data/lib/rspec_tracer/storage/serializer/json.rb +41 -0
  79. data/lib/rspec_tracer/storage/serializer/msgpack.rb +90 -0
  80. data/lib/rspec_tracer/storage/snapshot.rb +127 -0
  81. data/lib/rspec_tracer/storage/sqlite_backend.rb +686 -0
  82. data/lib/rspec_tracer/time_formatter.rb +37 -18
  83. data/lib/rspec_tracer/tracker/README.md +36 -0
  84. data/lib/rspec_tracer/tracker/coverage_adapter.rb +174 -0
  85. data/lib/rspec_tracer/tracker/declared_globs.rb +100 -0
  86. data/lib/rspec_tracer/tracker/dependency_graph.rb +134 -0
  87. data/lib/rspec_tracer/tracker/env_matcher.rb +127 -0
  88. data/lib/rspec_tracer/tracker/env_snapshot.rb +77 -0
  89. data/lib/rspec_tracer/tracker/example_registry.rb +153 -0
  90. data/lib/rspec_tracer/tracker/file_digest.rb +61 -0
  91. data/lib/rspec_tracer/tracker/filter.rb +127 -0
  92. data/lib/rspec_tracer/tracker/input.rb +99 -0
  93. data/lib/rspec_tracer/tracker/io_hooks/file.rb +55 -0
  94. data/lib/rspec_tracer/tracker/io_hooks/io.rb +24 -0
  95. data/lib/rspec_tracer/tracker/io_hooks/json.rb +23 -0
  96. data/lib/rspec_tracer/tracker/io_hooks/kernel.rb +26 -0
  97. data/lib/rspec_tracer/tracker/io_hooks/yaml.rb +38 -0
  98. data/lib/rspec_tracer/tracker/io_hooks.rb +195 -0
  99. data/lib/rspec_tracer/tracker/loaded_files_tracker.rb +295 -0
  100. data/lib/rspec_tracer/tracker/new_file_detector.rb +62 -0
  101. data/lib/rspec_tracer/tracker/whole_suite_invalidators.rb +96 -0
  102. data/lib/rspec_tracer/version.rb +4 -1
  103. data/lib/rspec_tracer.rb +232 -381
  104. metadata +93 -43
  105. data/lib/rspec_tracer/cache.rb +0 -207
  106. data/lib/rspec_tracer/coverage_merger.rb +0 -42
  107. data/lib/rspec_tracer/coverage_reporter.rb +0 -187
  108. data/lib/rspec_tracer/coverage_writer.rb +0 -58
  109. data/lib/rspec_tracer/html_reporter/Rakefile +0 -18
  110. data/lib/rspec_tracer/html_reporter/assets/javascripts/application.js +0 -56
  111. data/lib/rspec_tracer/html_reporter/assets/javascripts/libraries/jquery.js +0 -10881
  112. data/lib/rspec_tracer/html_reporter/assets/javascripts/plugins/datatables.js +0 -15381
  113. data/lib/rspec_tracer/html_reporter/assets/stylesheets/application.css +0 -196
  114. data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/datatables.css +0 -459
  115. data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/jquery-ui.css +0 -436
  116. data/lib/rspec_tracer/html_reporter/assets/stylesheets/print.css +0 -92
  117. data/lib/rspec_tracer/html_reporter/assets/stylesheets/reset.css +0 -265
  118. data/lib/rspec_tracer/html_reporter/public/application.css +0 -5
  119. data/lib/rspec_tracer/html_reporter/public/application.js +0 -6
  120. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc.png +0 -0
  121. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc_disabled.png +0 -0
  122. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_both.png +0 -0
  123. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc.png +0 -0
  124. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc_disabled.png +0 -0
  125. data/lib/rspec_tracer/html_reporter/public/favicon.png +0 -0
  126. data/lib/rspec_tracer/html_reporter/public/loading.gif +0 -0
  127. data/lib/rspec_tracer/html_reporter/reporter.rb +0 -242
  128. data/lib/rspec_tracer/html_reporter/views/duplicate_examples.erb +0 -34
  129. data/lib/rspec_tracer/html_reporter/views/examples.erb +0 -58
  130. data/lib/rspec_tracer/html_reporter/views/examples_dependency.erb +0 -36
  131. data/lib/rspec_tracer/html_reporter/views/files_dependency.erb +0 -36
  132. data/lib/rspec_tracer/html_reporter/views/flaky_examples.erb +0 -38
  133. data/lib/rspec_tracer/html_reporter/views/layout.erb +0 -38
  134. data/lib/rspec_tracer/remote_cache/aws.rb +0 -176
  135. data/lib/rspec_tracer/remote_cache/cache.rb +0 -75
  136. data/lib/rspec_tracer/remote_cache/repo.rb +0 -210
  137. data/lib/rspec_tracer/report_generator.rb +0 -158
  138. data/lib/rspec_tracer/report_merger.rb +0 -68
  139. data/lib/rspec_tracer/report_writer.rb +0 -141
  140. data/lib/rspec_tracer/reporter.rb +0 -204
  141. data/lib/rspec_tracer/rspec_reporter.rb +0 -41
  142. data/lib/rspec_tracer/rspec_runner.rb +0 -56
  143. data/lib/rspec_tracer/ruby_coverage.rb +0 -9
  144. data/lib/rspec_tracer/runner.rb +0 -278
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'snapshot'
4
+
5
+ module RSpecTracer
6
+ module Storage
7
+ # Lazy-reading view returned by `JsonBackend#load_graph`. Presents
8
+ # the same field surface as `Storage::Snapshot` but defers the disk
9
+ # read + deserialization for each field until the caller touches it.
10
+ #
11
+ # Rationale: on a large cache (100 MB+), eager-reading every file
12
+ # at setup dominates warm-run startup (see issue #17 / B12). Engine
13
+ # at setup touches 10/15 fields; reporters never touch the previous
14
+ # snapshot; third-party tooling often reads one or two. The 3-5
15
+ # fields the Engine does NOT touch at setup (duplicate_examples,
16
+ # reverse_dependency, env_dependency; examples_coverage is deferred
17
+ # to finalize per the seed refactor) save their full disk+parse
18
+ # cost for every run.
19
+ #
20
+ # Duck-types `Snapshot`: every Struct member becomes a method here,
21
+ # `respond_to?` returns true for each, `to_h` materializes the
22
+ # whole thing. Callers doing `prev.send(field)` or `prev.field`
23
+ # work unchanged.
24
+ #
25
+ # Thread-safety: the memoization Hash is not guarded. Engine is
26
+ # single-threaded per run; parallel_tests workers each own their
27
+ # own LazySnapshot instance. If a future caller reads fields from
28
+ # multiple threads, wrap with a Monitor at construct time.
29
+ class LazySnapshot
30
+ LAZY_FIELDS = (Snapshot.members - %i[schema_version run_id]).freeze
31
+
32
+ attr_reader :schema_version, :run_id
33
+
34
+ def initialize(schema_version:, run_id:, reader:)
35
+ @schema_version = schema_version
36
+ @run_id = run_id
37
+ @reader = reader
38
+ @loaded = {}
39
+ end
40
+
41
+ LAZY_FIELDS.each do |field|
42
+ define_method(field) do
43
+ return @loaded[field] if @loaded.key?(field)
44
+
45
+ @loaded[field] = @reader.read(field)
46
+ end
47
+ end
48
+
49
+ # Hash view matching `Struct#to_h` so callers composing snapshots
50
+ # (merge pipelines, reporter helpers) get the familiar shape.
51
+ # Forces every field to materialize, so only call this when the
52
+ # full cache is genuinely required.
53
+ def to_h
54
+ Snapshot.members.to_h { |m| [m, public_send(m)] }
55
+ end
56
+
57
+ # Materialize a full eager Snapshot. Use when an API requires the
58
+ # Struct type (Merger input, backends that accept Snapshot on
59
+ # save). Reads every field.
60
+ def to_snapshot
61
+ Snapshot.new(**to_h)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ # Internal Storage — see {RSpecTracer} for the user-facing surface.
5
+ # @api private
6
+ module Storage
7
+ # Cache schema version and compatibility policy. 1.x shipped caches
8
+ # without any version stamp; 2.0's first migration is simply to
9
+ # start emitting a version and refuse to load anything else.
10
+ #
11
+ # Compatibility rule (from ARCHITECTURE.md):
12
+ # - `CURRENT` is written into every new cache manifest.
13
+ # - `SUPPORTED` is the set of versions this backend is willing
14
+ # to load. For 2.0 there is exactly one supported version.
15
+ # - On mismatch, `load_graph` returns nil after an `info` log
16
+ # line and the run proceeds cold. No in-place migrators.
17
+ #
18
+ # Future version bumps (schema_version 3, 4, ...) add entries to
19
+ # `SUPPORTED` only if the backend can load both shapes. If a
20
+ # change is breaking, `SUPPORTED` resets to `[CURRENT]` and the
21
+ # caller pays one cold run on upgrade - the deal 1.x users already
22
+ # expect for any rspec-tracer version bump.
23
+ module Schema
24
+ # schema_version 2 was the first versioned schema (1.x was
25
+ # unstamped). 2.0 adds `Snapshot.boot_set` - breaking for any
26
+ # reader that assumed the v2 field list - so CURRENT bumps to 3
27
+ # and SUPPORTED narrows to only the new version. No v2 caches
28
+ # were ever persisted in user land.
29
+ CURRENT = 3
30
+ # Internal constant.
31
+ # @api private
32
+ SUPPORTED = [CURRENT].freeze
33
+
34
+ # True when the caller can load a cache stamped with `version`.
35
+ # nil (an unstamped 1.x cache) is explicitly unsupported -
36
+ # treating nil as "compatible" would defeat the whole point of
37
+ # the version field. The caller logs and falls back to cold run.
38
+ def self.supported?(version)
39
+ SUPPORTED.include?(version)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module RSpecTracer
6
+ # Internal Storage — see {RSpecTracer} for the user-facing surface.
7
+ # @api private
8
+ module Storage
9
+ # Internal Serializer — see {RSpecTracer} for the user-facing surface.
10
+ # @api private
11
+ module Serializer
12
+ # Default serializer for JsonBackend. Produces pretty-printed
13
+ # JSON strings; reads tolerate binary-mode bytes by forcing
14
+ # UTF-8 on decode (preserves the fix for example titles with
15
+ # non-ASCII bytes on US-ASCII-defaulted filesystems).
16
+ #
17
+ # Class-level methods (not module_function) so mutant-rspec can
18
+ # observe mutations through the call path; see the mutation-
19
+ # friendly-modules memo.
20
+ class Json
21
+ # Internal helper for the tracer pipeline.
22
+ # @api private
23
+ def self.extension
24
+ 'json'
25
+ end
26
+
27
+ # Internal helper for the tracer pipeline.
28
+ # @api private
29
+ def self.encode(payload)
30
+ ::JSON.pretty_generate(payload)
31
+ end
32
+
33
+ # Internal helper for the tracer pipeline.
34
+ # @api private
35
+ def self.decode(bytes)
36
+ ::JSON.parse(bytes.dup.force_encoding('UTF-8'))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+
5
+ module RSpecTracer
6
+ # Internal Storage — see {RSpecTracer} for the user-facing surface.
7
+ # @api private
8
+ module Storage
9
+ # Internal Serializer — see {RSpecTracer} for the user-facing surface.
10
+ # @api private
11
+ module Serializer
12
+ # Raised when the caller asks for :msgpack but the msgpack gem
13
+ # is not in the user's bundle. JsonBackend rescues at construct
14
+ # time + falls back to the Json serializer with a warn line,
15
+ # same optional-dep pattern the RedisBackend uses for the redis
16
+ # gem.
17
+ class MsgpackGemNotInstalled < StandardError; end
18
+
19
+ # MessagePack + zlib serializer. msgpack encode is ~2x smaller
20
+ # than pretty JSON on our representative dependency graphs;
21
+ # zlib deflate then buys another ~5x on path-repetitive
22
+ # payloads (dependency.json is the big one - the same path
23
+ # string appears once per example that touched it). Combined
24
+ # ratio matches the ~4-6x claim in remote_cache/archive.rb.
25
+ #
26
+ # Stdlib zlib avoids adding a second gem dep; msgpack itself is
27
+ # the single new dev-group gem. Users who want the backend
28
+ # add `gem 'msgpack'` to their own Gemfile per
29
+ # USER_FACING_SURFACE.md optional-dep convention.
30
+ #
31
+ # The require is lazy so pure-Ruby suites that stay on :json
32
+ # do not pay the msgpack load cost and so the LoadError path
33
+ # is exercisable in unit specs (hide_const-based).
34
+ class Msgpack
35
+ # Internal helper for the tracer pipeline.
36
+ # @api private
37
+ def self.extension
38
+ 'msgpack.gz'
39
+ end
40
+
41
+ # Internal helper for the tracer pipeline.
42
+ # @api private
43
+ def self.encode(payload)
44
+ ensure_available!
45
+ ::Zlib::Deflate.deflate(::MessagePack.pack(payload))
46
+ end
47
+
48
+ # Internal helper for the tracer pipeline.
49
+ # @api private
50
+ def self.decode(bytes)
51
+ ensure_available!
52
+ ::MessagePack.unpack(::Zlib::Inflate.inflate(bytes))
53
+ end
54
+
55
+ # Probe used by JsonBackend#initialize to decide whether to
56
+ # fall back to the Json serializer at construct time. True
57
+ # iff `require 'msgpack'` succeeds; false when the gem is
58
+ # missing. Idempotent without an explicit memo because Ruby's
59
+ # `require` short-circuits via `$LOADED_FEATURES` on repeat
60
+ # calls and `defined?(::MessagePack)` cheaply detects the
61
+ # post-load constant. The previous @msgpack_loaded ivar memo
62
+ # was load-bearing for mutation observability: once any prior
63
+ # test tripped the memo, mutations on the require line were
64
+ # observably equivalent.
65
+ def self.available?
66
+ ensure_available!
67
+ true
68
+ rescue MsgpackGemNotInstalled
69
+ false
70
+ end
71
+
72
+ class << self
73
+ private
74
+
75
+ # Internal method on the tracer pipeline.
76
+ # @api private
77
+ def ensure_available!
78
+ return if defined?(::MessagePack)
79
+
80
+ require 'msgpack'
81
+ rescue ::LoadError
82
+ raise MsgpackGemNotInstalled,
83
+ "msgpack gem is not installed; add `gem 'msgpack'` to your Gemfile " \
84
+ 'to use the :msgpack serializer.'
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module RSpecTracer
6
+ # Internal Storage — see {RSpecTracer} for the user-facing surface.
7
+ # @api private
8
+ module Storage
9
+ # Value object returned by `Backend#load_graph` and accepted by
10
+ # `Backend#save_graph`. Bundles every collection that 1.x's
11
+ # report_writer persists plus the schema_version + run_id envelope
12
+ # so a full cache can be reconstructed in one trip.
13
+ #
14
+ # The field layout mirrors 1.x's on-disk contract (see
15
+ # `JsonBackend::FILENAMES`); every field round-trips through
16
+ # `to_h` / `from_h` so the backend can serialize without reaching
17
+ # into struct internals.
18
+ #
19
+ # `examples_coverage` may be `nil` when a caller explicitly loads
20
+ # the cheap header only. The default load is eager - `nil` vs `{}`
21
+ # distinguishes "not yet loaded" from "loaded and empty."
22
+ #
23
+ # Methods are defined on the reopened class body (not inside the
24
+ # Struct.new block) so mutant can introspect them - same pattern
25
+ # as Tracker::Input.
26
+ #
27
+ # `boot_set` - Hash[relative_path => sha256_hex] of every project
28
+ # file loaded before any example runs (spec_helper requires, gem
29
+ # boot, eager autoload). Backs the constants-blind-spot fix: the
30
+ # engine compares this against the previous run's boot_set and ORs
31
+ # any mismatch with WholeSuiteInvalidators when computing the
32
+ # whole_suite_invalidated bool. Per-example loaded-set attribution
33
+ # folds into `dependency` via the existing graph registration path
34
+ # - no separate field.
35
+ #
36
+ # `wsi_snapshot` - Hash[watch_name => sha256_hex] produced by
37
+ # `WholeSuiteInvalidators#digest_snapshot`. Without it, a warm run
38
+ # can't tell whether Gemfile.lock / .ruby-version / .rspec-tracer
39
+ # (or the tracer gem identity) changed since the previous run, and
40
+ # the engine falls back to "first run = invalidate everything" on
41
+ # every warm run. The field is optional in the JSON layout so older
42
+ # caches continue to load (missing wsi.json coerces to `{}`, which
43
+ # compares unequal and triggers one cold re-run - safe fallback,
44
+ # same cost as any other cache miss).
45
+ #
46
+ # `env_snapshot` - Hash[env_name => md5_hex] produced by
47
+ # `Tracker::EnvSnapshot#digest_snapshot`. Covers env-var values
48
+ # declared via the per-example `tracks: { env: ... }` DSL.
49
+ # Without it, a warm run can't tell whether an env-gated example
50
+ # needs to re-run when the env changes. Same optional-in-JSON
51
+ # treatment as wsi_snapshot: missing file coerces to `{}`, no
52
+ # schema_version bump, one cold re-run on upgrade.
53
+ #
54
+ # `env_dependency` - Hash[example_id => Array<env_name>] capturing
55
+ # which env keys each tracked example declared. The per-run
56
+ # `env_snapshot` stores the digest of each key; this map stores
57
+ # the example-to-key attribution that the reporter layer needs to
58
+ # render "which env vars does this example depend on." Without it,
59
+ # reports can't surface env dependencies - Engine's per-run
60
+ # `@tracks_env` map would otherwise be lost at finalize.
61
+ # Same optional-in-JSON treatment as wsi_snapshot / env_snapshot:
62
+ # missing file coerces to `{}`, no schema_version bump.
63
+ #
64
+ # `cache_hit_reason` is a SUITE-LEVEL aggregate. It maps
65
+ # `reason_string => count` (e.g. `{"Files changed" => 12,
66
+ # "No cache" => 5}`) and surfaces "why did each non-skipped
67
+ # example run." JsonBackend persists it as `cache_hit_reason.json`
68
+ # under the per-run dir (same shape as wsi_snapshot / env_snapshot:
69
+ # one file per field; missing file coerces to `{}`, no
70
+ # schema_version bump). SqliteBackend does not persist it (would
71
+ # require a meta-table column / schema bump); SqliteBackend users
72
+ # see the field as `{}` on read until a future enhancement
73
+ # extends the meta table.
74
+ Snapshot = Struct.new(
75
+ :schema_version,
76
+ :run_id,
77
+ :all_examples,
78
+ :duplicate_examples,
79
+ :interrupted_examples,
80
+ :flaky_examples,
81
+ :failed_examples,
82
+ :pending_examples,
83
+ :skipped_examples,
84
+ :all_files,
85
+ :dependency,
86
+ :reverse_dependency,
87
+ :examples_coverage,
88
+ :boot_set,
89
+ :wsi_snapshot,
90
+ :env_snapshot,
91
+ :env_dependency,
92
+ :cache_hit_reason,
93
+ keyword_init: true
94
+ )
95
+
96
+ # Internal Snapshot — see {RSpecTracer} for the user-facing surface.
97
+ # @api private
98
+ class Snapshot
99
+ # Defaults every collection to its 1.x starting shape: Hash for
100
+ # keyed collections, Set for example-id lists. Keeps spec
101
+ # construction terse and prevents accidental nil-deref when a
102
+ # save is composed incrementally.
103
+ def self.empty(schema_version:, run_id:)
104
+ new(
105
+ schema_version: schema_version,
106
+ run_id: run_id,
107
+ all_examples: {},
108
+ duplicate_examples: {},
109
+ interrupted_examples: Set.new,
110
+ flaky_examples: Set.new,
111
+ failed_examples: Set.new,
112
+ pending_examples: Set.new,
113
+ skipped_examples: Set.new,
114
+ all_files: {},
115
+ dependency: {},
116
+ reverse_dependency: {},
117
+ examples_coverage: {},
118
+ boot_set: {},
119
+ wsi_snapshot: {},
120
+ env_snapshot: {},
121
+ env_dependency: {},
122
+ cache_hit_reason: {}
123
+ )
124
+ end
125
+ end
126
+ end
127
+ end