Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ISC License

Copyright (c) 2012-2015 Jason Cranmer, 2019-2025 Ole Henrik Dahle and Ann-Karin Kihle
Included D3 library is also under the ISC license, Copyright 2010-2021 Mike Bostock

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Various code coverage scripts, originally used for the Mozilla (browser) project by Joshua Cranmer.
Forked from https://github.com/jcranmer/mozilla-coverage, since that project is no longer maintained.
The Python scripts rely on lcov and d3.js to produce colored tables and treemaps to illustrate code coverage.
35 changes: 24 additions & 11 deletions ccov.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import shutil
import subprocess
import tempfile
import sys

def format_set_difference(a, b):
if a == b:
Expand Down Expand Up @@ -35,7 +36,7 @@ def add_line_hit(self, line, hitcount):

def lines(self):
'''Returns an iterator over (line #, hit count) for this file.'''
for i in xrange(len(self._lines)):
for i in range(len(self._lines)):
count = self._lines[i]
if count != -1:
yield (i, count)
Expand All @@ -53,8 +54,13 @@ def add_function_hit(self, name, hitcount, lineno=None):
def functions(self):
'''Returns an iterator over (function name, line #, hit count) for this
file.'''
for func, fndata in self._funcs.iteritems():
yield (func, fndata[0], fndata[1])
if sys.version_info[0] == 2:
# Python 2 has iteritems, Python 3 has items
for func, fndata in self._funcs.iteritems():
yield (func, fndata[0], fndata[1])
else:
for func, fndata in self._funcs.items():
yield (func, fndata[0], fndata[1])

