Skip to content

Instantly share code, notes, and snippets.

@cykod
Created November 29, 2011 14:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cykod/1404986 to your computer and use it in GitHub Desktop.
Save cykod/1404986 to your computer and use it in GitHub Desktop.
RCov + `git blame` = Find out who the non-testing Goat on your Rails project is
#!/usr/bin/env ruby1.8
require 'rubygems'
require 'nokogiri'
# Copyright @2011 Pascal Rettig - Released under the MIT License,
# No Warranty whatsoever. This will probably destroy your project
#
# Find out who the goat is on your Rails the project - i.e the person responsible for
# writing or modifying the most untested code. Drop goat.rb into your script/
# directory, make it executable then run:
# rake spec:rcov
# ./script/goat.rb
#
# goat.rb will modify each of the .html files in your coverage/ directory to add the commit
# and user who created the commit from git blame, allowing you to see who is responsible for
# submitting the untested code. The script will also output CSV data with each committer's name,
# the # of lines of tested code they are responsible for, the # of lines of untested code for
# committer is responsible for. Sample output in a coverage file: http://bit.ly/rufrwm
# the script ignores inferred lines, counting only marked and uncovered
#
# If you want the script to put in links to the specific commits, modify GITHUB_COMMIT_PATH
# according to the sample.
#
# If you pass in an argument, goat.rb will assume this is a directory where you want it to stick
# a day-labled file (so, in theory you could run rcov and goat.rb each day and then compile a time series
# of the csv's.)
#
# Notes
# 1. this script is fairly brittle and will probably break if you aren't using the same version of
# rcov as I was
# 2. Please don't put this to serious use, someone could just adjust the indenting of a bunch of
# lines of tested code to push their %'s up (however I can't think of any way of pushing your LOC
# down without writing tests)
#
# After writing this, I discovered I was the goat.
APP_PATH = File.expand_path('../../', __FILE__)
COVERAGE_PATH = File.expand_path('coverage',APP_PATH)
# Put in your github path here:
# eg: http://github.com/cykod/Webiva.com/commit
GITHUB_COMMIT_PATH ="#"
# sort by highest percentage
# sort_by = 4
# sort by highest LOC
sort_by = 1
user_count = {}
Dir.glob(File.join(COVERAGE_PATH,"*.html")).each do |file|
# Read in the file
coverage_file = File.open(file,"r").readlines
coverage_doc = Nokogiri::HTML(coverage_file.join("\n"))
# Get the file name
h2 = coverage_doc.css('h2').first
app_filename = h2.content if h2
# Make sure it doesn't start with spec/
unless !h2 || app_filename =~ /^spec/
puts app_filename
# execute the git blame
output = `git blame #{File.expand_path(app_filename, APP_PATH)}`.split("\n")
# Get the commit and blamee
output.map! do |line|
if line =~ /^([\^a-z0-9]+)( [^\(]+)? \((.*?) \d{4}-\d{2}-\d{2}/
[ $1, $3.strip ]
else
[]
end
end
last_line = nil
if coverage_file && output.length > 1
coverage_file.map! do |cov|
if cov =~ /<tr class="(inferred|uncovered|marked)">/
last_line = $1
cov
elsif cov =~ /<(td|TD)><pre><a name="line(\d+)">(\d+)<\/a>/
blame_content = output[$2.to_i - 1]
blame_commit = blame_content[0]
blame_user= blame_content[1]
user_count[blame_user] ||= { 'marked' => 0, 'uncovered' => 0 }
user_count[blame_user][last_line] += 1 unless last_line == 'inferred'
# output the line and upcase the existing TD's so we don't add in the commit data
# twice if we've already run this once
if $1 == 'TD'
cov
else
"<td><a href='#{GITHUB_COMMIT_PATH}/#{blame_commit}'>#{blame_commit}</a> #{blame_user}</td>#{cov.gsub("<td>","<TD>")}"
end
else
cov
end
end
File.open(file,'w') { |f| f.write(coverage_file.join("\n")) }
end
end
end
user_list = user_count.to_a.map do |user|
[ user[0],
user[1]['uncovered'],
user[1]['marked'],
user[1]['uncovered'] + user[1]['marked'],
(user[1]['uncovered'].to_f / (user[1]['uncovered'] + user[1]['marked']) * 100).round()
]
end
output = "User,Uncovered LOC,Marked LOC, Both, Percentage\n" +
user_list.sort { |a,b| b[sort_by] <=> a[sort_by] }.map { |line| line.join(", ") }.join("\n")
if ARGV[0]
now = Time.now
File.open(File.join(ARGV[0], "goat-#{now.strftime("%Y-%m-%d")}.csv"),"w") { |f| f.write(output) }
end
puts output
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment