When using SimpleCov in a very ill-covered project, I got amazingly good results: SimpleCov just did not count not-covered files. So files that were never used in our test-suite, were just simply being ignored. While I understand that approach, it did not feel good. I want to measure absolute progress, and I want to know how badly it really is.
So, on a mission to count all uncovered files with simplecov, I encountered an issue in their github repo. In itself it contained/mentioned two solutions:
- in a three year old comment, a solution to set a starting base-line, and merge it after the tests. Unfortunately, it did not work completely: all files were added, but with complete coverage. Doh.
- a pull request, claiming to fix it: while it did add all files, files that had before 100% coverage, where now no longer.
Let's get technical. The first solution added a baseline, with for all lines the value nil
. The second added the value 0
for each line. The value nil
is what SimpleCov uses internally to count for a never
line: a line that never matters. Which is either an empty line, a comment line, or begin and end of a class.
The zero is line that is not covered (a 1 is a line that was covered). When merging lines for files that had coverage, I assume the base-line 0
takes precedence over the coverage-calculated nil
and so we end up with a non-covered line in the merged result. Bummer.
So I added my spec_helper.rb
as follows:
if ENV["COVERAGE"]
require 'simplecov'
SimpleCov.start 'rails'
SimpleCov.coverage_dir 'coverage/rspec'
all_files = Dir['**/*.rb']
base_result = {}
all_files.each do |file|
absolute = File::expand_path(file)
lines = File.readlines(absolute, :encoding => 'UTF-8')
base_result[absolute] = lines.map do |l|
l.strip!
l.empty? || l =~ /^end$/ || l[0] == '#' ? nil : 0
end
end
SimpleCov.at_exit do
coverage_result = Coverage.result
covered_files = coverage_result.keys
covered_files.each do |covered_file|
base_result.delete(covered_file)
end
merged = SimpleCov::Result.new(coverage_result).original_result.merge_resultset(base_result)
result = SimpleCov::Result.new(merged)
result.format!
end
end
So, before the test runs, I create a "baseline" containing for all possible ruby files (might need to filter that later), a nil
if the line is empty or the "ending end" of a file, and a zero otherwise. After the tests have run, I remove the covered files from the "baseline", and those are then merged with the result that get the final result.
Now maybe try to translate that into a pull request :)