Module: RSpecTracer::CLI::Doctor Private

Defined in:
lib/rspec_tracer/cli/doctor.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 doctor` — diagnose config + environment. Reports Ruby + rspec-tracer versions, project root resolution, cache / coverage / report directory state, and SimpleCov / Rails presence. Exits 0 on healthy diagnosis, 1 if any check fails.

Constant Summary collapse

REMOTE_CACHE_PROBES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

When remote_cache is configured, verify the backend is reachable from doctor’s vantage point so the user catches misconfig (typo’d S3 path / unreachable Redis URL / unwritable local-fs dir) BEFORE the next CI run fails. Best-effort: never FAIL the gate, just surface a WARN/INFO line.

{
  s3: ->(opts) { remote_cache_s3_check(opts) },
  local_fs: ->(opts) { remote_cache_local_fs_check(opts) },
  redis: ->(opts) { remote_cache_redis_check(opts) }
}.freeze

Class Method Summary collapse

Class Method Details

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

Returns:

  • (Boolean)


249
250
251
252
253
# File 'lib/rspec_tracer/cli/doctor.rb', line 249

def self.ar_schema_enabled?
  return false unless RSpecTracer.respond_to?(:track_ar_schema_notifications?)

  RSpecTracer.track_ar_schema_notifications?
end

.ar_schema_narrow_attribution_checkObject

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.

Surface the narrow-attribution precondition at diagnostic time. When ‘track_ar_schema_notifications` is enabled AND Rails is loaded AND the rspec-rails default `use_transactional_fixtures

true` is in effect, per-example BEGIN/COMMIT fires

‘sql.active_record` inside the rspec-tracer bucket and attribution silently widens. Same shape as the boot-time warn in RSpecTracer.start, surfaced here so users running `rspec-tracer doctor` see the issue without having to boot a full rspec run first.



234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/rspec_tracer/cli/doctor.rb', line 234

def self.ar_schema_narrow_attribution_check
  return 'INFO AR schema:   track_ar_schema_notifications not enabled' unless ar_schema_enabled?
  return 'INFO AR schema:   Rails not loaded' unless rails_loaded?

  if transactional_fixtures_default?
    'WARN AR schema:   track_ar_schema_notifications + use_transactional_fixtures=true ' \
      'silently widens to whole-suite-on-schema-change. See README ' \
      'section "Narrow AR schema attribution".'
  else
    'OK   AR schema:   narrow attribution preconditions look good'
  end
end

.cache_path_checkObject

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.



76
77
78
# File 'lib/rspec_tracer/cli/doctor.rb', line 76

def self.cache_path_check
  path_check('cache_path:', RSpecTracer.cache_path)
end

.cache_schema_version_checkObject

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.

Surface a 1.x->2.0 cache mismatch BEFORE the user runs rspec and watches everything re-run mysteriously. Reads the cached ‘last_run.json` (if any) and compares its `schema_version` against the gem’s ‘Schema::CURRENT`. Three outcomes: no cache yet (INFO), match (OK), or mismatch (WARN with the cold-run note). Never FAIL - schema mismatches are the documented cold-run path, not a hard error.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/rspec_tracer/cli/doctor.rb', line 139

def self.cache_schema_version_check
  require 'rspec_tracer/storage/schema'
  require 'json'

  cache_path = RSpecTracer.cache_path
  last_run_path = File.join(cache_path.to_s, 'last_run.json')
  unless File.file?(last_run_path)
    return 'INFO schema:      no cache yet (next rspec run is cold; expected on first install)'
  end

  manifest = JSON.parse(File.read(last_run_path, encoding: 'UTF-8'))
  stored = manifest['schema_version']
  current = RSpecTracer::Storage::Schema::CURRENT
  if stored == current
    "OK   schema:      v#{current} (matches gem)"
  else
    "WARN schema:      stored v#{stored.inspect} != gem v#{current} " \
      '(next rspec run is a cold run; expected on 1.x->2.0 upgrade)'
  end
rescue StandardError => e
  "WARN schema:      could not read cache manifest: #{e.class}: #{e.message}"
end

.coverage_path_checkObject

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

def self.coverage_path_check
  path_check('coverage_path:', RSpecTracer.coverage_path)
end

.git_checkObject

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
109
110
# File 'lib/rspec_tracer/cli/doctor.rb', line 104

def self.git_check
  if system('git', 'rev-parse', 'HEAD', out: File::NULL, err: File::NULL)
    'OK   git:         HEAD reachable (remote_cache will work)'
  else
    'WARN git:         not in a git repo (remote_cache will degrade gracefully)'
  end
end

.path_check(label, 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.



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

def self.path_check(label, path)
  return "FAIL #{label} <missing>" if path.nil? || path.empty?
  return "FAIL #{label} #{path} (does not exist)" unless File.directory?(path)
  return "FAIL #{label} #{path} (not writable)" unless File.writable?(path)

  "OK   #{label} #{path}"
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.



46
47
48
49
50
51
52
53
54
# File 'lib/rspec_tracer/cli/doctor.rb', line 46

def self.print_help(stdout)
  stdout.puts <<~HELP
    Usage: rspec-tracer doctor

    Diagnose rspec-tracer config and environment. Prints a checklist
    of versions, paths, and integrations; exits 0 if all checks pass.
  HELP
  0
end

.project_root_checkObject

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

def self.project_root_check
  "OK   root:        #{RSpecTracer.root}"
end

.rails_checkObject

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.



124
125
126
127
128
129
130
# File 'lib/rspec_tracer/cli/doctor.rb', line 124

def self.rails_check
  if defined?(::Rails::VERSION) && !::Rails::VERSION.nil?
    "OK   Rails:       #{::Rails::VERSION::STRING}"
  else
    'INFO Rails:       not loaded (this is fine for non-Rails projects)'
  end
end

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

Returns:

  • (Boolean)


257
258
259
# File 'lib/rspec_tracer/cli/doctor.rb', line 257

def self.rails_loaded?
  defined?(::Rails::VERSION) && !::Rails::VERSION.nil?
end

.remote_cache_checkObject

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.



175
176
177
178
179
180
181
182
183
184
# File 'lib/rspec_tracer/cli/doctor.rb', line 175

def self.remote_cache_check
  entry = remote_cache_entry
  return 'INFO remote_cache: not configured (skip)' unless entry

  backend, opts = entry
  probe = REMOTE_CACHE_PROBES[backend]
  return "INFO remote_cache: custom backend #{backend.inspect} (skipping reachability probe)" if probe.nil?

  instance_exec(opts, &probe)
end

.remote_cache_entryObject

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.



188
189
190
191
192
# File 'lib/rspec_tracer/cli/doctor.rb', line 188

def self.remote_cache_entry
  return nil unless RSpecTracer.respond_to?(:remote_cache_backend_entry)

  RSpecTracer.remote_cache_backend_entry
end

.remote_cache_local_fs_check(opts) ⇒ 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.



206
207
208
209
210
211
212
213
# File 'lib/rspec_tracer/cli/doctor.rb', line 206

def self.remote_cache_local_fs_check(opts)
  path = opts[:path] || opts['path']
  return 'WARN remote_cache: :local_fs configured without :path' if path.nil? || path.empty?
  return "WARN remote_cache: :local_fs path #{path} does not exist" unless File.directory?(path)
  return "WARN remote_cache: :local_fs path #{path} not writable" unless File.writable?(path)

  "OK   remote_cache: :local_fs path=#{path}"
end

.remote_cache_redis_check(opts) ⇒ 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.



217
218
219
220
221
222
223
# File 'lib/rspec_tracer/cli/doctor.rb', line 217

def self.remote_cache_redis_check(opts)
  url = opts[:url] || opts['url'] || ENV.fetch('RSPEC_TRACER_REMOTE_CACHE_URI', nil)
  return 'WARN remote_cache: :redis configured without :url' if url.nil? || url.empty?

  "OK   remote_cache: :redis url=#{url} " \
    '(reachability not probed locally; verified end-to-end on next CI run)'
end

.remote_cache_s3_check(opts) ⇒ 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.



196
197
198
199
200
201
202
# File 'lib/rspec_tracer/cli/doctor.rb', line 196

def self.remote_cache_s3_check(opts)
  bucket = opts[:bucket] || opts['bucket']
  return 'WARN remote_cache: :s3 configured without :bucket' if bucket.nil? || bucket.empty?

  "OK   remote_cache: :s3 bucket=#{bucket} " \
    '(reachability not probed locally; verified end-to-end on next CI run)'
end

.report_path_checkObject

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.



88
89
90
# File 'lib/rspec_tracer/cli/doctor.rb', line 88

def self.report_path_check
  path_check('report_path:', RSpecTracer.report_path)
end

.ruby_version_checkObject

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.



58
59
60
# File 'lib/rspec_tracer/cli/doctor.rb', line 58

def self.ruby_version_check
  "OK   ruby:        #{RUBY_DESCRIPTION}"
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 = healthy, 1 = any check FAILed; warnings keep status 0).

Parameters:

  • args (Array<String>)

    sub-command args (‘-h` / `–help`).

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

