Module: SimpleCov::CLI::Dotfile

Extended by:
Dotfile
Included in:
Dotfile
Defined in:
lib/simplecov/cli/dotfile.rb

Overview

Loads a project's .simplecov config file purely to read configuration from it, with SimpleCov.start and the at_exit hook installer neutered so the load doesn't trigger coverage tracking. Used by the CLI to default paths to whatever the project's dotfile declares, without making every read-only subcommand pay for actually starting Coverage.

Constant Summary collapse

NEUTERED =

A constant rather than a block written at the call site, where an empty block and one answering nil are the same thing to every caller and so to every test.

proc {}

Instance Method Summary collapse

Instance Method Details

#baseline_fileObject

Read from .simplecov the way coverage_dir is, so a project that moved its baseline doesn't need --baseline on every ratchet.



30
31
32
33
34
35
36
37
38
# File 'lib/simplecov/cli/dotfile.rb', line 30

def baseline_file
  dotfile = find
  return Baseline::DEFAULT_FILENAME unless dotfile

  with_simplecov_loaded { read_baseline_file_from(dotfile) }
rescue ScriptError, StandardError => e
  warn "simplecov: failed to read baseline_file from #{dotfile}: #{e.class}: #{e}"
  Baseline::DEFAULT_FILENAME
end

#coverage_dirObject



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/simplecov/cli/dotfile.rb', line 15

def coverage_dir
  dotfile = find
  return "coverage" unless dotfile

  with_simplecov_loaded { read_from(dotfile) }
rescue ScriptError, StandardError => e
  # ScriptError covers the SyntaxError a malformed dotfile raises from
  # `load` (it is not a StandardError) along with LoadError; StandardError
  # covers the rest.
  warn "simplecov: failed to read coverage_dir from #{dotfile}: #{e.class}: #{e}"
  "coverage"
end

#findObject

The nearest .simplecov at or above the working directory, or nil, which is what the walk itself answers once it breaks at the filesystem root.



88
89
90
91
92
93
94
95
96
97
# File 'lib/simplecov/cli/dotfile.rb', line 88

def find
  dir = Pathname.new(Dir.pwd)
  loop do
    candidate = dir.join(".simplecov")
    return candidate.to_s if candidate.exist?
    break if dir.root?

    dir = dir.parent
  end
end

#load_simplecovObject

The deferred require exists for a standalone CLI process that has not loaded simplecov yet. It must happen inside the env guard above, so a first load here skips the default profile chain.



119
120
121
# File 'lib/simplecov/cli/dotfile.rb', line 119

def load_simplecov
  require "simplecov"
end

#load_with_start_neutered(path) ⇒ Object

Load path with SimpleCov.start and the at_exit installer turned into no-ops, so a project whose dotfile calls SimpleCov.start doesn't trigger Coverage just because we asked for coverage_dir. Config inside any SimpleCov.start { ... } block still runs.



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/simplecov/cli/dotfile.rb', line 132

def load_with_start_neutered(path)
  klass = SimpleCov.singleton_class
  names = %i[start_tracking install_at_exit_hook]
  stash = names.to_h { |name| [name, klass.instance_method(name)] } #: Hash[Symbol, UnboundMethod]
  # define_method over an existing method emits a "method redefined"
  # warning under $VERBOSE; the override and restore are intentional.
  silence_verbose { names.each { |name| klass.define_method(name, &(_ = NEUTERED)) } }
  load path
ensure
  # @type var stash: Hash[Symbol, UnboundMethod]
  # @type var klass: Class
  silence_verbose { stash.each { |name, method| klass.define_method(name, method) } }
end

#production_coverageObject

Nil when there is no dotfile, the dotfile names no store, or it cannot be read: unlike the other reads there is no sensible fallback path, and the command's own missing-store error is the right answer.



43
44
45
46
47
48
49
50
51
# File 'lib/simplecov/cli/dotfile.rb', line 43

def production_coverage
  dotfile = find
  return nil unless dotfile

  with_simplecov_loaded { read_production_coverage_from(dotfile) }
rescue ScriptError, StandardError => e
  # `warn` answers nil, which is this reader's whole fallback.
  warn "simplecov: failed to read production_coverage from #{dotfile}: #{e.class}: #{e}"
end

#read_baseline_file_from(dotfile) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/simplecov/cli/dotfile.rb', line 62

def read_baseline_file_from(dotfile)
  snapshot = SimpleCov.instance_variable_get(:@baseline_file) #: String?
  load_with_start_neutered(dotfile)
  SimpleCov.baseline_file
ensure
  # @type var snapshot: String?
  SimpleCov.instance_variable_set(:@baseline_file, snapshot)
end

#read_from(dotfile) ⇒ Object

Snapshot and restore SimpleCov.coverage_dir so we don't quietly clobber it in a host process that's already configured. The snapshot is intentionally narrow: a dotfile can still mutate other configuration via SimpleCov.configure or SimpleCov.start { ... } blocks.



75
76
77
78
79
80
81
82
83
84
# File 'lib/simplecov/cli/dotfile.rb', line 75

def read_from(dotfile)
  snapshot = SimpleCov.instance_variable_get(:@coverage_dir) #: String?
  load_with_start_neutered(dotfile)
  SimpleCov.coverage_dir
ensure
  # Restored even when the load raises, so a bad dotfile doesn't leave a
  # host process's configured dir clobbered.
  # @type var snapshot: String?
  SimpleCov.instance_variable_set(:@coverage_dir, snapshot)
end

#read_production_coverage_from(dotfile) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/simplecov/cli/dotfile.rb', line 53

def read_production_coverage_from(dotfile)
  snapshot = SimpleCov.instance_variable_get(:@production_coverage) #: String?
  load_with_start_neutered(dotfile)
  SimpleCov.production_coverage
ensure
  # @type var snapshot: String?
  SimpleCov.instance_variable_set(:@production_coverage, snapshot)
end

#silence_verboseObject



146
147
148
149
150
151
152
153
# File 'lib/simplecov/cli/dotfile.rb', line 146

def silence_verbose
  previous = $VERBOSE #: bool?
  $VERBOSE = nil
  yield
ensure
  # @type var previous: bool?
  $VERBOSE = previous
end

#with_simplecov_loadedObject



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/simplecov/cli/dotfile.rb', line 99

def with_simplecov_loaded
  previous_no_defaults = ENV.fetch("SIMPLECOV_NO_DEFAULTS", nil) #: String?
  previous_cli = ENV.fetch("SIMPLECOV_CLI", nil) #: String?
  ENV["SIMPLECOV_NO_DEFAULTS"] = "1"
  # SIMPLECOV_CLI lets a project's `.simplecov` opt some config into
  # CLI-only behavior: simplecov itself sets `coverage_dir` to the dogfood
  # path here but skips that for descendants.
  ENV["SIMPLECOV_CLI"] = "1"
  load_simplecov
  yield
ensure
  # @type var previous_no_defaults: String?
  # @type var previous_cli: String?
  ENV["SIMPLECOV_NO_DEFAULTS"] = previous_no_defaults
  ENV["SIMPLECOV_CLI"] = previous_cli
end