def add_branch_hit(self, lineno, brno, targetid, count):
'''Note that the brno'th branch on the line number going to the targetid
Expand All @@ -65,10 +71,17 @@ def add_branch_hit(self, lineno, brno, targetid, count):
def branches(self):
'''Returns an iterator over (line #, branch #, [ids], [counts]) for this
file.'''
for tup in self._branches.iteritems():
items = tup[1].items()
items.sort()
yield (tup[0][0], tup[0][1], [x[0] for x in items], [x[1] for x in items])
if sys.version_info[0] == 2:
# Python 2 has iteritems, Python 3 has items
for tup in self._branches.iteritems():
items = tup[1].items()
items.sort()
yield (tup[0][0], tup[0][1], [x[0] for x in items], [x[1] for x in items])
else:
for tup in self._branches.items():
items = tup[1].items()
items.sort()
yield (tup[0][0], tup[0][1], [x[0] for x in items], [x[1] for x in items])

def write_lcov_output(self, fd):
'''Writes the record for this file to the file descriptor in the LCOV
Expand Down Expand Up @@ -201,7 +214,7 @@ def loadGcdaTree(self, testname, gcdaDir):
gcnodata.add_to_coverage(self, testname, dirpath)
return
for dirpath, dirnames, filenames in os.walk(gcdaDir):
print 'Processing %s' % dirpath
print("Processing ", dirpath)
gcda_files = filter(lambda f: f.endswith('.gcda'), filenames)
gcno_files = [f[:-2] + 'no' for f in gcda_files]
filepairs = [(da, no) for (da, no) in zip(gcda_files, gcno_files)
Expand Down Expand Up @@ -301,7 +314,7 @@ def __init__(self, basedir, gcovtool='gcov', table={}):
self.table = table

def loadDirectory(self, directory, gcda_files):
print 'Processing %s' % directory
print("Processing ", directory)
gcda_files = map(lambda f: os.path.join(directory, f), gcda_files)
gcovdir = tempfile.mktemp("gcovdir")
os.mkdir(gcovdir)
Expand Down Expand Up @@ -389,7 +402,7 @@ def main(argv):
coverage = CoverageData()
if opts.more_files == None: opts.more_files = []
for lcovFile in opts.more_files:
print >> sys.stderr, "Reading file %s" % lcovFile
sys.stderr.write("Reading file ", lcovFile)
fd = open(lcovFile, 'r')
coverage.addFromLcovFile(fd)

Expand All @@ -404,7 +417,7 @@ def main(argv):
coverage.filterFilesByGlob(opts.extract_glob)
# Store it to output
if opts.outfile != None:
print >> sys.stderr, "Writing to file %s" % opts.outfile
sys.stderr.write("Writing to file", opts.outfile)
outfd = open(opts.outfile, 'w')
else:
outfd = sys.stdout
Expand Down
79 changes: 60 additions & 19 deletions make_ui.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/python

import cgi
import json
import os
import shutil
Expand All @@ -9,39 +8,48 @@

def main(argv):
from optparse import OptionParser
o = OptionParser()
usage = "Usage: %prog [options] inputfile(s)"
o = OptionParser(usage)
o.add_option('-o', '--output', dest="outdir",
help="Directory to store all HTML files", metavar="DIRECTORY")
o.add_option('-s', '--source-dir', dest="basedir",
help="Base directory for source code", metavar="DIRECTORY")
o.add_option('-l', '--limits', dest="limits",
help="Custom limits for medium,high coverage")
(opts, args) = o.parse_args(argv)
if opts.outdir is None:
print "Need to pass in -o!"
print("Need to pass in -o!")
sys.exit(1)

if len(args) < 2:
print("Need to specify at least one input file!")
sys.exit(1)

# Add in all the data
cov = CoverageData()
for lcovFile in args[1:]:
print("Reading coverage data from", lcovFile)
cov.addFromLcovFile(open(lcovFile, 'r'))

# Make the output directory
if not os.path.exists(opts.outdir):
os.makedirs(opts.outdir)

print ('Building UI...')
builder = UiBuilder(cov, opts.outdir, opts.basedir)
builder = UiBuilder(cov, opts.outdir, opts.basedir, opts.limits)
builder.makeStaticOutput()
builder.makeDynamicOutput()

class UiBuilder(object):
def __init__(self, covdata, outdir, basedir):
def __init__(self, covdata, outdir, basedir, limits):
self.data = covdata
self.flatdata = self.data.getFlatData()
self.outdir = outdir
self.uidir = os.path.dirname(__file__)
self.basedir = basedir
self.limits = limits
self.relsrc = None
self.tests = ['all']
self.tests = []

def _loadGlobalData(self):
json_data = self.buildJSONData(self.flatdata)
Expand Down Expand Up @@ -126,7 +134,8 @@ def makeStaticOutput(self):
def makeDynamicOutput(self):
# Dump out JSON files
json_data = self._loadGlobalData()
json.dump(json_data, open(os.path.join(self.outdir, 'all.json'), 'w'))
if 'all' in self.tests :
json.dump(json_data, open(os.path.join(self.outdir, 'all.json'), 'w'))
for test in self.data.getTests():
small_data = self.data.getTestData(test)
if len(small_data) == 0:
Expand Down Expand Up @@ -154,6 +163,14 @@ def _readTemplate(self, name):

def _makeDirectoryIndex(self, dirname, jsondata):
# Utility method for printing out rows of the table
mediumLimit = 75.0
highLimit = 90.0
if self.limits:
values = self.limits.split(",");
if len(values) == 2:
mediumLimit = float(values[0])
highLimit = float(values[1])

def summary_string(lhs, jsondata):
output = '<tr>'
output += '<td>%s</td>' % lhs
Expand All @@ -164,15 +181,17 @@ def summary_string(lhs, jsondata):
output += '<td>0 / 0</td><td>-</td>'
else:
ratio = 100.0 * hit / count
if ratio < 75.0: clazz = "lowcov"
elif ratio < 90.0: clazz = "mediumcov"
if ratio < mediumLimit: clazz = "lowcov"
elif ratio < highLimit: clazz = "mediumcov"
else: clazz = "highcov"
output += '<td class="%s">%d / %d</td><td class="%s">%.1f%%</td>' % (
clazz, hit, count, clazz, ratio)
return output + '</tr>'
htmltmp = self._readTemplate('directory.html')

jsondata['files'].sort(lambda x, y: cmp(x['name'], y['name']))
if dirname:
htmltmp = self._readTemplate('directory.html')
else:
htmltmp = self._readTemplate('root_directory.html')
jsondata['files'].sort(key=lambda x: x['name'])

# Parameters for output
parameters = {}
Expand All @@ -185,10 +204,12 @@ def summary_string(lhs, jsondata):
('<option>%s</option>' % test) for test in self.tests)
from datetime import date
parameters['date'] = date.today().isoformat()
if not dirname:
parameters['reponame'] = os.getcwd()[os.getcwd().rfind('/')+1:len(os.getcwd())]

def htmlname(json):
if len(json['files']) > 0:
return json['name']
return json['name'] + "/index.html"
else:
return json['name'] + '.html'
tablestr = '\n'.join(summary_string(
Expand All @@ -214,13 +235,28 @@ def htmlname(json):
self._makeFileData(dirname, child['name'], child)

def _makeFileData(self, dirname, filename, jsondata):
print 'Writing %s/%s.html' % (dirname, filename)
htmltmp = self._readTemplate('file.html')
# Python 2 / 3 compatibility fix
try:
import html
except ImportError:
import cgi as html

if dirname:
if dirname == 'inc':
htmltmp = self._readTemplate('single_test_file.html')
else:
htmltmp = self._readTemplate('file.html')
else:
htmltmp = self._readTemplate('single_test_file.html')
parameters = {}
parameters['file'] = os.path.join(dirname, filename)
parameters['directory'] = dirname
parameters['depth'] = '/'.join('..' for x in dirname.split('/'))

if dirname:
parameters['depth'] = '/'.join('..' for x in dirname.split('/'))
else:
parameters['depth'] = '.'

parameters['testoptions'] = '\n'.join(
'<option>%s</option>' % s for s in self.tests)
from datetime import date
Expand All @@ -234,8 +270,13 @@ def _makeFileData(self, dirname, filename, jsondata):
'<tr><td colspan="5">File could not be found</td></tr>')
parameters['data'] = ''
else:
with open(srcfile, 'r') as fd:
srclines = fd.readlines()
if sys.version_info[0] == 2:
# Python 2 version of open
with open(srcfile, mode="r") as fd:
srclines = fd.readlines()
else:
with open(srcfile, mode="r", encoding="utf-8", errors="ignore") as fd:
srclines = fd.readlines()

flatdata = self.flatdata[filekey]
del self.flatdata[filekey] # Scavenge memory we don't need anymore.
Expand Down Expand Up @@ -278,7 +319,7 @@ def _makeFileData(self, dirname, filename, jsondata):
outlines.append((' <tr%s><td>%d</td>' +
'<td>%s</td><td>%s</td><td>%s</td></tr>\n'
) % (covstatus, lineno, brcount, linecount,
cgi.escape(line.rstrip())))
html.escape(line.rstrip())))
lineno += 1
parameters['tbody'] = ''.join(outlines)

