我们每次修改代码大多数都是增量的,而通过lcov生成的代码覆盖率报告都是全量的,不方便查看,所以写了一个脚本,增量了增量部分的数据。脚本如下:
#!/usr/bin/python2
######################################################################
# Purpose: 根据git提交记录,修改代码覆盖率的html,增加增量的数据
# Author: rodinluo
######################################################################
__doc__ = '''
USAGE:
python ./incre_lconv.py <since>..<until> <check_dir> <lcov_html_dir><project_name>
example:
python incre_lcov.py 8b2628dfe839b6086e74721c93469b0cf18720e7..HEAD '["test1dir","test2dir"]' /data/ projectname
since:git 开始的版本
util:git 结束的版本
check_dir:关注的代码目录
lcov_html_dir:已经通过lcov生成的html根目录
project_name:项目名
本脚本应该在project_name目录执行,project_name目录包含test1dir\test2dir,/data下面是project_name/*
'''
import sys, os, re
import json
import subprocess as commands
from pprint import *
from bs4 import *
DEBUG = 0
class UTCover(object) :
def __init__(self, since_until, check_dirs, lcov_dir, root_dir) :
self.since, self.until = since_until.split('..')
self.check_dirs = json.loads(check_dirs)
self.lcov_dir = lcov_dir
self.root_dir = root_dir
def get_src(self):
satus, output = commands.getstatusoutput("git diff --name-only %s %s" %(self.since, self.until))
if DEBUG: print(output)
src_files = [f for f in output.split('\n')
for m in self.check_dirs if m in f
if os.path.splitext(f)[1][1:] in ['c', 'cpp']]
if DEBUG: pprint(src_files)
return src_files
def get_change(self, src_files):
changes = {}
for f in src_files:
satus, output = commands.getstatusoutput("git log --oneline %s..%s %s | awk '{print $1}'" %(self.since, self.until, f))
commits = output.split('\n')
cmd = "git blame %s | grep -E '(%s)' | awk -F' *|)' '{print $6}'" %(f, '|'.join(commits))
satus, lines = commands.getstatusoutput(cmd)
changes[f] = [ int(i) for i in lines.split('\n') if i.isdigit() ]
if DEBUG: pprint(changes)
return changes
def update_html(self, f, lines):
hf = os.path.join(self.lcov_dir, self.root_dir, f + '.gcov.html')
print(hf)
if not os.path.exists(hf):
return None
try:
html = open(hf, "r", encoding="utf-8")
handle = html.read()
except UnicodeDecodeError:
print("decode error:" + hf)
return None
soup = BeautifulSoup(handle,'lxml')
a = soup.findAll("a", {"name":True})
lineNoCov = 0
lineCov = 0
for i in range(len(a)):
if int(a[i]["name"]) in lines:
a[i].span["class"][0] = ("updateLineNum")
span = a[i].span.findNextSibling()
if span:
if span["class"][0] == "lineNoCov":
lineNoCov = lineNoCov + 1
else:
if span["class"][0] == "lineCov":
lineCov = lineCov + 1
headerCovTableEntry = soup.findAll("td", { "class" : "headerCovTableEntry"}, limit = 2)
if not headerCovTableEntry:
print("css.class headerCovTableEntry not found")
return None
contents0 = str(lineCov) + " - " + headerCovTableEntry[0].contents[0]
headerCovTableEntry[0].contents[0].replaceWith(contents0)
contents1 = str(lineNoCov + lineCov) + " - " + headerCovTableEntry[1].contents[0]
headerCovTableEntry[1].contents[0].replaceWith(contents1)
headerCovTableEntryX = soup.findAll("td", { "class" : ["headerCovTableEntryLo", "headerCovTableEntryMed", "headerCovTableEntryHi"]}, limit = 1)
if not headerCovTableEntryX :
print("css.class headerCovTableEntry* not found")
return None
totalLine = lineNoCov + lineCov
if totalLine == 0:
totalLine = 1
contents2 = str(round(lineCov / totalLine, 3) * 100) + "% - " + headerCovTableEntryX[0].contents[0]
headerCovTableEntryX[0].contents[0].replaceWith(contents2)
with open(hf, 'w',encoding="utf-8") as fp:
fp.write(soup.prettify())
def check(self):
with open(self.lcov_dir + "/gcov.css", 'a+') as f:
f.write('''
/* Source code view: line update number format */
span.updateLineNum
{
background-color: #33CC00;
}
''')
src_files = self.get_src()
changes = self.get_change(src_files)
for f, lines in changes.items():
self.update_html(f, lines)
if len(sys.argv) == 1:
print (__doc__)
sys.exit(0)
ret = UTCover(*sys.argv[1:]).check()
sys.exit(ret)
效果如下,其中最左边的行号绿底部分表示的是本次修改的代码行,最上面的Lines中,Hit的左边是本次修改代码的覆盖行数,右边是总的覆盖行数,Total的左边是本次修改代码的行数,右边是总的行数,Coverge的左边是本次修改代码的覆盖率,右边是总的覆盖率:
screenshot-20220401-100604.png