Returns:

  • (Integer)

    exit status (0 = healthy, 1 = any check FAILed; warnings keep status 0).



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/rspec_tracer/cli/doctor.rb', line 17

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

  require 'rspec_tracer/load_config'

  checks = [
    ruby_version_check,
    tracer_version_check,
    project_root_check,
    cache_path_check,
    coverage_path_check,
    report_path_check,
    git_check,
    simplecov_check,
    rails_check,
    cache_schema_version_check,
    remote_cache_check,
    ar_schema_narrow_attribution_check
  ]
  checks.each { |line| stdout.puts line }
  ok = checks.none? { |line| line.start_with?('FAIL') }
  ok ? 0 : 1
rescue StandardError => e
  stderr.puts "doctor: #{e.class}: #{e.message}"
  1
end

.simplecov_checkObject

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.



114
115
116
117
118
119
120
# File 'lib/rspec_tracer/cli/doctor.rb', line 114

def self.simplecov_check
  if defined?(::SimpleCov)
    'OK   SimpleCov:   loaded (interop active)'
  else
    'INFO SimpleCov:   not loaded (this is fine; SimpleCov is optional)'
  end
end

.tracer_version_checkObject

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

def self.tracer_version_check
  "OK   rspec-tracer: #{RSpecTracer::VERSION}"
end

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

Returns:

  • (Boolean)


263
264
265
266
267
268
269
270
271
272
# File 'lib/rspec_tracer/cli/doctor.rb', line 263

def self.transactional_fixtures_default?
  return false unless defined?(::RSpec) && ::RSpec.respond_to?(:configuration)

  cfg = ::RSpec.configuration
  return false unless cfg.respond_to?(:use_transactional_fixtures)

  cfg.use_transactional_fixtures != false
rescue StandardError
  false
end