Skip to content

Commit

Permalink
make missing else obvious
Browse files Browse the repository at this point in the history
  • Loading branch information
grosser committed May 20, 2023
1 parent d1634e6 commit c45c427
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 15 deletions.
6 changes: 4 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ SingleCov::PREFIXES_TO_IGNORE << "public"

### Missing coverage for implicit `else` in `if` or `case` statements

When a report shows for example `1:14-16 # else`, that indicates that the implicit else is not covered.

```ruby
# needs one test case for true and one for false (implicit else)
# needs 2 tests: one for `true` and one for `false`
raise if a == b

# needs one test case for `when b` and one for `else` (implicit else)
# needs 2 tests: one for `when b` and one for `else`
case a
when b
end
Expand Down
27 changes: 15 additions & 12 deletions lib/single_cov.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def all_covered?(result)
# ignore lines that are marked as uncovered via comments
# TODO: warn when using uncovered but the section is indeed covered
content = File.readlines("#{root}/#{file}")
uncovered.reject! do |line_start, _, _, _|
uncovered.reject! do |line_start, _, _, _, _|
content[line_start - 1].match?(UNCOVERED_COMMENT_MARKER)
end
next if uncovered.size == expected_uncovered
Expand Down Expand Up @@ -148,11 +148,11 @@ def uncovered(coverage)

uncovered_lines = indexes(coverage.fetch(:lines), 0).map! { |i| i + 1 }
uncovered_branches = uncovered_branches(coverage[:branches] || {})
uncovered_branches.reject! { |k| uncovered_lines.include?(k[0]) } # ignore branch when whole line is uncovered
uncovered_branches.reject! { |br| uncovered_lines.include?(br[0]) } # ignore branch when whole line is uncovered

# combine lines and branches while keeping them sorted
all = uncovered_lines.concat uncovered_branches
all.sort_by! { |line_start, char_start, _, _| [line_start, char_start || 0] } # branches are unsorted
all.sort_by! { |line_start, char_start, _, _, _| [line_start, char_start || 0] } # branches are unsorted
all
end

Expand All @@ -169,18 +169,21 @@ def main_process?
(!defined?(@main_process_pid) || @main_process_pid == Process.pid)
end

# {[branch_id] => {[branch_part] => coverage}} --> uncovered location
def uncovered_branches(coverage)
# {[branch_id] => {[branch_part] => coverage}} --> {location -> sum-of-coverage}
sum = Hash.new(0)
sum = {}
coverage.each_value do |branch|
branch.each do |part, c|
sum[[part[2], part[3] + 1, part[4], part[5] + 1]] += c
branch.filter_map do |part, c|
location = [part[2], part[3] + 1, part[4], part[5] + 1] # locations can be duplicated
type = part[0]
info = (sum[location] ||= [0, nil])
info[0] += c
info[1] = type if type == :else # only else is important to track since it often is not in the code
end
end

# show missing coverage
sum.select! { |_, v| v == 0 }
sum.keys
# keep location and type of missing coverage
sum.filter_map { |k, v| k + [v[1]] if v[0] == 0 }
end

def default_tests
Expand Down Expand Up @@ -325,13 +328,13 @@ def bad_coverage_error(file, expected_uncovered, uncovered)
[
"#{file} new uncovered lines introduced #{details}",
red("Lines missing coverage:"),
*uncovered.map do |line_start, char_start, line_end, char_end|
*uncovered.map do |line_start, char_start, line_end, char_end, type|
if char_start # branch coverage
if line_start == line_end
"#{file}:#{line_start}:#{char_start}-#{char_end}"
else # possibly unreachable since branches always seem to be on the same line
"#{file}:#{line_start}:#{char_start}-#{line_end}:#{char_end}"
end
end + (type ? " # #{type}" : "")
else
"#{file}:#{line_start}"
end
Expand Down
10 changes: 9 additions & 1 deletion specs/single_cov_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,15 @@ def add_missing_coverage(&block)
change_file("lib/a.rb", "i == 0", "i == 0 if i if 0 if false") do
result = sh "ruby test/a_test.rb", fail: true
expect(result).to include ".lib/a.rb new uncovered lines introduced (3 current vs 0 configured)"
expect(result).to include "lib/a.rb:4:19-23\nlib/a.rb:4:19-33\nlib/a.rb:4:19-38"
expect(result).to include "lib/a.rb:4:19-23\nlib/a.rb:4:19-33 # else\nlib/a.rb:4:19-38 # else"
end
end

it "complains about missing else coverage" do
change_file("lib/a.rb", "2.times", "1.times") do
result = sh "ruby test/a_test.rb", fail: true
expect(result).to include ".lib/a.rb new uncovered lines introduced (1 current vs 0 configured)"
expect(result).to include "lib/a.rb:4:19-33 # else"
end
end

Expand Down

0 comments on commit c45c427

Please sign in to comment.