rspec-tracer 2.0.0.pre.1 → 2.0.0.pre.2
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 +4 -4
- data/CHANGELOG.md +190 -0
- data/README.md +25 -10
- data/lib/rspec_tracer/cli/cache_clear.rb +17 -4
- data/lib/rspec_tracer/cli/cache_info.rb +23 -22
- data/lib/rspec_tracer/cli/doctor.rb +23 -14
- data/lib/rspec_tracer/cli/explain.rb +59 -49
- data/lib/rspec_tracer/configuration.rb +101 -5
- data/lib/rspec_tracer/engine.rb +159 -67
- data/lib/rspec_tracer/example.rb +128 -13
- data/lib/rspec_tracer/remote_cache/user_tasks.rb +47 -8
- data/lib/rspec_tracer/reporters/html/package.json +1 -1
- data/lib/rspec_tracer/rspec/runner_hook.rb +70 -9
- data/lib/rspec_tracer/storage/backend.rb +62 -0
- data/lib/rspec_tracer/storage/json_backend.rb +32 -14
- data/lib/rspec_tracer/storage/lazy_snapshot.rb +1 -1
- data/lib/rspec_tracer/storage/schema.rb +13 -6
- data/lib/rspec_tracer/storage/serializer/msgpack.rb +81 -4
- data/lib/rspec_tracer/storage/snapshot.rb +15 -1
- data/lib/rspec_tracer/storage/sqlite_backend.rb +12 -5
- data/lib/rspec_tracer/tracker/loaded_files_tracker.rb +1 -1
- data/lib/rspec_tracer/tracker/new_file_detector.rb +3 -3
- data/lib/rspec_tracer/version.rb +1 -1
- data/lib/rspec_tracer.rb +8 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 552b92475298cfa692f8748b7b7097b12a82a2e8b1003848d1194fe560c0b385
|
|
4
|
+
data.tar.gz: 34a9d94756083aaf46220460c4d10002d30992703c1aad0e417640ad5b2d79ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57b510bf3f8da2071a72ee222d598e9ce8042b5745cec84e053ea7b7db87e203e73829f890f105d934901835813cee66290ff0b24a07f278b1821c3b75febdb7
|
|
7
|
+
data.tar.gz: 57adb9424e1bdc3775294de9573726f3b80bea35615d6a41410985724d930faa00c984bf3c9b637297565fa11bf72c938bb92ce8464b7968c9be9fa5b476b2f2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,193 @@
|
|
|
1
|
+
## [2.0.0.pre.2] - 2026-05-16
|
|
2
|
+
|
|
3
|
+
Bug-fix + interop release after the field-test pass that followed
|
|
4
|
+
`v2.0.0.pre.1`. 15 issues filed publicly
|
|
5
|
+
([#182](https://github.com/avmnu-sng/rspec-tracer/issues/182)–[#196](https://github.com/avmnu-sng/rspec-tracer/issues/196))
|
|
6
|
+
plus two follow-on findings
|
|
7
|
+
([#210](https://github.com/avmnu-sng/rspec-tracer/issues/210) and
|
|
8
|
+
[#218](https://github.com/avmnu-sng/rspec-tracer/issues/218))
|
|
9
|
+
surfaced during fix-verification; all 17 are closed at tag. No CI
|
|
10
|
+
surface drops, no Ruby / Rails / RSpec floor changes.
|
|
11
|
+
|
|
12
|
+
The cumulative cache `schema_version` path is `3 → 5` (two bumps
|
|
13
|
+
across [#209](https://github.com/avmnu-sng/rspec-tracer/pull/209)
|
|
14
|
+
and [#211](https://github.com/avmnu-sng/rspec-tracer/pull/211));
|
|
15
|
+
a pre.1 cache cold-loads cleanly on the pre.2 upgrade in one cold
|
|
16
|
+
run, then warm caches resume. See [`UPGRADING.md`](UPGRADING.md).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`coverage_modes` config DSL** for the standalone Coverage
|
|
21
|
+
path (no SimpleCov). Pass any subset of
|
|
22
|
+
`[:lines, :branches, :methods, :oneshot_lines, :eval]`; default
|
|
23
|
+
`[:lines]` keeps byte-compatibility with prior runs. Threaded
|
|
24
|
+
through both `RSpecTracer.setup_coverage` and
|
|
25
|
+
`Engine#ensure_coverage_started`; inert when SimpleCov drives
|
|
26
|
+
Coverage. New COOKBOOK recipe "Coverage modes (rspec-tracer +
|
|
27
|
+
SimpleCov interop)" under recipe 9 documents the per-mode
|
|
28
|
+
interop matrix.
|
|
29
|
+
- **`bin/rspec-tracer cache:clear --force` / `-f`** as a synonym
|
|
30
|
+
for `--yes` / `-y`. Matches the common Unix-CLI convention.
|
|
31
|
+
- **COOKBOOK recipe for the `:msgpack` serializer** documenting
|
|
32
|
+
the `storage_backend :json, serializer: :msgpack` option for
|
|
33
|
+
~3.5× smaller caches than `:json` on dependency-heavy suites.
|
|
34
|
+
Notes that `.msgpack.gz` payloads are raw `Zlib::Deflate`
|
|
35
|
+
streams (not gzip format) — the suffix is cosmetic and may
|
|
36
|
+
change in a future major release.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Cache `schema_version` bump 3 → 5** (cumulative). The
|
|
41
|
+
`example_id` digest now drops the load-order-dependent
|
|
42
|
+
generated-class-name suffix and the line-number fields, and
|
|
43
|
+
substitutes a positional discriminator for unnamed
|
|
44
|
+
`it { }` / `specify { }` / `example { }` examples that
|
|
45
|
+
previously picked up RSpec's `"example at <path>:<line>"`
|
|
46
|
+
description fallback. First run on pre.2 is cold; subsequent
|
|
47
|
+
runs return to warm. See [`UPGRADING.md`](UPGRADING.md)
|
|
48
|
+
"Schema-version cold runs."
|
|
49
|
+
- **Duplicate-example-identity detection now prune-and-continue.**
|
|
50
|
+
When the runner detects two examples with the same identity,
|
|
51
|
+
it drops the colliders from the run and lets the rest of the
|
|
52
|
+
suite proceed, instead of aborting the entire run to zero
|
|
53
|
+
examples. `fail_on_duplicates` becomes purely an exit-code
|
|
54
|
+
lever — the non-colliding remainder always runs. The error log
|
|
55
|
+
names the colliding examples (file:line + description) with a
|
|
56
|
+
remediation hint. See [`UPGRADING.md`](UPGRADING.md)
|
|
57
|
+
"Duplicate example identities."
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **Restored flaky-test detection across runs.** A top-line
|
|
62
|
+
README feature present in 1.x since v1.0.0; silently dropped
|
|
63
|
+
in the 2.0 rewrite — the registry `:flaky` status, the
|
|
64
|
+
`:flaky_example` filter reason, the `flaky_examples` snapshot
|
|
65
|
+
field, and the HTML reporter's Flaky tab were all retained,
|
|
66
|
+
but no production code path transitioned an example into
|
|
67
|
+
`:flaky`. `on_example_passed` now promotes a
|
|
68
|
+
previously-failed-or-flaky example into `:flaky`;
|
|
69
|
+
`on_example_failed` keeps a previously-flaky example sticky.
|
|
70
|
+
Closes [#194](https://github.com/avmnu-sng/rspec-tracer/issues/194).
|
|
71
|
+
- **`run_reason` field in `report.json` (and the terminal
|
|
72
|
+
`by reason:` line) now persists the correct reason on warm
|
|
73
|
+
runs for every reason path** — `Failed previously`,
|
|
74
|
+
`Pending previously`, `Interrupted previously`, `Files changed`,
|
|
75
|
+
`Environment changed`. Previously displayed as `No cache` on
|
|
76
|
+
every warm-run case because `Engine#register_example`
|
|
77
|
+
short-circuited on the entry already seeded from the previous
|
|
78
|
+
snapshot. Single-character fix closing all five reason paths.
|
|
79
|
+
Closes [#186](https://github.com/avmnu-sng/rspec-tracer/issues/186).
|
|
80
|
+
- **Parallel-`tests` `cache_hit_reason` counts no longer inflated
|
|
81
|
+
by worker count.** Each worker independently computed an
|
|
82
|
+
identical `filtered_examples` hash against the global
|
|
83
|
+
previous-run snapshot; the pre-fix sum-merge inflated
|
|
84
|
+
always-re-run buckets N-fold. The merge now keys on
|
|
85
|
+
`example_id` (first-write-wins), then re-tallies. Closes
|
|
86
|
+
[#193](https://github.com/avmnu-sng/rspec-tracer/issues/193).
|
|
87
|
+
- **`example_id` stable across runs when multiple files share a
|
|
88
|
+
`describe` name** (the load-order-dependent
|
|
89
|
+
`RSpec::ExampleGroups::Name_N` disambiguator suffix is no
|
|
90
|
+
longer in the digest) **and stable across line-shift edits for
|
|
91
|
+
unnamed one-liner examples** (`it { is_expected.to eq(7) }`,
|
|
92
|
+
`specify { ... }`, `example { ... }`). Long-standing bugs since
|
|
93
|
+
v1.0.0; pervasive in shoulda-matchers model specs which are
|
|
94
|
+
almost entirely one-liner matcher syntax. Closes
|
|
95
|
+
[#196](https://github.com/avmnu-sng/rspec-tracer/issues/196)
|
|
96
|
+
+ [#210](https://github.com/avmnu-sng/rspec-tracer/issues/210).
|
|
97
|
+
- **NPE in `RSpec.world.example_count` for suites with
|
|
98
|
+
intermediate describe groups after rspec-tracer drops a
|
|
99
|
+
duplicate-identity example.** Companion fix to the
|
|
100
|
+
duplicate-detection prune-and-continue redesign above — the
|
|
101
|
+
kept-map needed a default block so descendants of an
|
|
102
|
+
intermediate describe (a describe containing only nested
|
|
103
|
+
describes, no direct `it`s) resolve to an empty array on the
|
|
104
|
+
`filtered_examples` lookup instead of `nil`. Closes
|
|
105
|
+
[#218](https://github.com/avmnu-sng/rspec-tracer/issues/218).
|
|
106
|
+
- **`storage_backend :json, serializer: :msgpack` no longer
|
|
107
|
+
crashes on `Time` values** (and `Symbol` values now round-trip
|
|
108
|
+
losslessly across the cache). Registered
|
|
109
|
+
`MessagePack::Factory` type extensions for `Time` (12-byte
|
|
110
|
+
`tv_sec + tv_nsec`, UTC-canonicalized on decode) and `Symbol`
|
|
111
|
+
(UTF-8 body). Users who followed rspec-tracer's own 50 MiB
|
|
112
|
+
cache warning's `:msgpack` recommendation no longer brick
|
|
113
|
+
their cache silently on the first run that writes a `Time`.
|
|
114
|
+
Closes [#182](https://github.com/avmnu-sng/rspec-tracer/issues/182).
|
|
115
|
+
- **`bin/rspec-tracer cache:info` and `explain` now compose with
|
|
116
|
+
`storage_backend :sqlite`.** Previously hardcoded the
|
|
117
|
+
JsonBackend on-disk layout and reported `no last_run.json yet`
|
|
118
|
+
on every sqlite run, even after a successful rspec. Both CLI
|
|
119
|
+
sub-commands now dispatch through a shared
|
|
120
|
+
`Storage::Backend.build` factory; sqlite metadata-table reads
|
|
121
|
+
surface alongside JSON manifest reads behind the same
|
|
122
|
+
protocol. Closes
|
|
123
|
+
[#183](https://github.com/avmnu-sng/rspec-tracer/issues/183).
|
|
124
|
+
- **`bin/rspec-tracer doctor` no longer false-reports `SimpleCov:
|
|
125
|
+
not loaded` / `Rails: not loaded`** when those gems ARE in the
|
|
126
|
+
Gemfile (doctor runs in its own process; app code doesn't load
|
|
127
|
+
there). Three states are now reported: loaded-in-this-process
|
|
128
|
+
(`OK`), installed-but-not-loaded
|
|
129
|
+
(`INFO ... installed (<version>; not loaded in doctor's process)`),
|
|
130
|
+
and not-installed (`INFO ... not installed`). Closes
|
|
131
|
+
[#184](https://github.com/avmnu-sng/rspec-tracer/issues/184).
|
|
132
|
+
- **`bin/rspec-tracer` invocation guidance flipped to
|
|
133
|
+
`bundle exec rspec-tracer`** across README, COOKBOOK, and
|
|
134
|
+
UPGRADING. The bare `bin/rspec-tracer` form required users to
|
|
135
|
+
run `bundle binstubs rspec-tracer` first; `bundle exec
|
|
136
|
+
rspec-tracer` works out of the box. Closes
|
|
137
|
+
[#185](https://github.com/avmnu-sng/rspec-tracer/issues/185).
|
|
138
|
+
- **`reports_s3_path` deprecation warning no longer false-flags
|
|
139
|
+
on the probe path** — when no `remote_cache_backend` /
|
|
140
|
+
`remote_cache_uri` is configured AND the user runs
|
|
141
|
+
`rake rspec_tracer:remote_cache:download` / `:upload` /
|
|
142
|
+
`:prune_all`. A new non-warning predicate gates the probe; the
|
|
143
|
+
deprecation now fires only on legitimate use of the legacy
|
|
144
|
+
DSL. Closes
|
|
145
|
+
[#187](https://github.com/avmnu-sng/rspec-tracer/issues/187).
|
|
146
|
+
- **`remote_cache` success now emits visible INFO lines** for
|
|
147
|
+
`download!` (`restored cache from <ref>` — with a
|
|
148
|
+
`(cross-branch fallback)` qualifier when a PR-tier download
|
|
149
|
+
falls through to a commit-ancestry ref successfully),
|
|
150
|
+
`upload!` (`uploaded cache to <ref>`), and `prune_all!`
|
|
151
|
+
(`prune_all removed N refs`). One fix covers all three
|
|
152
|
+
backends (s3 / redis / file) + the cron-driven `prune_all`
|
|
153
|
+
admin task. Closes
|
|
154
|
+
[#188](https://github.com/avmnu-sng/rspec-tracer/issues/188).
|
|
155
|
+
- **`track_ar_schema_notifications` now installs correctly under
|
|
156
|
+
the canonical README setup order** (`RSpecTracer.start` BEFORE
|
|
157
|
+
`require_relative '../config/environment'`). Previously,
|
|
158
|
+
`defined?(::Rails::VERSION)` was false at engine.setup time
|
|
159
|
+
and the entire Rails-observer install path short-circuited —
|
|
160
|
+
the `sql.active_record` subscriber never attached AND the
|
|
161
|
+
documented `use_transactional_fixtures`-widening warn never
|
|
162
|
+
fired. Now late-binds via a `before(:suite)` hook that
|
|
163
|
+
re-checks Rails-loaded state after `rails_helper.rb` has
|
|
164
|
+
required the environment. Closes
|
|
165
|
+
[#192](https://github.com/avmnu-sng/rspec-tracer/issues/192).
|
|
166
|
+
- **`RSpecTracer.start` no longer crashes** when the user
|
|
167
|
+
pre-starts `::Coverage` (e.g. to opt into branch coverage for
|
|
168
|
+
SimpleCov-free runs). The `setup_coverage` entry point now
|
|
169
|
+
matches `Engine#ensure_coverage_started`'s `Coverage.running?`
|
|
170
|
+
guard + `RuntimeError` rescue. Closes
|
|
171
|
+
[#195](https://github.com/avmnu-sng/rspec-tracer/issues/195).
|
|
172
|
+
- **`InvalidUsageError` raised on conflicting
|
|
173
|
+
`remote_cache_backend` / `remote_cache_uri` configuration now
|
|
174
|
+
names both DSLs and explains they are alternatives.** The
|
|
175
|
+
previous `<dsl> already configured` message was confusing when
|
|
176
|
+
the user only typed `remote_cache_uri` (which dispatches
|
|
177
|
+
internally to `remote_cache_backend`).
|
|
178
|
+
- **README per-example-precision section now covers Rails
|
|
179
|
+
engines.** An engine's own `lib/` is `require`d at gem-load
|
|
180
|
+
time via the Gemfile.lock cascade and lands in the boot set
|
|
181
|
+
**regardless of `eager_load`**. COOKBOOK gains a
|
|
182
|
+
`transitive_load_tracking false` opt-out recipe with the
|
|
183
|
+
trade-off documented. Closes
|
|
184
|
+
[#189](https://github.com/avmnu-sng/rspec-tracer/issues/189).
|
|
185
|
+
- **Engine + dummy-app two-rspec-summary explainer** added to
|
|
186
|
+
COOKBOOK — clarifies which of the two terminal summary totals
|
|
187
|
+
is authoritative when an engine fixture invokes RSpec against
|
|
188
|
+
its dummy app. Closes
|
|
189
|
+
[#190](https://github.com/avmnu-sng/rspec-tracer/issues/190).
|
|
190
|
+
|
|
1
191
|
## [2.0.0.pre.1] - 2026-05-06
|
|
2
192
|
|
|
3
193
|
The first pre-release of the 2.0 line. Architecture rewrite around
|
data/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Rails 8.0 needs Ruby 3.2+. JRuby 9.4 is supported.
|
|
|
48
48
|
```ruby
|
|
49
49
|
# 2.0 is in pre-release. Pin to the pre-release version explicitly;
|
|
50
50
|
# switch to '~> 2.0' once 2.0.0 final ships.
|
|
51
|
-
gem 'rspec-tracer', '= 2.0.0.pre.
|
|
51
|
+
gem 'rspec-tracer', '= 2.0.0.pre.2', group: :test, require: false
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
`bundle install` will resolve the pre-release version. You can
|
|
@@ -144,6 +144,17 @@ eager-loaded test environments — see
|
|
|
144
144
|
[`CHANGELOG.md`](CHANGELOG.md) "Deferred to 2.1" for the planned
|
|
145
145
|
contract.
|
|
146
146
|
|
|
147
|
+
**Rails engines:** a gem-loaded engine's own `lib/` files are
|
|
148
|
+
`require`d at gem-load time via the Gemfile.lock cascade and land
|
|
149
|
+
in the boot set **regardless of `eager_load`**. Editing them
|
|
150
|
+
re-runs every example — same SAFE-but-coarser shape as the
|
|
151
|
+
`eager_load = true` case above. The rationale (closing the
|
|
152
|
+
constants-lookup blind spot) lives in
|
|
153
|
+
[`lib/rspec_tracer/tracker/loaded_files_tracker.rb`](lib/rspec_tracer/tracker/loaded_files_tracker.rb).
|
|
154
|
+
Teams that want tighter per-example precision and accept the blind
|
|
155
|
+
spot can set `transitive_load_tracking false` — see
|
|
156
|
+
[`COOKBOOK.md`](COOKBOOK.md) recipe 2 for the trade-off.
|
|
157
|
+
|
|
147
158
|
## Per-example `tracks:` DSL
|
|
148
159
|
|
|
149
160
|
Annotate any describe / context / example with extra inputs the
|
|
@@ -365,19 +376,23 @@ Or override per-run via env: `RSPEC_TRACER_STORAGE=sqlite`.
|
|
|
365
376
|
|
|
366
377
|
## Command-line tools
|
|
367
378
|
|
|
368
|
-
`
|
|
379
|
+
`rspec-tracer` exposes five sub-commands. Run them via Bundler so the
|
|
380
|
+
gem's executable resolves cleanly without needing `bundle binstubs
|
|
381
|
+
rspec-tracer` first:
|
|
369
382
|
|
|
370
383
|
```sh
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
384
|
+
bundle exec rspec-tracer doctor # diagnose config + environment
|
|
385
|
+
bundle exec rspec-tracer cache:info # size, last run, invalidation stats
|
|
386
|
+
bundle exec rspec-tracer cache:clear # rm cache dirs
|
|
387
|
+
bundle exec rspec-tracer report:open # open the HTML report
|
|
388
|
+
bundle exec rspec-tracer explain <id> # why is <example_id> scheduled to (re-)run?
|
|
376
389
|
```
|
|
377
390
|
|
|
378
|
-
|
|
379
|
-
`
|
|
380
|
-
|
|
391
|
+
Generated binstubs (`bin/rspec-tracer …`) work too once you've run
|
|
392
|
+
`bundle binstubs rspec-tracer` in your project. The CLI is opt-in for
|
|
393
|
+
local-dev convenience; the `rake rspec_tracer:remote_cache:*` tasks
|
|
394
|
+
remain first-class for CI integration — nothing in the CLI replaces
|
|
395
|
+
them.
|
|
381
396
|
|
|
382
397
|
## SimpleCov interop
|
|
383
398
|
|
|
@@ -23,8 +23,7 @@ module RSpecTracer
|
|
|
23
23
|
return nothing_to_remove(stdout) if existing.empty?
|
|
24
24
|
|
|
25
25
|
announce(stdout, existing)
|
|
26
|
-
|
|
27
|
-
return aborted(stdout) unless force || confirm?(stdout)
|
|
26
|
+
return aborted(stdout) unless skip_confirmation?(args) || confirm?(stdout)
|
|
28
27
|
|
|
29
28
|
remove_each(stdout, existing)
|
|
30
29
|
0
|
|
@@ -33,6 +32,19 @@ module RSpecTracer
|
|
|
33
32
|
1
|
|
34
33
|
end
|
|
35
34
|
|
|
35
|
+
# Returns true when any of the documented skip-confirmation
|
|
36
|
+
# flags is present. `--yes` / `-y` is the canonical form;
|
|
37
|
+
# `--force` / `-f` is the Unix-conventional synonym accepted
|
|
38
|
+
# so users' muscle memory works. Either form skips the
|
|
39
|
+
# interactive `Proceed? [y/N]` prompt.
|
|
40
|
+
# @api private
|
|
41
|
+
SKIP_CONFIRMATION_FLAGS = %w[--yes -y --force -f].freeze
|
|
42
|
+
|
|
43
|
+
# @api private
|
|
44
|
+
def self.skip_confirmation?(args)
|
|
45
|
+
args.any? { |arg| SKIP_CONFIRMATION_FLAGS.include?(arg) }
|
|
46
|
+
end
|
|
47
|
+
|
|
36
48
|
# Internal helper for the tracer pipeline.
|
|
37
49
|
# @api private
|
|
38
50
|
def self.existing_targets
|
|
@@ -83,13 +95,14 @@ module RSpecTracer
|
|
|
83
95
|
# @api private
|
|
84
96
|
def self.print_help(stdout)
|
|
85
97
|
stdout.puts <<~HELP
|
|
86
|
-
Usage: rspec-tracer cache:clear [--yes]
|
|
98
|
+
Usage: rspec-tracer cache:clear [--yes | --force]
|
|
87
99
|
|
|
88
100
|
Remove cache, coverage, and report directories. The next rspec
|
|
89
101
|
run will be a cold run (full re-execution + cache rebuild).
|
|
90
102
|
|
|
91
103
|
Options:
|
|
92
|
-
-y, --yes
|
|
104
|
+
-y, --yes Skip the confirmation prompt.
|
|
105
|
+
-f, --force Synonym for --yes.
|
|
93
106
|
HELP
|
|
94
107
|
0
|
|
95
108
|
end
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'rspec_tracer/storage/backend'
|
|
4
|
+
require 'rspec_tracer/storage/json_backend'
|
|
5
|
+
require 'rspec_tracer/storage/schema'
|
|
6
|
+
require 'rspec_tracer/storage/sqlite_backend' if RUBY_ENGINE == 'ruby'
|
|
4
7
|
|
|
5
8
|
module RSpecTracer
|
|
6
9
|
# Internal CLI — see {RSpecTracer} for the user-facing surface.
|
|
7
10
|
# @api private
|
|
8
11
|
module CLI
|
|
9
12
|
# `rspec-tracer cache:info` — show cache size, last run, and
|
|
10
|
-
# invalidation stats.
|
|
11
|
-
#
|
|
13
|
+
# invalidation stats. Backend-agnostic: dispatches through
|
|
14
|
+
# {RSpecTracer::Storage::Backend.build} so `storage_backend
|
|
15
|
+
# :sqlite` reports the populated cache instead of the false
|
|
16
|
+
# "no cache yet" the JsonBackend-only path used to emit.
|
|
12
17
|
module CacheInfo
|
|
13
18
|
# @param args [Array<String>] sub-command args (`-h` / `--help`).
|
|
14
19
|
# @param stdout [IO]
|
|
@@ -23,18 +28,15 @@ module RSpecTracer
|
|
|
23
28
|
stdout.puts "cache_path: #{cache_path}"
|
|
24
29
|
stdout.puts "size: #{format_bytes(directory_size(cache_path))}"
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
backend = Storage::Backend.build(cache_path: cache_path, configuration: RSpecTracer)
|
|
32
|
+
run_id = backend.last_run_id
|
|
33
|
+
if run_id.nil? || run_id.to_s.empty?
|
|
34
|
+
stdout.puts 'last_run: no cache yet (run rspec first)'
|
|
29
35
|
return 0
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
manifest = JSON.parse(File.read(last_run_path, encoding: 'UTF-8'))
|
|
33
|
-
run_id = manifest['run_id']
|
|
34
38
|
stdout.puts "last_run: #{run_id}"
|
|
35
|
-
stdout
|
|
36
|
-
|
|
37
|
-
print_run_summary(stdout, cache_path, run_id) if run_id
|
|
39
|
+
print_example_count(stdout, backend)
|
|
38
40
|
0
|
|
39
41
|
rescue StandardError => e
|
|
40
42
|
stderr.puts "cache:info: #{e.class}: #{e.message}"
|
|
@@ -48,24 +50,23 @@ module RSpecTracer
|
|
|
48
50
|
Usage: rspec-tracer cache:info
|
|
49
51
|
|
|
50
52
|
Show the on-disk cache size, the last run id, and example counts
|
|
51
|
-
for the most recent run.
|
|
52
|
-
|
|
53
|
+
for the most recent run. Backend-aware: works under
|
|
54
|
+
`storage_backend :json` (default) and `storage_backend :sqlite`.
|
|
55
|
+
Read-only; does not modify the cache.
|
|
53
56
|
HELP
|
|
54
57
|
0
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
# Internal helper for the tracer pipeline.
|
|
58
61
|
# @api private
|
|
59
|
-
def self.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
def self.print_example_count(stdout, backend)
|
|
63
|
+
snapshot = backend.load_graph(schema_version: Storage::Schema::CURRENT)
|
|
64
|
+
if snapshot.nil?
|
|
65
|
+
stdout.puts 'examples: <unknown> (schema mismatch; next rspec run will be cold)'
|
|
66
|
+
return
|
|
67
|
+
end
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
total = data.size
|
|
68
|
-
stdout.puts "examples: #{total} tracked"
|
|
69
|
+
stdout.puts "examples: #{snapshot.all_examples.size} tracked"
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
# Internal helper for the tracer pipeline.
|
|
@@ -109,24 +109,33 @@ module RSpecTracer
|
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
#
|
|
113
|
-
#
|
|
112
|
+
# `bundle exec rspec-tracer doctor` runs in its own process via
|
|
113
|
+
# the gem's `bin/rspec-tracer` binstub, NOT inside the user's
|
|
114
|
+
# rspec boot — so app code never loads here and a bare
|
|
115
|
+
# `defined?(::SimpleCov)` check would falsely report "not
|
|
116
|
+
# loaded" on projects that DO have SimpleCov in their
|
|
117
|
+
# Gemfile. Probe `Gem.loaded_specs` first to surface the
|
|
118
|
+
# "installed but not loaded in doctor's process" case
|
|
119
|
+
# separately from "actually not installed."
|
|
114
120
|
def self.simplecov_check
|
|
115
|
-
if defined?(::SimpleCov)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
return 'OK SimpleCov: loaded (interop active)' if defined?(::SimpleCov)
|
|
122
|
+
|
|
123
|
+
spec = Gem.loaded_specs['simplecov']
|
|
124
|
+
return "INFO SimpleCov: installed (v#{spec.version}; not loaded in doctor's process)" if spec
|
|
125
|
+
|
|
126
|
+
'INFO SimpleCov: not installed (this is fine; SimpleCov is optional)'
|
|
120
127
|
end
|
|
121
128
|
|
|
122
|
-
#
|
|
123
|
-
#
|
|
129
|
+
# See {.simplecov_check} for the doctor-runs-in-its-own-
|
|
130
|
+
# process rationale. Same three-state probe shape: loaded in
|
|
131
|
+
# this process / installed but not loaded / not installed.
|
|
124
132
|
def self.rails_check
|
|
125
|
-
if defined?(::Rails::VERSION) && !::Rails::VERSION.nil?
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
return "OK Rails: #{::Rails::VERSION::STRING}" if defined?(::Rails::VERSION) && !::Rails::VERSION.nil?
|
|
134
|
+
|
|
135
|
+
spec = Gem.loaded_specs['rails']
|
|
136
|
+
return "INFO Rails: installed (v#{spec.version}; not loaded in doctor's process)" if spec
|
|
137
|
+
|
|
138
|
+
'INFO Rails: not installed (this is fine for non-Rails projects)'
|
|
130
139
|
end
|
|
131
140
|
|
|
132
141
|
# Surface a 1.x->2.0 cache mismatch BEFORE the user runs
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'rspec_tracer/storage/backend'
|
|
4
|
+
require 'rspec_tracer/storage/json_backend'
|
|
5
|
+
require 'rspec_tracer/storage/schema'
|
|
6
|
+
require 'rspec_tracer/storage/sqlite_backend' if RUBY_ENGINE == 'ruby'
|
|
4
7
|
|
|
5
8
|
module RSpecTracer
|
|
6
9
|
# Internal CLI — see {RSpecTracer} for the user-facing surface.
|
|
7
10
|
# @api private
|
|
8
11
|
module CLI
|
|
9
12
|
# `rspec-tracer explain <example>` — show why a given example is
|
|
10
|
-
# scheduled to run or skip on the next rspec invocation.
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
13
|
+
# scheduled to run or skip on the next rspec invocation. Backend-
|
|
14
|
+
# agnostic: dispatches through {RSpecTracer::Storage::Backend.build}
|
|
15
|
+
# so `storage_backend :sqlite` resolves the latest run from the
|
|
16
|
+
# meta table instead of the JsonBackend-only `last_run.json` file.
|
|
14
17
|
module Explain
|
|
15
18
|
# @param args [Array<String>] sub-command args. First positional
|
|
16
19
|
# arg is the example_id (or substring) to explain.
|
|
@@ -24,14 +27,13 @@ module RSpecTracer
|
|
|
24
27
|
require 'rspec_tracer/load_config'
|
|
25
28
|
cache_path = RSpecTracer.cache_path
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
return 1 if
|
|
30
|
+
snapshot = load_snapshot(cache_path, stderr)
|
|
31
|
+
return 1 if snapshot.nil?
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return no_match(args.first, all_examples, stderr) if match.nil?
|
|
33
|
+
match = find_example(snapshot.all_examples, args.first)
|
|
34
|
+
return no_match(args.first, snapshot.all_examples, stderr) if match.nil?
|
|
33
35
|
|
|
34
|
-
print_explanation(stdout, match,
|
|
36
|
+
print_explanation(stdout, match, snapshot)
|
|
35
37
|
0
|
|
36
38
|
rescue StandardError => e
|
|
37
39
|
stderr.puts "explain: #{e.class}: #{e.message}"
|
|
@@ -40,21 +42,21 @@ module RSpecTracer
|
|
|
40
42
|
|
|
41
43
|
# Internal helper for the tracer pipeline.
|
|
42
44
|
# @api private
|
|
43
|
-
def self.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
def self.load_snapshot(cache_path, stderr)
|
|
46
|
+
backend = Storage::Backend.build(cache_path: cache_path, configuration: RSpecTracer)
|
|
47
|
+
run_id = backend.last_run_id
|
|
48
|
+
if run_id.nil? || run_id.to_s.empty?
|
|
49
|
+
stderr.puts "explain: no cache yet at #{cache_path} — run rspec first"
|
|
47
50
|
return nil
|
|
48
51
|
end
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
stderr.puts "explain: run_id #{run_id} directory missing at #{run_dir}"
|
|
53
|
+
snapshot = backend.load_graph(schema_version: Storage::Schema::CURRENT)
|
|
54
|
+
if snapshot.nil?
|
|
55
|
+
stderr.puts "explain: cache at #{cache_path} is incompatible with this rspec-tracer; next rspec run is cold"
|
|
54
56
|
return nil
|
|
55
57
|
end
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
snapshot
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
# Internal helper for the tracer pipeline.
|
|
@@ -73,20 +75,13 @@ module RSpecTracer
|
|
|
73
75
|
|
|
74
76
|
Show why an example is scheduled to run or skip. Matches against
|
|
75
77
|
example_id exactly first, then falls back to a substring match
|
|
76
|
-
on the example's full_description.
|
|
78
|
+
on the example's full_description. Backend-aware: works under
|
|
79
|
+
`storage_backend :json` (default) and `storage_backend :sqlite`.
|
|
80
|
+
Requires a prior rspec run.
|
|
77
81
|
HELP
|
|
78
82
|
0
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
# Internal helper for the tracer pipeline.
|
|
82
|
-
# @api private
|
|
83
|
-
def self.read_json(path)
|
|
84
|
-
return {} unless File.file?(path)
|
|
85
|
-
|
|
86
|
-
parsed = JSON.parse(File.read(path, encoding: 'UTF-8'))
|
|
87
|
-
parsed.is_a?(Hash) ? parsed : {}
|
|
88
|
-
end
|
|
89
|
-
|
|
90
85
|
# Internal helper for the tracer pipeline.
|
|
91
86
|
# @api private
|
|
92
87
|
def self.find_example(all_examples, query)
|
|
@@ -94,50 +89,65 @@ module RSpecTracer
|
|
|
94
89
|
|
|
95
90
|
all_examples.find do |id, meta|
|
|
96
91
|
meta = {} unless meta.is_a?(::Hash)
|
|
97
|
-
desc = meta
|
|
92
|
+
desc = fetch_meta(meta, 'full_description') || fetch_meta(meta, 'description') || ''
|
|
98
93
|
id.include?(query) || desc.include?(query)
|
|
99
94
|
end&.last
|
|
100
95
|
end
|
|
101
96
|
|
|
102
97
|
# Internal helper for the tracer pipeline.
|
|
103
98
|
# @api private
|
|
104
|
-
def self.print_explanation(stdout, meta,
|
|
99
|
+
def self.print_explanation(stdout, meta, snapshot)
|
|
105
100
|
meta = {} unless meta.is_a?(::Hash)
|
|
106
101
|
format_lines(meta).each { |line| stdout.puts line }
|
|
107
|
-
print_dependency_summary(stdout, meta,
|
|
102
|
+
print_dependency_summary(stdout, meta, snapshot)
|
|
108
103
|
end
|
|
109
104
|
|
|
110
105
|
# Internal helper for the tracer pipeline.
|
|
111
106
|
# @api private
|
|
112
107
|
def self.format_lines(meta)
|
|
113
|
-
id =
|
|
114
|
-
file =
|
|
115
|
-
line =
|
|
116
|
-
status = meta
|
|
108
|
+
id = fetch_meta(meta, 'example_id', 'id') || '<unknown>'
|
|
109
|
+
file = fetch_meta(meta, 'rerun_file_name', 'file_name')
|
|
110
|
+
line = fetch_meta(meta, 'rerun_line_number', 'line_number')
|
|
111
|
+
status = dig_meta(meta, 'execution_result', 'status') || fetch_meta(meta, 'status') || 'unknown'
|
|
117
112
|
[
|
|
118
113
|
"id: #{id}",
|
|
119
|
-
"description: #{
|
|
114
|
+
"description: #{fetch_meta(meta, 'full_description', 'description')}",
|
|
120
115
|
"location: #{file}:#{line}",
|
|
121
116
|
"last status: #{status}",
|
|
122
|
-
"run reason: #{meta
|
|
117
|
+
"run reason: #{fetch_meta(meta, 'run_reason') || '<not recorded>'}"
|
|
123
118
|
]
|
|
124
119
|
end
|
|
125
120
|
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
# Look up a key from a Hash, tolerating both String and Symbol
|
|
122
|
+
# storage. Snapshot Hashes round-tripped through JSON yield
|
|
123
|
+
# String keys; the post-#182 msgpack serializer preserves
|
|
124
|
+
# Symbol keys end-to-end, so callers can't assume either shape.
|
|
125
|
+
def self.fetch_meta(meta, *keys)
|
|
126
|
+
keys.each do |k|
|
|
127
|
+
v = meta[k]
|
|
128
|
+
return v unless v.nil?
|
|
129
|
+
|
|
130
|
+
sym_value = meta[k.to_sym]
|
|
131
|
+
return sym_value unless sym_value.nil?
|
|
132
|
+
end
|
|
130
133
|
nil
|
|
131
134
|
end
|
|
132
135
|
|
|
136
|
+
# Look up a nested key from a Hash, tolerating both String and
|
|
137
|
+
# Symbol storage at each level. See {.fetch_meta} for rationale.
|
|
138
|
+
def self.dig_meta(meta, *keys)
|
|
139
|
+
keys.reduce(meta) do |acc, k|
|
|
140
|
+
break nil if acc.nil? || !acc.is_a?(::Hash)
|
|
141
|
+
|
|
142
|
+
acc[k] || acc[k.to_sym]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
133
146
|
# Internal helper for the tracer pipeline.
|
|
134
147
|
# @api private
|
|
135
|
-
def self.print_dependency_summary(stdout, meta,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
deps = read_json(deps_path)
|
|
140
|
-
id = meta['example_id'] || meta['id']
|
|
148
|
+
def self.print_dependency_summary(stdout, meta, snapshot)
|
|
149
|
+
id = fetch_meta(meta, 'example_id', 'id')
|
|
150
|
+
deps = snapshot.dependency || {}
|
|
141
151
|
files = Array(deps[id])
|
|
142
152
|
stdout.puts "dependencies: #{files.size} files tracked"
|
|
143
153
|
files.first(10).each { |f| stdout.puts " - #{f}" }
|