diff --git a/Readme.md b/Readme.md index 42b0341..9f9a7d0 100644 --- a/Readme.md +++ b/Readme.md @@ -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 diff --git a/lib/single_cov.rb b/lib/single_cov.rb index 3aacce9..0d83b54 100644 --- a/lib/single_cov.rb +++ b/lib/single_cov.rb @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/specs/single_cov_spec.rb b/specs/single_cov_spec.rb index eefd6e5..6e184fa 100644 --- a/specs/single_cov_spec.rb +++ b/specs/single_cov_spec.rb @@ -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