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

Class Method Details

.dig_meta(meta, *keys) ⇒ 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.

Look up a nested key from a Hash, tolerating both String and Symbol storage at each level. See fetch_meta for rationale.



138
139
140
141
142
143
144
# File 'lib/rspec_tracer/cli/explain.rb', line 138

def self.dig_meta(meta, *keys)
  keys.reduce(meta) do |acc, k|
    break nil if acc.nil? || !acc.is_a?(::Hash)

    acc[k] || acc[k.to_sym]
  end
end

.fetch_meta(meta, *keys) ⇒ 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.

Look up a key from a Hash, tolerating both String and Symbol storage. Snapshot Hashes round-tripped through JSON yield String keys; the post-#182 msgpack serializer preserves Symbol keys end-to-end, so callers can’t assume either shape.



125
126
127
128
129
130
131
132
133
134
# File 'lib/rspec_tracer/cli/explain.rb', line 125

def self.fetch_meta(meta, *keys)
  keys.each do |k|
    v = meta[k]
    return v unless v.nil?

    sym_value = meta[k.to_sym]
    return sym_value unless sym_value.nil?
  end
  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.



87
88
89
90
91
92
93
94
95
# File 'lib/rspec_tracer/cli/explain.rb', line 87

def self.find_example(all_examples, query)
  return all_examples[query] if all_examples.key?(query)

  all_examples.find do |id, meta|
    meta = {} unless meta.is_a?(::Hash)
    desc = fetch_meta(meta, 'full_description') || fetch_meta(meta, 'description') || ''
    id.include?(query) || desc.include?(query)
  end&.last
end

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



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rspec_tracer/cli/explain.rb', line 107

def self.format_lines(meta)
  id = fetch_meta(meta, 'example_id', 'id') || '<unknown>'
  file = fetch_meta(meta, 'rerun_file_name', 'file_name')
  line = fetch_meta(meta, 'rerun_line_number', 'line_number')
  status = dig_meta(meta, 'execution_result', 'status') || fetch_meta(meta, 'status') || 'unknown'
  [
    "id:           #{id}",
    "description:  #{fetch_meta(meta, 'full_description', 'description')}",
    "location:     #{file}:#{line}",
    "last status:  #{status}",
    "run reason:   #{fetch_meta(meta, 'run_reason') || '<not recorded>'}"
  ]
end

.load_snapshot(cache_path, 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.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/rspec_tracer/cli/explain.rb', line 45

def self.load_snapshot(cache_path, stderr)
  backend = Storage::Backend.build(cache_path: cache_path, configuration: RSpecTracer)
  run_id = backend.last_run_id
  if run_id.nil? || run_id.to_s.empty?
    stderr.puts "explain: no cache yet at #{cache_path} — run rspec first"
    return nil
  end

  snapshot = backend.load_graph(schema_version: Storage::Schema::CURRENT)
  if snapshot.nil?
    stderr.puts "explain: cache at #{cache_path} is incompatible with this rspec-tracer; next rspec run is cold"
    return nil
  end

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



64
65
66
67
68
# File 'lib/rspec_tracer/cli/explain.rb', line 64

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

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.



148
149
150
151
152
153
154
155
# File 'lib/rspec_tracer/cli/explain.rb', line 148

def self.print_dependency_summary(stdout, meta, snapshot)
  id = fetch_meta(meta, '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

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.



99
100
101
102
103
# File 'lib/rspec_tracer/cli/explain.rb', line 99

def self.print_explanation(stdout, meta, snapshot)
  meta = {} unless meta.is_a?(::Hash)
  format_lines(meta).each { |line| stdout.puts line }
  print_dependency_summary(stdout, meta, snapshot)
end

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.



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/rspec_tracer/cli/explain.rb', line 72

def self.print_help(stdout)
  stdout.puts <<~HELP
    Usage: rspec-tracer explain <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.
  HELP
  0
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).

Parameters:

  • args (Array<String>)

    sub-command args. First positional arg is the example_id (or substring) to explain.

  • stdout (IO) (defaults to: $stdout)
  • stderr (IO) (defaults to: $stderr)

Returns:

  • (Integer)

    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
# File 'lib/rspec_tracer/cli/explain.rb', line 24

def self.run(args, stdout: $stdout, stderr: $stderr)
  return print_help(stdout) if args.empty? || args.include?('-h') || args.include?('--help')

  require 'rspec_tracer/load_config'
  cache_path = RSpecTracer.cache_path

  snapshot = load_snapshot(cache_path, stderr)
  return 1 if snapshot.nil?

  match = find_example(snapshot.all_examples, args.first)
  return no_match(args.first, snapshot.all_examples, stderr) if match.nil?

  print_explanation(stdout, match, snapshot)
  0
rescue StandardError => e
  stderr.puts "explain: #{e.class}: #{e.message}"
  1
end