public
Created

Annotate coverage report with lines modified by patch

  • Download Gist
patch_coverage.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
#!/usr/bin/env python
 
import os
from collections import defaultdict
import patch
import re
import coverage
import django
from optparse import OptionParser
import webbrowser
import sys
 
 
 
"""
requires http://python-patch.googlecode.com/svn/trunk/patch.py
 
"""
# TODO don't include files in report when lines are only removed
 
django_path = os.path.abspath(os.path.dirname(os.path.dirname(django.__file__)))
coverage_html_dir = os.path.join(django_path, 'tests/coverage_html')
line_end = '(?:\n|\r\n?)'
 
# pattern to use to insert new stylesheet
# this is currently pretty brittle - but lighterweight than doing something with
# lxml and/or pyquery
current_style = "<link rel='stylesheet' href='style.css' type='text/css'>"
 
def parse_patch(patch_file):
"""
returns a dictionary of {filepath:[lines patched]}
"""
patch_set = patch.fromfile(patch_file)
target_files = set()
target_files.update([os.path.join(django_path, p.target.lstrip('/ab')) for p in patch_set.items])
target_files = [p for p in target_files if 'test' not in p]
target_files = [p for p in target_files if 'docs' not in p]
target_files = [p for p in target_files if os.path.exists(p)]
target_lines = defaultdict(list)
 
for p in patch_set.items:
source_file = os.path.join(django_path, p.target)
if source_file not in target_files:
continue
 
source_lines = open(source_file, 'r').readlines()
last_hunk_offset = 0
for hunk in p.hunks:
patched_lines = []
hunkreplace = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " +"]
hunk_offset = last_hunk_offset
in_candidate_match = False
hunk_pos = 0
for i, line in enumerate(source_lines):
# I tried unsucessfully to get multiline re to work,
# fell back to this
if i < last_hunk_offset:
continue
if hunk_pos > len(hunkreplace)-1:
break
if hunkreplace[hunk_pos] == line:
if not in_candidate_match:
in_candidate_match = True
hunk_offset = i
hunk_pos += 1
continue
else:
hunk_pos += 1
continue
else:
in_candidate_match = False
hunk_offset = 0
hunk_pos = 0
continue
 
line_offset = hunk_offset
for hline in hunk.text:
if hline.startswith('-'):
continue
if hline.startswith('+'):
patched_lines.append(hunk_offset)
line_offset += 1
target_lines[p.target].extend(patched_lines)
last_hunk_offset = hunk_offset
return target_lines
 
 
 
 
def generate_css(target_lines):
coverage_files = os.listdir(coverage_html_dir)
 
for target in target_lines:
target_name = target.replace('/', '_')
fname = target_name.replace(".py", ".css")
html_name = target_name.replace(".py", ".html")
css = ','.join(["#n%s" %l for l in target_lines[target]])
css += " {background: lightgreen;}"
css_file = os.path.join(coverage_html_dir, fname)
with open(css_file, 'w') as f:
f.write(css)
html_pattern = re.compile(html_name)
# html_file = filter(html_pattern.search, coverage_files)
html_file = [p for p in coverage_files if html_pattern.search(p)]
if len(html_file) != 1:
raise ValueError("Found wrong number of matching html files")
html_file = os.path.join(coverage_html_dir,html_file[0])
 
html_source = open(html_file, 'r').read()
style_start = html_source.find(current_style)
new_html = html_source[:style_start]
new_html += "<link rel='stylesheet' href='%s' type='text/css'>\n" % fname
new_html += html_source[style_start:]
os.unlink(html_file)
with open(html_file, 'w') as f:
f.write(new_html)
 
 
 
 
if __name__ == "__main__":
opt = OptionParser()
(options, args) = opt.parse_args()
if not args:
print "No patch file provided"
sys.exit(1)
patchfile = args[0]
target_lines = parse_patch(patchfile)
 
# generate coverage reports
cov = coverage.coverage(data_file = os.path.join(django_path, 'tests', '.coverage'))
cov.load()
targets = [os.path.join(django_path, x) for x in target_lines.keys()]
print targets
cov.html_report(morfs=targets, directory=coverage_html_dir)
 
generate_css(target_lines)
webbrowser.open(os.path.join(coverage_html_dir, 'index.html'))

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.