Created
November 29, 2011 14:30
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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