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. Reads the most recent run’s JSON files (all_examples.json + dependency.json + failed_examples.json + flaky_examples.json) to surface the dependency set, last-run status, and the run-decision reason.

Class Method Summary collapse

Class Method Details

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



92
93
94
95
96
97
98
99
100
# File 'lib/rspec_tracer/cli/explain.rb', line 92

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 = meta['full_description'] || meta['description'] || ''
    id.include?(query) || desc.include?(query)
  end&.last
end

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

Internal helper for the tracer pipeline.



128
129
130
131
# File 'lib/rspec_tracer/cli/explain.rb', line 128

def self.first_non_nil(meta, *keys)
  keys.each { |k| return meta[k] unless meta[k].nil? }
  nil
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.



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/rspec_tracer/cli/explain.rb', line 112

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



62
63
64
65
66
# File 'lib/rspec_tracer/cli/explain.rb', line 62

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.



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

def self.print_dependency_summary(stdout, meta, run_dir)
  deps_path = File.join(run_dir, 'dependency.json')
  return unless File.file?(deps_path)

  deps = read_json(deps_path)
  id = meta['example_id'] || meta['id']
  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.



104
105
106
107
108
# File 'lib/rspec_tracer/cli/explain.rb', line 104

def self.print_explanation(stdout, meta, run_dir)
  meta = {} unless meta.is_a?(::Hash)
  format_lines(meta).each { |line| stdout.puts line }
  print_dependency_summary(stdout, meta, run_dir)
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.



70
71
72
73
74
75
76
77
78
79
# File 'lib/rspec_tracer/cli/explain.rb', line 70

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. Requires a prior rspec run.
  HELP
  0
end

.read_json(path) ⇒ 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.



83
84
85
86
87
88
# File 'lib/rspec_tracer/cli/explain.rb', line 83

def self.read_json(path)
  return {} unless File.file?(path)

  parsed = JSON.parse(File.read(path, encoding: 'UTF-8'))
  parsed.is_a?(Hash) ? parsed : {}
end

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



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

def self.resolve_run_dir(cache_path, stderr)
  last_run_path = File.join(cache_path, 'last_run.json')
  unless File.file?(last_run_path)
    stderr.puts "explain: no last_run.json at #{cache_path} — run rspec first"
    return nil
  end

  run_id = JSON.parse(File.read(last_run_path, encoding: 'UTF-8'))['run_id']
  run_dir = File.join(cache_path, run_id.to_s)
  unless File.directory?(run_dir)
    stderr.puts "explain: run_id #{run_id} directory missing at #{run_dir}"
    return nil
  end

  run_dir
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).



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/rspec_tracer/cli/explain.rb', line 21

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

  run_dir = resolve_run_dir(cache_path, stderr)
  return 1 if run_dir.nil?

  all_examples = read_json(File.join(run_dir, 'all_examples.json'))
  match = find_example(all_examples, args.first)
  return no_match(args.first, all_examples, stderr) if match.nil?

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