Custom Source Control: Code Coverage

I originally started writing this post a few months ago. Unfortunately I got wrapped up in other things and had to put this off. I don’t have much to add but wanted to just wrap this up. This is a continuation of my last post on refactoring the custom source control application in ruby. I will be working off the previous code, so be sure to grab a copy if you don’t already have it.

A while back, Aaron Patterson @tenderlove wrote a blog post on code coverage and ruby. He wrote a very basic code coverage tool using ruby’s built in stuff. I can’t find that post otherwise I’d link to it. I used what I learned in that post to build something to capture our code coverage in csc.

In specs/custom_coverage.rb add the following:

require 'erb'

class CustomCoverage
def self.build(coverages)
_template = ERB.new template, nil, '><'
body = coverages.map do |filename, coverage|
source = File.readlines filename
stats = stats(coverage)
_template.result binding
end
header << body.join << footer
end

def self.stats(coverage)
stats = { hits: 0, misses: 0, nils: 0 }
coverage.each do |stat|
if stat.nil?
stats[:nils] += 1
elsif stat == 0
stats[:misses] += 1
elsif stat >= 1
stats[:hits] += 1
stats
end
end
stats[:coverage] = ((stats[:hits].to_f / (stats[:hits] + stats[:misses])) * 100).round(2)
stats
end

def self.footer
%q[</body>
</html>]
.gsub(/^[ ]{6}/, '')
end

def self.header
%q[<html>
<head>
<title>Custom Coverage</title>
<style>
.empty{background-color:#fff;}
.hit{background-color:#cdf2cd;}
.miss{background-color:#f7cfcf;}
.never{background-color:#efefef;}
</style>
</head>
<body style="width:960px;margin: 0 auto;padding:25px 0;">
<h1 style="width:100%;text-align:center;">Custom Coverage</h1>]
.gsub(/^[ ]{6}/, '')
end

def self.template
%q[<hr />
<h2 style="text-align:center;"><%= filename %></h2>
<h3 style="text-align:center;">Coverage: <%= stats[:coverage] %>%</h3>
<table style="width:100%;border:1px solid #000">
<tr>
<th>Total Lines</th>
<th>Relevant Lines</th>
<th>Irrelevant Lines</th>
<th>Covered Lines</th>
<th>Missed Lines</th>
</tr>
<tr>
<td style="text-align:center;"><%= stats[:hits] + stats[:misses] + stats[:nils] %></td>
<td style="text-align:center;"><%= stats[:hits] + stats[:misses] %></td>
<td style="text-align:center;"><%= stats[:nils] %></td>
<td style="text-align:center;"><%= stats[:hits] %></td>
<td style="text-align:center;"><%= stats[:misses] %></td>
</tr>
</table>
<table style="width:100%;border:1px solid #000">
<% source.zip(coverage).each_with_index do |(line, cov), idx| %>
<% classname = cov ? (cov > 0 ? 'hit' : 'miss') : (line.chomp.empty? ? 'empty' : 'never' ) %>
<tr class="<%= classname %>" <%= cov ? "data-hits=\"#{cov}\"" : '' %>>
<th>
<a name="line<%= idx + 1 %>"><%= idx + 1 %></a>
</th>
<td>
<pre><%= line.chomp %></pre>
</td>
</tr>
<% end %>
</table>]
.gsub(/^[ ]{6}/, '')
end
end

require 'coverage'
Coverage.start

at_exit do
coverages = Coverage.result
coverage = {}
base_dir = File.expand_path(File.join(File.dirname(File.realpath(__FILE__)), '..'))
lib_dir = File.expand_path(File.join(base_dir, 'lib'))
sources = Dir.glob(File.join(lib_dir, '**', '*.rb'))
coverages.each do |src, cov|
if sources.include? src
coverage[src] = cov
end
end

report = CustomCoverage.build coverage

cov_dir = File.join(base_dir, 'coverage')
FileUtils.mkdir_p cov_dir
File.open(File.join(cov_dir, 'index.html'), 'w') do |file|
file.write report
end
end

In specs/helper replace the contents with the following:

require 'custom_coverage'

require 'minitest/autorun'
require 'minitest/mock'

require 'custom_source_control'

Now if you run it you’ll get some coverage to look at.

$ rake test
$ open index.html

Custom Source Control: Code Coverage Report

This was a fun experiment for me. It helped me better understand git, touched on TDD, and now code coverage. I hope those of you reading found it useful. Feel free to leave me any comments or feedback. You can find the project on github.

comments powered by Disqus