Module: SimpleCov::CLI::Patch::Output
Constant Summary collapse
- OPTIONAL_CRITERIA =
%i[branch method].freeze
- CRITERIA =
%i[line branch method].freeze
Instance Method Summary collapse
-
#annotate(stdout, rows, kind) ⇒ Object
One diagnostic per contiguous missed range per measured criterion, in the CI host's own annotation form.
-
#criterion_cell(label, stats, color) ⇒ Object
A "lines" cell always, "branches" and "methods" cells only when the touched lines actually carried one: a hollow 0/0 cell is noise.
- #criterion_cells(row, color) ⇒ Object
- #emit(stdout, rows, opts) ⇒ Object
- #emit_text(stdout, rows, color) ⇒ Object
- #format_row(row, color) ⇒ Object
- #format_total(rows, color) ⇒ Object
-
#gate(rows, minimum) ⇒ Object
No --minimum is report-only.
- #json_rows(rows) ⇒ Object
-
#measured?(stats) ⇒ Boolean
Branch and method stats are nil when the report never measured them, and 0/0 when the change touched none.
- #missing_note(row) ⇒ Object
-
#only(run) ⇒ Object
mutant:disable.
- #pct(stats) ⇒ Object
-
#ranges(numbers, separator = ", ") ⇒ Object
Collapses a sorted line list into ranges: [41, 42, 43, 47] -> "41-43, 47".
-
#short?(stats, minimum) ⇒ Boolean
Cross-multiplied rather than compared as percentages so neither rounding nor float division can shift the verdict at the boundary: reading the displayed
pctwould round a 19_999/20_000 patch up to 100 and pass it against--minimum 100, while dividing makes 23/40 compute as 57.4999... - #span(run) ⇒ Object
- #sum_stats(rows, criterion) ⇒ Object
Instance Method Details
#annotate(stdout, rows, kind) ⇒ Object
One diagnostic per contiguous missed range per measured criterion, in the CI host's own annotation form. Totals and the empty-change notice stay off stdout: the exit status under --minimum is the verdict.
19 20 21 22 23 24 25 26 27 |
# File 'lib/simplecov/cli/patch/output.rb', line 19 def annotate(stdout, rows, kind) diagnostics = rows.flat_map do |row| CRITERIA.flat_map do |criterion| stats = row.fetch(criterion) stats ? Annotations.diagnostics(row.fetch(:file), stats.fetch(:missing), criterion) : [] end end Annotations.emit(stdout, kind, diagnostics) end |
#criterion_cell(label, stats, color) ⇒ Object
A "lines" cell always, "branches" and "methods" cells only when the touched lines actually carried one: a hollow 0/0 cell is noise.
60 61 62 63 64 |
# File 'lib/simplecov/cli/patch/output.rb', line 60 def criterion_cell(label, stats, color) percent = pct(stats) cell = Color.colorize(format("%6.2f%%", percent), (percent >= 100) ? :green : :red, enabled: color) "#{cell} (#{stats.fetch(:covered)}/#{stats.fetch(:relevant)}) #{label}" end |
#criterion_cells(row, color) ⇒ Object
51 52 53 54 55 56 |
# File 'lib/simplecov/cli/patch/output.rb', line 51 def criterion_cells(row, color) cells = [criterion_cell("lines", row.fetch(:line), color)] cells << criterion_cell("branches", row.fetch(:branch), color) if measured?(row.fetch(:branch)) cells << criterion_cell("methods", row.fetch(:method), color) if measured?(row.fetch(:method)) cells end |
#emit(stdout, rows, opts) ⇒ Object
29 30 31 32 33 34 35 |
# File 'lib/simplecov/cli/patch/output.rb', line 29 def emit(stdout, rows, opts) if opts.fetch(:json) stdout.puts(JSON.pretty_generate(json_rows(rows))) else emit_text(stdout, rows, CLI.color_enabled?(opts, stdout)) end end |
#emit_text(stdout, rows, color) ⇒ Object
37 38 39 40 41 42 43 |
# File 'lib/simplecov/cli/patch/output.rb', line 37 def emit_text(stdout, rows, color) return stdout.puts("simplecov patch: no coverable lines changed") if rows.empty? rows.sort_by! { |row| [pct(row.fetch(:line)), row.fetch(:file)] } rows.each { |row| stdout.puts(format_row(row, color)) } stdout.puts(format_total(rows, color)) end |
#format_row(row, color) ⇒ Object
45 46 47 48 49 |
# File 'lib/simplecov/cli/patch/output.rb', line 45 def format_row(row, color) line = " #{criterion_cells(row, color).join(" ")} #{row.fetch(:file)}" note = missing_note(row) note.empty? ? line : "#{line} #{note}" end |
#format_total(rows, color) ⇒ Object
76 77 78 79 |
# File 'lib/simplecov/cli/patch/output.rb', line 76 def format_total(rows, color) totals = {line: sum_stats(rows, :line), branch: sum_stats(rows, :branch), method: sum_stats(rows, :method)} " Patch coverage: #{criterion_cells(totals, color).join(", ")}" end |
#gate(rows, minimum) ⇒ Object
No --minimum is report-only. With one, every measured criterion must clear
the floor, and short? never fails an unmeasured one.
131 132 133 134 135 136 |
# File 'lib/simplecov/cli/patch/output.rb', line 131 def gate(rows, minimum) return 0 unless minimum below = %i[line branch method].any? { |criterion| short?(sum_stats(rows, criterion), minimum) } below ? 1 : 0 end |
#json_rows(rows) ⇒ Object
81 82 83 84 85 86 87 88 89 90 |
# File 'lib/simplecov/cli/patch/output.rb', line 81 def json_rows(rows) rows.map do |row| data = {file: row.fetch(:file), line: row.fetch(:line).merge(percent: pct(row.fetch(:line)))} OPTIONAL_CRITERIA.each do |criterion| stats = row.fetch(criterion) data[criterion] = stats.merge(percent: pct(stats)) if measured?(stats) end data end end |
#measured?(stats) ⇒ Boolean
Branch and method stats are nil when the report never measured them, and 0/0 when the change touched none.
94 95 96 |
# File 'lib/simplecov/cli/patch/output.rb', line 94 def measured?(stats) stats.is_a?(Hash) && stats.fetch(:relevant).positive? end |
#missing_note(row) ⇒ Object
66 67 68 69 70 71 72 73 74 |
# File 'lib/simplecov/cli/patch/output.rb', line 66 def missing_note(row) parts = [] #: Array[String] parts << "missing #{ranges(row.fetch(:line).fetch(:missing))}" if row.fetch(:line).fetch(:missing).any? OPTIONAL_CRITERIA.each do |criterion| stats = row.fetch(criterion) parts << "#{criterion} #{ranges(stats.fetch(:missing))}" if measured?(stats) && stats.fetch(:missing).any? end parts.join(" ") end |
#only(run) ⇒ Object
mutant:disable
109 110 111 |
# File 'lib/simplecov/cli/patch/output.rb', line 109 def only(run) format("%<only>d", only: run.first) end |
#pct(stats) ⇒ Object
122 123 124 125 126 127 |
# File 'lib/simplecov/cli/patch/output.rb', line 122 def pct(stats) relevant = stats.fetch(:relevant) return 100.0 if relevant.zero? (stats.fetch(:covered).to_f / relevant * 100).round(2) end |
#ranges(numbers, separator = ", ") ⇒ Object
Collapses a sorted line list into ranges: [41, 42, 43, 47] -> "41-43, 47".
The show subcommand borrows this with a bare-comma separator for its
greppable path:40,52-58,71 form.
101 102 103 104 105 106 |
# File 'lib/simplecov/cli/patch/output.rb', line 101 def ranges(numbers, separator = ", ") numbers.slice_when { |prev, curr| curr > prev + 1 }.each_with_object(+"") do |run, out| out << separator unless out.empty? out << (run.one? ? only(run) : span(run)) end end |
#short?(stats, minimum) ⇒ Boolean
Cross-multiplied rather than compared as percentages so neither rounding
nor float division can shift the verdict at the boundary: reading the
displayed pct would round a 19_999/20_000 patch up to 100 and pass it
against --minimum 100, while dividing makes 23/40 compute as 57.4999...
and fail an exactly-57.5% patch. The minimum becomes an exact Rational
from its own text, and covered * 100 is an integer, so the comparison is
exact. A criterion with nothing relevant never falls short.
145 146 147 |
# File 'lib/simplecov/cli/patch/output.rb', line 145 def short?(stats, minimum) stats.fetch(:covered) * 100 < Rational(minimum.to_s) * stats.fetch(:relevant) end |
#span(run) ⇒ Object
113 114 115 |
# File 'lib/simplecov/cli/patch/output.rb', line 113 def span(run) format("%<from>d-%<to>d", from: run.first, to: run.last) end |
#sum_stats(rows, criterion) ⇒ Object
117 118 119 120 |
# File 'lib/simplecov/cli/patch/output.rb', line 117 def sum_stats(rows, criterion) stats = rows.filter_map { |row| row.fetch(criterion) } {covered: stats.sum { |stat| stat.fetch(:covered) }, relevant: stats.sum { |stat| stat.fetch(:relevant) }} end |