Expand Down
20 changes: 18 additions & 2 deletions uitemplates/coverage.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
.style("position", "relative")
.style("width", width + "px")
.style("height", height + "px");
d3.json("all.json", loadJsonData);
d3.json(d3.select("#testsuite").node().value + ".json", loadJsonData);

// Bind the coverage scale
d3.select("#scale").selectAll("rect")
Expand Down Expand Up @@ -171,6 +171,7 @@
}

function get_source_file(data) {
if(!data) return '';
if ("_path" in data)
return data._path;
if ('parent' in data) {
Expand Down Expand Up @@ -210,7 +211,22 @@
while(d && d.parent != root) d = d.parent;
}
if (d)
reroot(d);
{
if (d.files.length > 0)
{
reroot(d);
} else
{
// Single file, open textual view
var url = window.location.href;
if (url.indexOf('?'))
{
url = url.substr(0,url.indexOf('?') -1);
}
var directory = url.substr(0,url.lastIndexOf("/"));
window.location.href = directory + "/" + d._path + ".html"
}
}
}).transition().delay(2000).style("opacity", 1);
nodes.order();

Expand Down
6 changes: 3 additions & 3 deletions uitemplates/directory.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
</head>
<body onload="onDirectoryLoad()">
<h1>Code coverage report for <span id="filepath">${directory}</span></h1>
<hr>
<div class="info-container">
<div class="info-panel">Test:
<select id="testsuite">${testoptions}</select>
</div>
<div class="info-panel">Date compiled: ${date}</div>
<div class="info-panel"><a href="${depth}/coverage.html?dir=${directory}">Graphical Overview</a> | <b>Detailed Report</b></div>
<div class="info-panel"><a href="..">Parent directory</a></div>
<hr>
<div class="info-panel"><a href="../index.html">Parent directory</a></div>
</div>
<table id="coveredtable">
<thead>
<tr><th>Filename</th><th colspan="2">Line</th><th colspan="2">Function</th><th colspan="2">Branch</th></tr>
Expand Down
30 changes: 30 additions & 0 deletions uitemplates/root_directory.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8"/>
<title>Code coverage for ${directory}</title>
<link href="${depth}/ccov.css" rel="stylesheet" type="text/css" />
<script src="${depth}/dynamic-results.js"></script>
<script src="${depth}/d3.v3.min.js" charset="UTF-8"></script>
<script>var path="${directory}".split("/"); var depth="${depth}";</script>
</head>
<body onload="onDirectoryLoad()">
<h1>Code coverage report for <span id="reponame">${reponame}</span></h1>
<div class="info-container">>
<div class="info-panel">Test:
<select id="testsuite">${testoptions}</select>
</div>
<div class="info-panel">Date compiled: ${date}</div>
<div class="info-panel"><a href="${depth}/coverage.html?dir=${directory}">Graphical Overview</a> | <b>Detailed Report</b></div>
</div>
<table id="coveredtable">
<thead>
<tr><th>Filename</th><th colspan="2">Line</th><th colspan="2">Function</th><th colspan="2">Branch</th></tr>
</thead>
<tbody>
${tbody}
</tbody>
<tfoot>
${tfoot}
</table>
</body>
</html>
Loading