Module: SimpleCov::CLI::Uncovered

Extended by:
CommandHelpers, Uncovered
Included in:
Uncovered
Defined in:
lib/simplecov/cli/uncovered.rb,
lib/simplecov/cli/uncovered/misses.rb

Overview

simplecov uncovered: the lowest-coverage files by the chosen criterion, ascending, so a developer can answer "where should I add tests next?" without opening a browser.

Defined Under Namespace

Modules: Misses

Constant Summary collapse

DEFAULT_TOP =
10

Constants included from CommandHelpers

CommandHelpers::STATS_ROW_FORMAT

Instance Method Summary collapse

Methods included from CommandHelpers

build_parser, command_name, common_options, error, error_nil, on_help, one?, parse_common, quiet_option, recorded_contexts, stats_row

Instance Method Details

#build_row(fname, pct, payload, opts, keys) ⇒ Object



128
129
130
131
132
# File 'lib/simplecov/cli/uncovered.rb', line 128

def build_row(fname, pct, payload, opts, keys)
  row = [fname, pct, payload[keys.fetch(:covered)].to_i, payload.fetch(keys.fetch(:total)).to_i]
  row << Misses.missed_for(payload, opts.fetch(:criterion)) if opts.fetch(:missing)
  row
end

#emit(stdout, files, opts) ⇒ Object



64
65
66
67
68
# File 'lib/simplecov/cli/uncovered.rb', line 64

def emit(stdout, files, opts)
  return Misses.annotate(stdout, files) if opts.fetch(:annotate)

  opts.fetch(:json) ? emit_json(stdout, files) : emit_text(stdout, files, CLI.color_enabled?(opts, stdout))
end

#emit_json(stdout, files) ⇒ Object



101
102
103
104
105
106
107
108
# File 'lib/simplecov/cli/uncovered.rb', line 101

def emit_json(stdout, files)
  rows = files.map do |fname, pct, covered, total, missed|
    row = {"file" => fname, "percent" => pct, "covered" => covered, "total" => total}
    row["missing"] = missed if missed
    row
  end
  stdout.puts(JSON.pretty_generate(rows))
end

#emit_text(stdout, files, color) ⇒ Object



93
94
95
96
97
98
99
# File 'lib/simplecov/cli/uncovered.rb', line 93

def emit_text(stdout, files, color)
  files.each do |fname, pct, covered, total, missed|
    row = format_row(fname, pct, covered, total, color)
    row += "  missing #{Patch::Output.ranges(missed, ",")}" if missed&.any?
    stdout.puts(row)
  end
end

#empty(opts, stdout) ⇒ Object



54
55
56
57
# File 'lib/simplecov/cli/uncovered.rb', line 54

def empty(opts, stdout)
  stdout.puts(empty_message(opts.fetch(:json))) unless opts.fetch(:annotate)
  0
end

#empty_message(json) ⇒ Object



110
111
112
# File 'lib/simplecov/cli/uncovered.rb', line 110

def empty_message(json)
  json ? "[]" : "simplecov uncovered: nothing to report"
end

#format_row(fname, pct, covered, total, color) ⇒ Object



134
135
136
137
138
# File 'lib/simplecov/cli/uncovered.rb', line 134

def format_row(fname, pct, covered, total, color)
  format("%<pct>s  %<covered>d/%<total>d  %<fname>s",
    pct: Color.colorize_percent(pct, format("%6.2f%%", pct), enabled: color),
    covered: covered, total: total, fname: fname)
end

#own_options(parser, options) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/simplecov/cli/uncovered.rb', line 76

def own_options(parser, options)
  parser.on("--threshold N", Float) { |v| options[:threshold] = v }
  parser.on("--top N", Integer) { |v| options[:top] = validate_top(v) }
  parser.on("--criterion C") { |v| options[:criterion] = v.to_sym }
  parser.on("--missing") { options[:missing] = true }
  parser.on("--annotate KIND") { |v| options[:annotate] = v }
end

#parse(args) ⇒ Object



70
71
72
73
74
# File 'lib/simplecov/cli/uncovered.rb', line 70

def parse(args)
  opts, = parse_common(args, threshold: 100.0, top: DEFAULT_TOP, criterion: :line,
    missing: false, annotate: nil) { |o, options| own_options(o, options) }
  opts
end

#precheck(opts) ⇒ Object



34
35
36
37
38
39
40
41
# File 'lib/simplecov/cli/uncovered.rb', line 34

def precheck(opts)
  return nil unless opts.fetch(:annotate)
  unless opts.fetch(:annotate).eql?("github")
    return "unknown --annotate #{opts.fetch(:annotate).inspect} (only github is supported)"
  end

  "cannot combine --annotate with --json" if opts.fetch(:json)
end

#rank(coverage_hash, opts, keys) ⇒ Object



114
115
116
117
# File 'lib/simplecov/cli/uncovered.rb', line 114

def rank(coverage_hash, opts, keys)
  rows = coverage_hash.filter_map { |fname, payload| row_for(fname, payload, opts, keys) }
  rows.sort_by { |_fname, pct, _c, _t| pct }
end

#report(opts, keys, stdout, stderr) ⇒ Object



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

def report(opts, keys, stdout, stderr)
  coverage = CoverageFile.load_coverage(opts.fetch(:input), command: "uncovered", stderr: stderr)
  return 1 unless coverage

  files = rank(coverage, opts, keys).first(opts.fetch(:top))
  return empty(opts, stdout) if files.empty?

  emit(stdout, files, opts)
  0
end

#row_for(fname, payload, opts, keys) ⇒ Object



119
120
121
122
123
124
125
126
# File 'lib/simplecov/cli/uncovered.rb', line 119

def row_for(fname, payload, opts, keys)
  return unless payload.instance_of?(Hash) && payload[keys.fetch(:total)].to_i.positive?

  pct = payload[keys.fetch(:percent)].to_f
  return if pct >= opts.fetch(:threshold)

  build_row(fname, pct, payload, opts, keys)
end

#run(args, stdout:, stderr:) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/simplecov/cli/uncovered.rb', line 22

def run(args, stdout:, stderr:, **)
  opts = parse(args)
  issue = precheck(opts)
  return error(stderr, issue) if issue

  keys = CoverageFile::CRITERIA[opts.fetch(:criterion)]
  return unknown_criterion(opts.fetch(:criterion), stderr) unless keys

  opts[:missing] = true if opts.fetch(:annotate)
  report(opts, keys, stdout, stderr)
end

#unknown_criterion(criterion, stderr) ⇒ Object



59
60
61
62
# File 'lib/simplecov/cli/uncovered.rb', line 59

def unknown_criterion(criterion, stderr)
  stderr.puts("simplecov uncovered: unknown --criterion #{criterion.inspect} (expected line, branch, or method)")
  1
end

#validate_top(value) ⇒ Object

A negative count would raise from Array#first, so it is reported as the parse error it is. --top 0 is allowed and shows nothing. OptionParser prepends the offending option's name, so the raise carries only the reason.

Raises:

  • (OptionParser::InvalidArgument)


87
88
89
90
91
# File 'lib/simplecov/cli/uncovered.rb', line 87

def validate_top(value)
  raise OptionParser::InvalidArgument, "must not be negative" if value.negative?

  value
end