Module: RSpecTracer::CLI::Explain Private
- Defined in:
- lib/rspec_tracer/cli/explain.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
rspec-tracer explain <example> -- show why a given example is
scheduled to run or skip on the next rspec invocation. Backend-
agnostic: dispatches through Storage::Backend.build
(via SnapshotHelpers.load_snapshot) so storage_backend :sqlite
resolves the latest run from the meta table instead of the
JsonBackend-only last_run.json file.
Class Method Summary collapse
-
.always_rerun_reason(id, snapshot) ⇒ String?
private
First always-re-run status the id carries, in the filter's precedence order (interrupted > flaky > failed > pending), or nil when the example has no status-based re-run trigger.
-
.find_example(all_examples, query) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.format_lines(meta, skipped: false) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.in_id_set?(id_set, id) ⇒ Boolean
private
Internal helper for the tracer pipeline.
-
.last_run_line(id, snapshot, filter_persisted:) ⇒ String
private
One
last run:line for the--not-runview, derived from per-run membership: skipped_examples (the filter skipped it), filtered_examples (it ran, with the recorded reason), or neither. -
.last_status(meta) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.next_run_line(id, snapshot) ⇒ String
private
Prediction line for the NEXT run, from the persisted status sets (the inputs to the next run's always-re-run triggers).
-
.no_match(query, all_examples, stderr) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.not_run_detail_lines(id, snapshot) ⇒ Array<String>
private
Detail lines printed under
last run:: the itemized skip derivation for a skipped id, a run-side hint for an id that actually ran, nothing for the no-decision fallbacks. -
.print_dependency_summary(stdout, meta, snapshot) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.print_explanation(stdout, meta, snapshot) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.print_help(stdout) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.print_not_run_explanation(stdout, meta, snapshot, backend) ⇒ Object
private
The
--not-runview. -
.print_not_run_header(stdout, meta, id) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.ran_reason(id, snapshot) ⇒ Object
private
Internal helper for the tracer pipeline.
-
.run(args, stdout: $stdout, stderr: $stderr) ⇒ Integer
private
Exit status (0 = explanation printed, 1 = example not found / cache missing).
-
.run_reason_field(meta, skipped) ⇒ Object
private
The run-side
run reason:value. -
.skip_reason_lines(id, snapshot) ⇒ Array<String>
private
Itemized derivation of WHY the filter skipped the example.
-
.skipped?(id, snapshot) ⇒ Boolean
private
Internal helper for the tracer pipeline.
Class Method Details
.always_rerun_reason(id, snapshot) ⇒ String?
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.
First always-re-run status the id carries, in the filter's precedence order (interrupted > flaky > failed > pending), or nil when the example has no status-based re-run trigger.
236 237 238 239 240 241 242 243 |
# File 'lib/rspec_tracer/cli/explain.rb', line 236 def self.always_rerun_reason(id, snapshot) return 'interrupted' if in_id_set?(snapshot.interrupted_examples, id) return 'flaky' if in_id_set?(snapshot.flaky_examples, id) return 'failed' if in_id_set?(snapshot.failed_examples, id) return 'pending' if in_id_set?(snapshot.pending_examples, id) nil end |
.find_example(all_examples, query) ⇒ 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.
Internal helper for the tracer pipeline.
82 83 84 85 86 87 88 89 90 |
# File 'lib/rspec_tracer/cli/explain.rb', line 82 def self.find_example(all_examples, query) return all_examples[query] if all_examples.key?(query) all_examples.find do |id, | = {} unless .is_a?(::Hash) desc = SnapshotHelpers.example_description() || '' id.include?(query) || desc.include?(query) end&.last end |
.format_lines(meta, skipped: false) ⇒ 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.
Internal helper for the tracer pipeline.
103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/rspec_tracer/cli/explain.rb', line 103 def self.format_lines(, skipped: false) id = SnapshotHelpers.(, 'example_id', 'id') || '<unknown>' file, line = SnapshotHelpers.example_location() status = last_status() [ "id: #{id}", "description: #{SnapshotHelpers.example_description()}", "location: #{file}:#{line}", "last status: #{status}", "run reason: #{run_reason_field(, skipped)}" ] end |
.in_id_set?(id_set, id) ⇒ Boolean
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.
Internal helper for the tracer pipeline.
259 260 261 |
# File 'lib/rspec_tracer/cli/explain.rb', line 259 def self.in_id_set?(id_set, id) (id_set || []).include?(id) end |
.last_run_line(id, snapshot, filter_persisted:) ⇒ String
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.
One last run: line for the --not-run view, derived from
per-run membership: skipped_examples (the filter skipped it),
filtered_examples (it ran, with the recorded reason), or
neither. The neither case splits on filter_persisted: a
backend that persists filter decisions (json) with an empty
decision set means the last run recorded no decision for this
id (a cold run persists empty sets by design, and an id absent
from the last run has no entry either) -- only a
non-persisting backend (sqlite) earns the storage-backend
wording.
192 193 194 195 196 197 198 199 200 |
# File 'lib/rspec_tracer/cli/explain.rb', line 192 def self.last_run_line(id, snapshot, filter_persisted:) return 'last run: skipped (cache hit)' if skipped?(id, snapshot) reason = ran_reason(id, snapshot) return "last run: ran -- #{reason}" if reason return 'last run: <not recorded by this storage backend>' unless filter_persisted 'last run: no filter decision recorded (cold run, or this example was not part of it)' end |
.last_status(meta) ⇒ 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.
Internal helper for the tracer pipeline.
134 135 136 137 |
# File 'lib/rspec_tracer/cli/explain.rb', line 134 def self.last_status() SnapshotHelpers.(, 'execution_result', 'status') || SnapshotHelpers.(, 'status') || 'unknown' end |
.next_run_line(id, snapshot) ⇒ String
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.
Prediction line for the NEXT run, from the persisted status sets (the inputs to the next run's always-re-run triggers).
225 226 227 228 229 230 |
# File 'lib/rspec_tracer/cli/explain.rb', line 225 def self.next_run_line(id, snapshot) reason = always_rerun_reason(id, snapshot) return "will re-run regardless (#{reason} last run)" if reason 'runs only if a dependency, whole-suite invalidator, boot file, or tracked env var changes' end |
.no_match(query, all_examples, stderr) ⇒ 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.
Internal helper for the tracer pipeline.
53 54 55 56 57 |
# File 'lib/rspec_tracer/cli/explain.rb', line 53 def self.no_match(query, all_examples, stderr) stderr.puts "explain: no example matching #{query.inspect}" stderr.puts " cache has #{all_examples.size} examples; pass an example_id or substring of description" 1 end |
.not_run_detail_lines(id, snapshot) ⇒ Array<String>
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.
Detail lines printed under last run:: the itemized skip
derivation for a skipped id, a run-side hint for an id that
actually ran, nothing for the no-decision fallbacks.
171 172 173 174 175 176 177 178 179 |
# File 'lib/rspec_tracer/cli/explain.rb', line 171 def self.not_run_detail_lines(id, snapshot) return skip_reason_lines(id, snapshot) if skipped?(id, snapshot) if ran_reason(id, snapshot) return [" it was not skipped; run 'rspec-tracer explain #{id}' for the run-side view"] end [] end |
.print_dependency_summary(stdout, meta, snapshot) ⇒ 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.
Internal helper for the tracer pipeline.
265 266 267 268 269 270 271 272 |
# File 'lib/rspec_tracer/cli/explain.rb', line 265 def self.print_dependency_summary(stdout, , snapshot) id = SnapshotHelpers.(, 'example_id', 'id') deps = snapshot.dependency || {} files = Array(deps[id]) stdout.puts "dependencies: #{files.size} files tracked" files.first(10).each { |f| stdout.puts " - #{f}" } stdout.puts " ... (#{files.size - 10} more)" if files.size > 10 end |
.print_explanation(stdout, meta, snapshot) ⇒ 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.
Internal helper for the tracer pipeline.
94 95 96 97 98 99 |
# File 'lib/rspec_tracer/cli/explain.rb', line 94 def self.print_explanation(stdout, , snapshot) = {} unless .is_a?(::Hash) id = SnapshotHelpers.(, 'example_id', 'id') format_lines(, skipped: skipped?(id, snapshot)).each { |line| stdout.puts line } print_dependency_summary(stdout, , snapshot) end |
.print_help(stdout) ⇒ 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.
Internal helper for the tracer pipeline.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/rspec_tracer/cli/explain.rb', line 61 def self.print_help(stdout) stdout.puts <<~HELP Usage: rspec-tracer explain [--not-run] <example_id_or_substring> Show why an example is scheduled to run or skip. Matches against example_id exactly first, then falls back to a substring match on the example's full_description. Backend-aware: works under `storage_backend :json` (default) and `storage_backend :sqlite`. Requires a prior rspec run. --not-run flips to the skip-side view: whether the example was skipped on the last run (and why no run trigger fired), its last recorded status, and what would make it run on the next rspec invocation. The cache keeps only the most recent snapshot, so "last status" reflects the last run, not a run history. HELP 0 end |
.print_not_run_explanation(stdout, meta, snapshot, backend) ⇒ 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.
The --not-run view. Deliberately does NOT print the
run reason: field from all_examples meta: for a skipped
example that field is the carry-forward PRIOR-run reason
(seeded from the previous snapshot), so echoing it here would
misreport why the example did not run. Everything below is
derived from the per-run membership sets instead
(skipped_examples / filtered_examples / the status id-sets).
146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/rspec_tracer/cli/explain.rb', line 146 def self.print_not_run_explanation(stdout, , snapshot, backend) = {} unless .is_a?(::Hash) id = SnapshotHelpers.(, 'example_id', 'id') || '<unknown>' filter_persisted = SnapshotHelpers.filter_decisions_persisted?(backend) print_not_run_header(stdout, , id) stdout.puts last_run_line(id, snapshot, filter_persisted: filter_persisted) not_run_detail_lines(id, snapshot).each { |detail| stdout.puts detail } stdout.puts "last status: #{last_status()} (most recent snapshot; this cache keeps only the last run)" stdout.puts "next run: #{next_run_line(id, snapshot)}" print_dependency_summary(stdout, , snapshot) end |
.print_not_run_header(stdout, meta, id) ⇒ 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.
Internal helper for the tracer pipeline.
160 161 162 163 164 165 |
# File 'lib/rspec_tracer/cli/explain.rb', line 160 def self.print_not_run_header(stdout, , id) file, line = SnapshotHelpers.example_location() stdout.puts "id: #{id}" stdout.puts "description: #{SnapshotHelpers.example_description()}" stdout.puts "location: #{file}:#{line}" end |
.ran_reason(id, snapshot) ⇒ 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.
Internal helper for the tracer pipeline.
253 254 255 |
# File 'lib/rspec_tracer/cli/explain.rb', line 253 def self.ran_reason(id, snapshot) SnapshotHelpers.(snapshot.filtered_examples || {}, id) end |
.run(args, stdout: $stdout, stderr: $stderr) ⇒ Integer
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.
Returns exit status (0 = explanation printed, 1 = example not found / cache missing).
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/rspec_tracer/cli/explain.rb', line 24 def self.run(args, stdout: $stdout, stderr: $stderr) args = args.dup not_run = !args.delete('--not-run').nil? return print_help(stdout) if SnapshotHelpers.help_requested?(args) loaded = SnapshotHelpers.load_snapshot(RSpecTracer.cache_path, command: 'explain', stderr: stderr) return 1 if loaded.nil? snapshot, backend = loaded match = find_example(snapshot.all_examples, args.first) return no_match(args.first, snapshot.all_examples, stderr) if match.nil? if not_run print_not_run_explanation(stdout, match, snapshot, backend) else print_explanation(stdout, match, snapshot) end 0 rescue Errno::EPIPE # Downstream pipe (`... | head`) closed early -- routine in # shell pipelines, not a failure. Exit 0 silently. 0 rescue StandardError => e stderr.puts "explain: #{e.class}: #{e.}" 1 end |
.run_reason_field(meta, skipped) ⇒ 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.
The run-side run reason: value. For an example the filter
SKIPPED on the last run, the persisted run_reason is the
carry-forward reason seeded from an earlier snapshot -- it says
why the example ran back then, not why it is in its current
state. Printing it bare would misreport a skipped example as
having a current run trigger, so flag it and point at the
--not-run skip-side view instead.
124 125 126 127 128 129 130 |
# File 'lib/rspec_tracer/cli/explain.rb', line 124 def self.run_reason_field(, skipped) reason = SnapshotHelpers.(, 'run_reason') || '<not recorded>' return reason unless skipped "#{reason} (carried forward from an earlier run; " \ 'this example was SKIPPED last run -- use --not-run for the skip-side view)' end |
.skip_reason_lines(id, snapshot) ⇒ Array<String>
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.
Itemized derivation of WHY the filter skipped the example. Sound because a skip logically implies every trigger in the engine's precedence chain declined: no whole-suite invalidator fired, no boot file changed (the engine ORs the boot-set check into whole-suite invalidation), the example carried no always-re-run status, none of its tracked dependency files changed, and its tracked environment snapshot was unchanged.
210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/rspec_tracer/cli/explain.rb', line 210 def self.skip_reason_lines(id, snapshot) deps = Array((snapshot.dependency || {})[id]) [ 'skip reason: no run trigger fired last run:', ' - whole-suite invalidators (Gemfile.lock, .ruby-version, .rspec-tracer, gem version): unchanged', ' - boot set: no boot file changed', ' - prior status: not failed / flaky / pending / interrupted', " - dependency files: #{deps.size} tracked, none changed", ' - environment snapshot: unchanged for this example' ] end |
.skipped?(id, snapshot) ⇒ Boolean
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.
Internal helper for the tracer pipeline.
247 248 249 |
# File 'lib/rspec_tracer/cli/explain.rb', line 247 def self.skipped?(id, snapshot) in_id_set?(snapshot.skipped_examples, id) end |