-#! /usr/bin/python
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
#
# A report generator for gcov 3.4
#
except:
import cgi as html
import copy
-import glob
import os
import re
import subprocess
import xml.dom.minidom
import datetime
import posixpath
+import itertools
+import zlib
from optparse import OptionParser
from string import Template
from os.path import normpath
+try:
+ xrange
+except NameError:
+ xrange = range
+
medium_coverage = 75.0
high_coverage = 90.0
low_color = "LightPink"
high_color = "LightGreen"
covered_color = "LightGreen"
uncovered_color = "LightPink"
+takenBranch_color = "Green"
+notTakenBranch_color = "Red"
-__version__ = "3.2"
+__version__ = "3.3"
src_revision = "$Revision$"
output_re = re.compile("[Cc]reating [`'](.*)'$")
-source_re = re.compile("cannot open (source|graph) file")
+source_re = re.compile("[Cc]annot open (source|graph) file")
starting_dir = os.getcwd()
c_style_comment_pattern = re.compile('/\*.*?\*/')
cpp_style_comment_pattern = re.compile('//.*?$')
+
def version_str():
ans = __version__
m = re.match('\$Revision:\s*(\S+)\s*\$', src_revision)
ans = ans + " (r%s)" % (m.group(1))
return ans
+
#
# Container object for coverage statistics
#
class CoverageData(object):
-
- def __init__(self, fname, uncovered, uncovered_exceptional, covered, branches, noncode):
- self.fname=fname
+ def __init__(
+ self, fname, uncovered, uncovered_exceptional, covered, branches,
+ noncode):
+ self.fname = fname
# Shallow copies are cheap & "safe" because the caller will
# throw away their copies of covered & uncovered after calling
# us exactly *once*
self.uncovered = copy.copy(uncovered)
self.uncovered_exceptional = copy.copy(uncovered_exceptional)
- self.covered = copy.copy(covered)
- self.noncode = copy.copy(noncode)
+ self.covered = copy.copy(covered)
+ self.noncode = copy.copy(noncode)
# But, a deep copy is required here
self.all_lines = copy.deepcopy(uncovered)
self.all_lines.update(uncovered_exceptional)
self.all_lines.update(covered.keys())
self.branches = copy.deepcopy(branches)
- def update(self, uncovered, uncovered_exceptional, covered, branches, noncode):
+ def update(
+ self, uncovered, uncovered_exceptional, covered, branches,
+ noncode):
self.all_lines.update(uncovered)
self.all_lines.update(uncovered_exceptional)
self.all_lines.update(covered.keys())
self.uncovered_exceptional.update(uncovered_exceptional)
self.noncode.intersection_update(noncode)
for k in covered.keys():
- self.covered[k] = self.covered.get(k,0) + covered[k]
+ self.covered[k] = self.covered.get(k, 0) + covered[k]
for k in branches.keys():
for b in branches[k]:
d = self.branches.setdefault(k, {})
def uncovered_str(self, exceptional):
if options.show_branch:
+ #
# Don't do any aggregation on branch results
+ #
tmp = []
for line in self.branches.keys():
for branch in self.branches[line]:
if self.branches[line][branch] == 0:
tmp.append(line)
break
-
tmp.sort()
return ",".join([str(x) for x in tmp]) or ""
-
+
if exceptional:
tmp = list(self.uncovered_exceptional)
else:
if len(tmp) == 0:
return ""
+ #
+ # Walk through the uncovered lines in sorted order.
+ # Find blocks of consecutive uncovered lines, and return
+ # a string with that information.
+ #
tmp.sort()
first = None
last = None
- ranges=[]
+ ranges = []
for item in tmp:
if last is None:
- first=item
- last=item
- elif item == (last+1):
- last=item
+ first = item
+ last = item
+ elif item == (last + 1):
+ last = item
else:
- if len(self.noncode.intersection(range(last+1,item))) \
- == item - last - 1:
- last = item
- continue
-
- if first==last:
+ #
+ # Should we include noncode lines in the range of lines
+ # to be covered??? This simplifies the ranges summary, but it
+ # provides a counterintuitive listing.
+ #
+ # if len(self.noncode.intersection(range(last+1,item))) \
+ # == item - last - 1:
+ # last = item
+ # continue
+ #
+ if first == last:
ranges.append(str(first))
else:
- ranges.append(str(first)+"-"+str(last))
- first=item
- last=item
- if first==last:
+ ranges.append(str(first) + "-" + str(last))
+ first = item
+ last = item
+ if first == last:
ranges.append(str(first))
else:
- ranges.append(str(first)+"-"+str(last))
+ ranges.append(str(first) + "-" + str(last))
return ",".join(ranges)
def coverage(self):
- if ( options.show_branch ):
+ if options.show_branch:
total = 0
cover = 0
for line in self.branches.keys():
else:
total = len(self.all_lines)
cover = len(self.covered)
-
- percent = total and str(int(100.0*cover/total)) or "--"
+
+ percent = total and str(int(100.0 * cover / total)) or "--"
return (total, cover, percent)
def summary(self):
- tmp = options.root_filter.sub('',self.fname)
+ tmp = options.root_filter.sub('', self.fname)
if not self.fname.endswith(tmp):
# Do no truncation if the filter does not start matching at
# the beginning of the string
tmp = self.fname
tmp = tmp.ljust(40)
if len(tmp) > 40:
- tmp=tmp+"\n"+" "*40
+ tmp = tmp + "\n" + " " * 40
(total, cover, percent) = self.coverage()
- uncovered_lines = self.uncovered_str(False)
- if not options.show_branch:
- t = self.uncovered_str(True)
- if len(t):
- uncovered_lines += " [* " + t + "]";
- return ( total, cover,
- tmp + str(total).rjust(8) + str(cover).rjust(8) + \
- percent.rjust(6) + "% " + uncovered_lines )
+ uncovered_lines = self.uncovered_str(False)
+ if not options.show_branch:
+ t = self.uncovered_str(True)
+ if len(t):
+ uncovered_lines += " [* " + t + "]"
+ return (total, cover,
+ tmp + str(total).rjust(8) + str(cover).rjust(8) +
+ percent.rjust(6) + "% " + uncovered_lines)
def resolve_symlinks(orig_path):
return os.path.realpath(orig_path)
# WEH - why doesn't os.path.realpath() suffice here?
#
- drive,tmp = os.path.splitdrive(os.path.abspath(orig_path))
+ drive, tmp = os.path.splitdrive(os.path.abspath(orig_path))
if not drive:
drive = os.path.sep
parts = tmp.split(os.path.sep)
continue
actual_path[-1] = os.readlink(os.path.join(*actual_path))
tmp_drive, tmp_path = os.path.splitdrive(
- resolve_symlinks(os.path.join(*actual_path)) )
+ resolve_symlinks(os.path.join(*actual_path)))
if tmp_drive:
drive = tmp_drive
actual_path = [drive] + tmp_path.split(os.path.sep)
return os.path.join(*actual_path)
-
#
# Class that creates path aliases
#
class PathAliaser(object):
-
def __init__(self):
self.aliases = {}
self.master_targets = set()
def path_startswith(self, path, base):
return path.startswith(base) and (
- len(base) == len(path) or path[len(base)] == os.path.sep )
+ len(base) == len(path) or path[len(base)] == os.path.sep)
def master_path(self, path):
match_found = False
sys.stderr.write(
"(ERROR) violating fundamental assumption while walking "
"directory tree.\n\tPlease report this to the gcovr "
- "developers.\n" )
+ "developers.\n")
return path, None, match_found
def unalias_path(self, path):
def set_preferred(self, master, preferred):
self.preferred_name[master] = preferred
+
aliases = PathAliaser()
+
# This is UGLY. Here's why: UNIX resolves symbolic links by walking the
# entire directory structure. What that means is that relative links
# are always relative to the actual directory inode, and not the
# I have replaced this logic with os.walk(), which works for Python >= 2.6
#
def link_walker(path):
- if sys.version_info >= (2,6):
- for root, dirs, files in os.walk(os.path.abspath(path), followlinks=True):
- yield os.path.abspath(os.path.realpath(root)), dirs, files
+ if sys.version_info >= (2, 6):
+ for root, dirs, files in os.walk(
+ os.path.abspath(path), followlinks=True
+ ):
+ for exc in options.exclude_dirs:
+ for d in dirs:
+ m = exc.search(d)
+ if m is not None:
+ dirs[:] = [d for d in dirs if d is not m.group()]
+ yield (os.path.abspath(os.path.realpath(root)), dirs, files)
else:
targets = [os.path.abspath(path)]
while targets:
target_dir = targets.pop(0)
actual_dir = resolve_symlinks(target_dir)
- #print "target dir: %s (%s)" % (target_dir, actual_dir)
+ # print "target dir: %s (%s)" % (target_dir, actual_dir)
master_name, master_base, visited = aliases.master_path(actual_dir)
if visited:
- #print " ...root already visited as %s" % master_name
+ # print " ...root already visited as %s" % master_name
aliases.add_alias(target_dir, master_name)
continue
if master_name != target_dir:
aliases.set_preferred(master_name, target_dir)
aliases.add_alias(target_dir, master_name)
aliases.add_master_target(master_name)
- #print " ...master name = %s" % master_name
- #print " ...walking %s" % target_dir
+ # print " ...master name = %s" % master_name
+ # print " ...walking %s" % target_dir
for root, dirs, files in os.walk(target_dir, topdown=True):
- #print " ...reading %s" % root
+ # print " ...reading %s" % root
for d in dirs:
tmp = os.path.abspath(os.path.join(root, d))
- #print " ...checking %s" % tmp
+ # print " ...checking %s" % tmp
if os.path.islink(tmp):
- #print " ...buffering link %s" % tmp
+ # print " ...buffering link %s" % tmp
targets.append(tmp)
- yield root, dirs, files
+ yield (root, dirs, files)
def search_file(expr, path):
if path is None or path == ".":
path = os.getcwd()
elif not os.path.exists(path):
- raise IOError("Unknown directory '"+path+"'")
+ raise IOError("Unknown directory '" + path + "'")
for root, dirs, files in link_walker(path):
for name in files:
if pattern.match(name):
- name = os.path.join(root,name)
+ name = os.path.join(root, name)
if os.path.islink(name):
- ans.append( os.path.abspath(os.readlink(name)) )
+ ans.append(os.path.abspath(os.readlink(name)))
else:
- ans.append( os.path.abspath(name) )
+ ans.append(os.path.abspath(name))
return ans
+def commonpath(files):
+ if len(files) == 1:
+ return os.path.join(os.path.relpath(os.path.split(files[0])[0]), '')
+
+ common_path = os.path.realpath(files[0])
+ common_dirs = common_path.split(os.path.sep)
+
+ for f in files[1:]:
+ path = os.path.realpath(f)
+ dirs = path.split(os.path.sep)
+ common = []
+ for a, b in itertools.izip(dirs, common_dirs):
+ if a == b:
+ common.append(a)
+ elif common:
+ common_dirs = common
+ break
+ else:
+ return ''
+
+ return os.path.join(os.path.relpath(os.path.sep.join(common_dirs)), '')
+
+
#
# Get the list of datafiles in the directories specified by the user
#
def get_datafiles(flist, options):
- allfiles=set()
+ allfiles = set()
for dir_ in flist:
if options.gcov_files:
if options.verbose:
- sys.stdout.write( "Scanning directory %s for gcov files...\n"
- % (dir_, ) )
+ sys.stdout.write(
+ "Scanning directory %s for gcov files...\n" % (dir_,)
+ )
files = search_file(".*\.gcov$", dir_)
gcov_files = [file for file in files if file.endswith('gcov')]
if options.verbose:
sys.stdout.write(
"Found %d files (and will process %d)\n" %
- ( len(files), len(gcov_files) ) )
+ (len(files), len(gcov_files))
+ )
allfiles.update(gcov_files)
else:
if options.verbose:
- sys.stdout.write( "Scanning directory %s for gcda/gcno files...\n"
- % (dir_, ) )
+ sys.stdout.write(
+ "Scanning directory %s for gcda/gcno files...\n" % (dir_,)
+ )
files = search_file(".*\.gc(da|no)$", dir_)
# gcno files will *only* produce uncovered results; however,
# that is useful information for the case where a compilation
# unit is never actually exercised by the test code. So, we
# will process gcno files, but ONLY if there is no corresponding
# gcda file.
- gcda_files = [file for file in files if file.endswith('gcda')]
+ gcda_files = [
+ filenm for filenm in files if filenm.endswith('gcda')
+ ]
tmp = set(gcda_files)
- gcno_files = [ file for file in files if
- file.endswith('gcno') and file[:-2]+'da' not in tmp ]
+ gcno_files = [
+ filenm for filenm in files if
+ filenm.endswith('gcno') and filenm[:-2] + 'da' not in tmp
+ ]
if options.verbose:
sys.stdout.write(
"Found %d files (and will process %d)\n" %
- ( len(files), len(gcda_files) + len(gcno_files) ) )
+ (len(files), len(gcda_files) + len(gcno_files)))
allfiles.update(gcda_files)
allfiles.update(gcno_files)
return allfiles
+noncode_mapper = dict.fromkeys(ord(i) for i in '}{)(;')
+
+
+def is_non_code(code):
+ if sys.version_info < (3, 0):
+ code = code.strip().translate(None, '}{)(;')
+ else:
+ code = code.strip().translate(noncode_mapper)
+ return len(code) == 0 or code.startswith("//") or code == 'else'
+
+
#
# Process a single gcov datafile
#
-def process_gcov_data(data_fname, covdata, options):
- INPUT = open(data_fname,"r")
+def process_gcov_data(data_fname, covdata, source_fname, options):
+ INPUT = open(data_fname, "r")
#
# Get the filename
#
line = INPUT.readline()
- segments=line.split(':',3)
- if len(segments) != 4 or not segments[2].lower().strip().endswith('source'):
- raise RuntimeError('Fatal error parsing gcov file, line 1: \n\t"%s"' % line.rstrip())
+ segments = line.split(':', 3)
+ if len(segments) != 4 or not \
+ segments[2].lower().strip().endswith('source'):
+ raise RuntimeError(
+ 'Fatal error parsing gcov file, line 1: \n\t"%s"' % line.rstrip()
+ )
+ #
+ # Find the source file
+ #
currdir = os.getcwd()
- os.chdir(starting_dir)
- if sys.version_info >= (2,6):
- fname = os.path.abspath((segments[-1]).strip())
+ root_dir = os.path.abspath(options.root)
+ if source_fname is None:
+ common_dir = os.path.commonprefix([data_fname, currdir])
+ if sys.version_info >= (2, 6):
+ fname = aliases.unalias_path(os.path.join(common_dir, segments[-1].strip()))
+ else:
+ fname = aliases.unalias_path(os.path.join(common_dir, segments[-1]).strip())
else:
- fname = aliases.unalias_path(os.path.abspath((segments[-1]).strip()))
- os.chdir(currdir)
+ # 1. Try using the path to common prefix with the root_dir as the source directory
+ fname = os.path.join(root_dir, segments[-1].strip())
+ if not os.path.exists(fname):
+ # 2. Try using the path to the gcda file as the source directory
+ fname = os.path.join(os.path.dirname(source_fname), os.path.basename(segments[-1].strip()))
+
+ if options.verbose:
+ print("Finding source file corresponding to a gcov data file")
+ print(' currdir ' + currdir)
+ print(' gcov_fname ' + data_fname)
+ print(' ' + str(segments))
+ print(' source_fname ' + source_fname)
+ print(' root ' + root_dir)
+ # print(' common_dir '+common_dir)
+ # print(' subdir '+subdir)
+ print(' fname ' + fname)
+
if options.verbose:
sys.stdout.write("Parsing coverage data for file %s\n" % fname)
#
# Return if the filename does not match the filter
#
filtered_fname = None
- for i in range(0,len(options.filter)):
+ for i in range(0, len(options.filter)):
if options.filter[i].match(fname):
- filtered_fname = options.root_filter.sub('',fname)
+ filtered_fname = options.root_filter.sub('', fname)
break
if filtered_fname is None:
if options.verbose:
#
# Return if the filename matches the exclude pattern
#
- for i in range(0,len(options.exclude)):
- if (filtered_fname is not None and options.exclude[i].match(filtered_fname)) or \
- options.exclude[i].match(fname) or \
- options.exclude[i].match(os.path.abspath(fname)):
+ for exc in options.exclude:
+ if (filtered_fname is not None and exc.match(filtered_fname)) or \
+ exc.match(fname) or \
+ exc.match(os.path.abspath(fname)):
if options.verbose:
- sys.stdout.write(" Excluding coverage data for file %s\n" % fname)
+ sys.stdout.write(
+ " Excluding coverage data for file %s\n" % fname
+ )
return
#
- # Parse each line, and record the lines
- # that are uncovered
+ # Parse each line, and record the lines that are uncovered
#
excluding = []
noncode = set()
uncovered_exceptional = set()
covered = {}
branches = {}
- #first_record=True
+ # first_record=True
lineno = 0
last_code_line = ""
last_code_lineno = 0
last_code_line_excluded = False
for line in INPUT:
- segments=line.split(":",2)
+ segments = line.split(":", 2)
+ # print "\t","Y", segments
tmp = segments[0].strip()
if len(segments) > 1:
try:
lineno = int(segments[1].strip())
except:
- pass # keep previous line number!
+ pass # keep previous line number!
if exclude_line_flag in line:
excl_line = False
sys.stderr.write(
"(WARNING) %s_EXCL_START found on line %s "
"was terminated by %s_EXCL_STOP on line %s, "
- "when processing %s\n"
- % (_header, _line, header, lineno, fname) )
+ "when processing %s\n"
+ % (_header, _line, header, lineno, fname)
+ )
else:
sys.stderr.write(
"(WARNING) mismatched coverage exclusion flags.\n"
"\t%s_EXCL_STOP found on line %s without "
- "corresponding %s_EXCL_START, when processing %s\n"
- % (header, lineno, header, fname) )
+ "corresponding %s_EXCL_START, when processing %s\n"
+ % (header, lineno, header, fname)
+ )
elif flag == 'LINE':
# We buffer the line exclusion so that it is always
# the last thing added to the exclusion list (and so
is_code_statement = True
code = segments[2].strip()
# remember certain non-executed lines
- if excluding or len(code) == 0 or code == "{" or code == "}" or \
- code.startswith("//") or code == 'else':
- noncode.add( lineno )
+ if excluding or is_non_code(segments[2]):
+ noncode.add(lineno)
elif tmp[0] == '#':
is_code_statement = True
- uncovered.add( lineno )
+ if is_non_code(segments[2]):
+ noncode.add(lineno)
+ else:
+ uncovered.add(lineno)
elif tmp[0] == '=':
is_code_statement = True
- uncovered_exceptional.add( lineno )
+ uncovered_exceptional.add(lineno)
elif tmp[0] in "0123456789":
is_code_statement = True
covered[lineno] = int(segments[0].strip())
elif tmp.startswith('branch'):
exclude_branch = False
- if options.exclude_unreachable_branches and lineno == last_code_lineno:
+ if options.exclude_unreachable_branches and \
+ lineno == last_code_lineno:
if last_code_line_excluded:
exclude_branch = True
exclude_reason = "marked with exclude pattern"
code = re.sub(c_style_comment_pattern, '', code)
code = code.strip()
code_nospace = code.replace(' ', '')
- exclude_branch = len(code) == 0 or code == '{' or code == '}' or code_nospace == '{}'
+ exclude_branch = \
+ code in ['', '{', '}'] or code_nospace == '{}'
exclude_reason = "detected as compiler-generated code"
if exclude_branch:
if options.verbose:
- sys.stdout.write("Excluding unreachable branch on line %d in file %s (%s).\n"
- % (lineno, fname, exclude_reason))
+ sys.stdout.write(
+ "Excluding unreachable branch on line %d "
+ "in file %s (%s).\n"
+ % (lineno, fname, exclude_reason)
+ )
else:
fields = line.split()
try:
pass
elif tmp[0] == 'f':
pass
- #if first_record:
- #first_record=False
- #uncovered.add(prev)
- #if prev in uncovered:
- #tokens=re.split('[ \t]+',tmp)
- #if tokens[3] != "0":
- #uncovered.remove(prev)
- #prev = int(segments[1].strip())
- #first_record=True
+ # if first_record:
+ # first_record=False
+ # uncovered.add(prev)
+ # if prev in uncovered:
+ # tokens=re.split('[ \t]+',tmp)
+ # if tokens[3] != "0":
+ # uncovered.remove(prev)
+ # prev = int(segments[1].strip())
+ # first_record=True
else:
sys.stderr.write(
"(WARNING) Unrecognized GCOV output: '%s'\n"
"\tThis is indicitive of a gcov output parse error.\n"
- "\tPlease report this to the gcovr developers." % tmp )
+ "\tPlease report this to the gcovr developers." % tmp
+ )
# save the code line to use it later with branches
if is_code_statement:
if excluding and not excluding[-1]:
excluding.pop()
- ##print 'uncovered',uncovered
- ##print 'covered',covered
- ##print 'branches',branches
- ##print 'noncode',noncode
+ if options.verbose:
+ print('uncovered ' + str(uncovered))
+ # print('covered ',+covered)
+ # print('branches ',+str(branches))
+ # print('noncode ',+str(noncode))
#
# If the file is already in covdata, then we
# remove lines that are covered here. Otherwise,
# initialize covdata
#
if not fname in covdata:
- covdata[fname] = CoverageData(fname,uncovered,uncovered_exceptional,covered,branches,noncode)
+ covdata[fname] = CoverageData(
+ fname, uncovered, uncovered_exceptional, covered, branches, noncode
+ )
else:
- covdata[fname].update(uncovered,uncovered_exceptional,covered,branches,noncode)
+ covdata[fname].update(
+ uncovered, uncovered_exceptional, covered, branches, noncode
+ )
INPUT.close()
for header, line in excluding:
"corresponding %s_EXCL_STOP flag\n\t in file %s.\n"
% (header, line, header, fname))
+
#
# Process a datafile (generated by running the instrumented application)
# and run gcov with the corresponding arguments
# trial-and-error here)
#
def process_datafile(filename, covdata, options):
+ if options.verbose:
+ print("Processing file: " + filename)
#
# Launch gcov
#
abs_filename = os.path.abspath(filename)
- (dirname,fname) = os.path.split(abs_filename)
- #(name,ext) = os.path.splitext(base)
+ dirname, fname = os.path.split(abs_filename)
potential_wd = []
- errors=[]
+ errors = []
Done = False
if options.objdir:
+ # print "X - objdir"
src_components = abs_filename.split(os.sep)
components = normpath(options.objdir).split(os.sep)
idx = 1
while idx <= len(components):
if idx > len(src_components):
break
- if components[-1*idx] != src_components[-1*idx]:
+ if components[-1 * idx] != src_components[-1 * idx]:
break
idx += 1
if idx > len(components):
- pass # a parent dir; the normal process will find it
- elif components[-1*idx] == '..':
+ pass # a parent dir; the normal process will find it
+ elif components[-1 * idx] == '..':
# NB: os.path.join does not re-add leading '/' characters!?!
- dirs = [ os.path.sep.join(src_components[:len(src_components)-idx]) ]
- while idx <= len(components) and components[-1*idx] == '..':
+ dirs = [
+ os.path.sep.join(src_components[:len(src_components) - idx])
+ ]
+ while idx <= len(components) and components[-1 * idx] == '..':
tmp = []
for d in dirs:
for f in os.listdir(d):
- x = os.path.join(d,f)
+ x = os.path.join(d, f)
if os.path.isdir(x):
tmp.append(x)
dirs = tmp
else:
if components[0] == '':
# absolute path
- tmp = [ options.objdir ]
+ tmp = [options.objdir]
else:
# relative path: check relative to both the cwd and the
# gcda file
- tmp = [ os.path.join(x, options.objdir) for x in
- [os.path.dirname(abs_filename), os.getcwd()] ]
- potential_wd = [ testdir for testdir in tmp
- if os.path.isdir(testdir) ]
+ tmp = [
+ os.path.join(x, options.objdir) for x in
+ [os.path.dirname(abs_filename), os.getcwd()]
+ ]
+ potential_wd = [
+ testdir for testdir in tmp if os.path.isdir(testdir)
+ ]
if len(potential_wd) == 0:
errors.append("ERROR: cannot identify the location where GCC "
"was run using --object-directory=%s\n" %
options.objdir)
- # Revert to the normal
- #sys.exit(1)
# no objdir was specified (or it was a parent dir); walk up the dir tree
if len(potential_wd) == 0:
+ potential_wd.append(root_dir)
+ # print "X - potential_wd", root_dir
wd = os.path.split(abs_filename)[0]
while True:
potential_wd.append(wd)
wd = os.path.split(wd)[0]
if wd == potential_wd[-1]:
+ #
+ # Stop at the root of the file system
+ #
break
- cmd = [ options.gcov_cmd, abs_filename,
- "--branch-counts", "--branch-probabilities", "--preserve-paths",
- '--object-directory', dirname ]
+ #
+ # If the first element of cmd - the executable name - has embedded spaces it probably
+ # includes extra arguments.
+ #
+ cmd = options.gcov_cmd.split(' ') + [
+ abs_filename,
+ "--branch-counts", "--branch-probabilities", "--preserve-paths",
+ '--object-directory', dirname
+ ]
# NB: Currently, we will only parse English output
env = dict(os.environ)
env['LC_ALL'] = 'en_US'
-
+ # print "HERE", potential_wd
while len(potential_wd) > 0 and not Done:
# NB: either len(potential_wd) == 1, or all entires are absolute
# paths, so we don't have to chdir(starting_dir) at every
# iteration.
- os.chdir(potential_wd.pop(0))
-
-
- #if options.objdir:
- # cmd.extend(["--object-directory", Template(options.objdir).substitute(filename=filename, head=dirname, tail=base, root=name, ext=ext)])
+
+ #
+ # Iterate from the end of the potential_wd list, which is the root
+ # directory
+ #
+ dir_ = potential_wd.pop(0)
+ # print "X DIR:", dir_
+ os.chdir(dir_)
if options.verbose:
- sys.stdout.write("Running gcov: '%s' in '%s'\n" % ( ' '.join(cmd), os.getcwd() ))
- (out, err) = subprocess.Popen( cmd, env=env,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE ).communicate()
- out=out.decode('utf-8')
- err=err.decode('utf-8')
+ sys.stdout.write(
+ "Running gcov: '%s' in '%s'\n" % (' '.join(cmd), os.getcwd())
+ )
+ out, err = subprocess.Popen(
+ cmd, env=env,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+ out = out.decode('utf-8')
+ err = err.decode('utf-8')
# find the files that gcov created
- gcov_files = {'active':[], 'filter':[], 'exclude':[]}
+ gcov_files = {'active': [], 'filter': [], 'exclude': []}
for line in out.splitlines():
found = output_re.search(line.strip())
if found is not None:
sys.stdout.write("Filtering gcov file %s\n" % fname)
gcov_files['filter'].append(fname)
continue
- exclude=False
- for i in range(0,len(options.gcov_exclude)):
- if options.gcov_exclude[i].match(options.gcov_filter.sub('',fname)) or \
- options.gcov_exclude[i].match(fname) or \
- options.gcov_exclude[i].match(os.path.abspath(fname)):
- exclude=True
+ exclude = False
+ for exc in options.gcov_exclude:
+ if exc.match(options.gcov_filter.sub('', fname)) or \
+ exc.match(fname) or \
+ exc.match(os.path.abspath(fname)):
+ exclude = True
break
if not exclude:
gcov_files['active'].append(fname)
sys.stdout.write("Excluding gcov file %s\n" % fname)
gcov_files['exclude'].append(fname)
+ # print "HERE", err, "XXX", source_re.search(err)
if source_re.search(err):
+ #
# gcov tossed errors: try the next potential_wd
+ #
errors.append(err)
else:
+ #
# Process *.gcov files
+ #
for fname in gcov_files['active']:
- process_gcov_data(fname, covdata, options)
+ process_gcov_data(fname, covdata, abs_filename, options)
Done = True
if not options.keep:
if options.delete:
if not abs_filename.endswith('gcno'):
os.remove(abs_filename)
-
+
if not Done:
sys.stderr.write(
"(WARNING) GCOV produced the following errors processing %s:\n"
- "\t %s"
+ "\t %s"
"\t(gcovr could not infer a working directory that resolved it.)\n"
- % ( filename, "\t ".join(errors) ) )
+ % (filename, "\t ".join(errors))
+ )
+
#
# Process Already existing gcov files
#
def process_existing_gcov_file(filename, covdata, options):
+ #
+ # Ignore this file if it does not match the gcov filter
+ #
if not options.gcov_filter.match(filename):
if options.verbose:
- sys.stdout.write("Filtering gcov file %s\n" % filename)
+ sys.stdout.write("This gcov file does not match the filter: %s\n" % filename)
return
-
- exclude=False
- for i in range(0,len(options.gcov_exclude)):
- if options.gcov_exclude[i].match(options.gcov_filter.sub('',filename)) or \
- options.gcov_exclude[i].match(filename) or \
- options.gcov_exclude[i].match(os.path.abspath(filename)):
+ #
+ # Ignore this file if it matches one of the exclusion regex's
+ #
+ for exc in options.gcov_exclude:
+ if exc.match(options.gcov_filter.sub('', filename)) or \
+ exc.match(filename) or \
+ exc.match(os.path.abspath(filename)):
if options.verbose:
- sys.stdout.write("Excluding gcov file %s\n" % filename)
+ sys.stdout.write("Excluding gcov file: %s\n" % filename)
return
-
- process_gcov_data(filename, covdata, options)
-
+ #
+ # Process the gcov data file
+ #
+ process_gcov_data(filename, covdata, None, options)
+ #
+ # Remove the file unless the user has indicated that we keep gcov data files
+ #
if not options.keep:
+ #
+ # Only remove files that actually exist.
+ #
if os.path.exists(filename):
- # Only remove files that actually exist.
os.remove(filename)
+
#
# Produce the classic gcovr text report
#
def _num_uncovered(key):
(total, covered, percent) = covdata[key].coverage()
return total - covered
+
def _percent_uncovered(key):
(total, covered, percent) = covdata[key].coverage()
if covered:
- return -1.0*covered/total
+ return -1.0 * covered / total
else:
return total or 1e6
+
def _alpha(key):
return key
if options.output:
- OUTPUT = open(options.output,'w')
+ OUTPUT = open(options.output, 'w')
else:
OUTPUT = sys.stdout
- total_lines=0
- total_covered=0
+ total_lines = 0
+ total_covered = 0
# Header
- OUTPUT.write("-"*78 + '\n')
- OUTPUT.write(" "*27 + "GCC Code Coverage Report\n")
- OUTPUT.write("Directory: "+options.root+"\n")
- OUTPUT.write("-"*78 + '\n')
+ OUTPUT.write("-" * 78 + '\n')
+ OUTPUT.write(" " * 27 + "GCC Code Coverage Report\n")
+ if options.root is not None:
+ OUTPUT.write("Directory: " + options.root + "\n")
+
+ OUTPUT.write("-" * 78 + '\n')
a = options.show_branch and "Branches" or "Lines"
b = options.show_branch and "Taken" or "Exec"
c = "Missing"
- OUTPUT.write("File".ljust(40) + a.rjust(8) + b.rjust(8)+ " Cover " + c + "\n")
- OUTPUT.write("-"*78 + '\n')
+ OUTPUT.write(
+ "File".ljust(40) + a.rjust(8) + b.rjust(8) + " Cover " + c + "\n"
+ )
+ OUTPUT.write("-" * 78 + '\n')
# Data
keys = list(covdata.keys())
- keys.sort(key=options.sort_uncovered and _num_uncovered or \
- options.sort_percent and _percent_uncovered or _alpha)
+ keys.sort(
+ key=options.sort_uncovered and _num_uncovered or
+ options.sort_percent and _percent_uncovered or _alpha
+ )
for key in keys:
(t, n, txt) = covdata[key].summary()
total_lines += t
OUTPUT.write(txt + '\n')
# Footer & summary
- OUTPUT.write("-"*78 + '\n')
- percent = total_lines and str(int(100.0*total_covered/total_lines)) or "--"
- OUTPUT.write("TOTAL".ljust(40) + str(total_lines).rjust(8) + \
- str(total_covered).rjust(8) + str(percent).rjust(6)+"%" + '\n')
- OUTPUT.write("-"*78 + '\n')
+ OUTPUT.write("-" * 78 + '\n')
+ percent = total_lines and str(int(100.0 * total_covered / total_lines)) \
+ or "--"
+ OUTPUT.write(
+ "TOTAL".ljust(40) + str(total_lines).rjust(8) +
+ str(total_covered).rjust(8) + str(percent).rjust(6) + "%" + '\n'
+ )
+ OUTPUT.write("-" * 78 + '\n')
# Close logfile
if options.output:
OUTPUT.close()
+
#
# Prints a small report to the standard output
#
branches_total += t
branches_covered += n
- percent = lines_total and (100.0*lines_covered/lines_total)
- percent_branches = branches_total and (100.0*branches_covered/branches_total)
+ percent = lines_total and (100.0 * lines_covered / lines_total)
+ percent_branches = branches_total and \
+ (100.0 * branches_covered / branches_total)
+
+ lines_out = "lines: %0.1f%% (%s out of %s)\n" % (
+ percent, lines_covered, lines_total
+ )
+ branches_out = "branches: %0.1f%% (%s out of %s)\n" % (
+ percent_branches, branches_covered, branches_total
+ )
- lines_out = "lines: %0.1f%% (%s out of %s)\n" % (percent, lines_covered, lines_total)
- branches_out = "branches: %0.1f%% (%s out of %s)\n" % (percent_branches, branches_covered, branches_total)
-
sys.stdout.write(lines_out)
sys.stdout.write(branches_out)
+
#
# CSS declarations for the HTML output
#
color: navy;
text-decoration: underline;
}
-
+
/*** TD formats ***/
td
{
font-size: 20pt;
font-weight: bold;
}
-
+
/* TD Header Information */
td.headerName
{
padding-right: 10px;
padding-top: 2px;
}
-
+
/* Color of horizontal ruler */
td.hr
{
font-family: sans-serif;
}
- /* Coverage Table */
+ /* Coverage Table */
td.coverTableHead
{
{
text-align: left;
padding-left: 10px;
- padding-right: 20px;
+ padding-right: 20px;
color: black;
background-color: LightBlue;
font-family: monospace;
white-space: nowrap;
font-weight: bold;
}
-
+
/* Link Details */
a.detail:link
{
color: #FFFFFF;
font-size:80%;
}
-
+
.graphcont{
color:#000;
font-weight:700;
.graph .bar span{
position:absolute;
left:1em
- }
+ }
td.coveredLine,
span.coveredLine
background-color: ${uncovered_color}!important;
}
- .linecount
+ .linebranch, .linecount
{
border-right: 1px gray solid;
background-color: lightgray;
}
- .src
+ span.takenBranch
+ {
+ color: ${takenBranch_color}!important;
+ cursor: help;
+ }
+
+ span.notTakenBranch
+ {
+ color: ${notTakenBranch_color}!important;
+ cursor: help;
+ }
+
+ .src
{
padding-left: 12px;
}
- .srcHeader
+ .srcHeader,
+ span.takenBranch,
+ span.notTakenBranch
{
- font-family: monospace;
+ font-family: monospace;
font-weight: bold;
}
- pre
+ pre
{
height : 15px;
margin-top: 0;
<table cellspacing="0" cellpadding="1">
<tr>
<td width="5%" align="right" class="srcHeader">Line</td>
- <td width="10%" align="right" class="srcHeader">Exec</td>
+ <td width="5%" align="right" class="srcHeader">Branch</td>
+ <td width="5%" align="right" class="srcHeader">Exec</td>
<td width="75%" align="left" class="srcHeader src">Source</td>
</tr>
</html>
''')
+
+def calculate_coverage(covered, total):
+ return 0.0 if total == 0 else round(100.0 * covered / total, 1)
+
+
+def coverage_to_color(coverage):
+ if coverage < medium_coverage:
+ return low_color
+ elif coverage < high_coverage:
+ return medium_color
+ else:
+ return high_color
+
+
#
# Produce an HTML report
#
def _num_uncovered(key):
(total, covered, percent) = covdata[key].coverage()
return total - covered
+
def _percent_uncovered(key):
(total, covered, percent) = covdata[key].coverage()
if covered:
- return -1.0*covered/total
+ return -1.0 * covered / total
else:
return total or 1e6
+
def _alpha(key):
return key
data['high_color'] = high_color
data['COVERAGE_MED'] = medium_coverage
data['COVERAGE_HIGH'] = high_coverage
- data['CSS'] = css.substitute(low_color=low_color, medium_color=medium_color, high_color=high_color, covered_color=covered_color, uncovered_color=uncovered_color)
+ data['CSS'] = css.substitute(low_color=low_color, medium_color=medium_color, high_color=high_color,
+ covered_color=covered_color, uncovered_color=uncovered_color,
+ takenBranch_color=takenBranch_color, notTakenBranch_color=notTakenBranch_color
+ )
data['DIRECTORY'] = ''
branchTotal = 0
branchCovered += covered
data['BRANCHES_EXEC'] = str(branchCovered)
data['BRANCHES_TOTAL'] = str(branchTotal)
- coverage = 0.0 if branchTotal == 0 else round(100.0*branchCovered / branchTotal,1)
+ coverage = calculate_coverage(branchCovered, branchTotal)
data['BRANCHES_COVERAGE'] = str(coverage)
- if coverage < medium_coverage:
- data['BRANCHES_COLOR'] = low_color
- elif coverage < high_coverage:
- data['BRANCHES_COLOR'] = medium_color
- else:
- data['BRANCHES_COLOR'] = high_color
+ data['BRANCHES_COLOR'] = coverage_to_color(coverage)
lineTotal = 0
lineCovered = 0
lineCovered += covered
data['LINES_EXEC'] = str(lineCovered)
data['LINES_TOTAL'] = str(lineTotal)
- coverage = 0.0 if lineTotal == 0 else round(100.0*lineCovered / lineTotal,1)
+ coverage = calculate_coverage(lineCovered, lineTotal)
data['LINES_COVERAGE'] = str(coverage)
- if coverage < medium_coverage:
- data['LINES_COLOR'] = low_color
- elif coverage < high_coverage:
- data['LINES_COLOR'] = medium_color
- else:
- data['LINES_COLOR'] = high_color
-
+ data['LINES_COLOR'] = coverage_to_color(coverage)
# Generate the coverage output (on a per-package basis)
- #source_dirs = set()
+ # source_dirs = set()
files = []
+ dirs = []
filtered_fname = ''
keys = list(covdata.keys())
- keys.sort(key=options.sort_uncovered and _num_uncovered or \
- options.sort_percent and _percent_uncovered or _alpha)
+ keys.sort(
+ key=options.sort_uncovered and _num_uncovered or
+ options.sort_percent and _percent_uncovered or _alpha
+ )
for f in keys:
cdata = covdata[f]
- filtered_fname = options.root_filter.sub('',f)
+ filtered_fname = options.root_filter.sub('', f)
files.append(filtered_fname)
+ dirs.append(os.path.dirname(filtered_fname) + os.sep)
cdata._filename = filtered_fname
ttmp = os.path.abspath(options.output).split('.')
- if len(ttmp) > 1:
- cdata._sourcefile = '.'.join( ttmp[:-1] ) + '.' + cdata._filename.replace('/','_') + '.' + ttmp[-1]
- else:
- cdata._sourcefile = ttmp[0] + '.' + cdata._filename.replace('/','_') + '.html'
+ longname = cdata._filename.replace(os.sep, '_')
+ longname_hash = ""
+ while True:
+ if len(ttmp) > 1:
+ cdata._sourcefile = \
+ '.'.join(ttmp[:-1]) + \
+ '.' + longname + longname_hash + \
+ '.' + ttmp[-1]
+ else:
+ cdata._sourcefile = \
+ ttmp[0] + '.' + longname + longname_hash + '.html'
+ # we add a hash at the end and attempt to shorten the
+ # filename if it exceeds common filesystem limitations
+ if len(os.path.basename(cdata._sourcefile)) < 256:
+ break
+ longname_hash = "_" + hex(zlib.crc32(longname) & 0xffffffff)[2:]
+ longname = longname[(len(cdata._sourcefile) - len(longname_hash)):]
+
# Define the common root directory, which may differ from options.root
# when source files share a common prefix.
if len(files) > 1:
- commondir = posixpath.commonprefix(files)
+ commondir = commonpath(files)
if commondir != '':
data['DIRECTORY'] = commondir
else:
if dir_ != '':
data['DIRECTORY'] = dir_ + os.sep
-
for f in keys:
cdata = covdata[f]
class_lines = 0
class_hits = 0
class_branches = 0
class_branch_hits = 0
- for line in cdata.all_lines:
+ for line in sorted(cdata.all_lines):
hits = cdata.covered.get(line, 0)
class_lines += 1
if hits > 0:
for v in branches.values():
if v > 0:
b_hits += 1
- coverage = 100*b_hits/len(branches)
+ coverage = 100 * b_hits / len(branches)
class_branch_hits += b_hits
class_branches += len(branches)
-
- lines_covered = 100.0 if class_lines == 0 else 100.0*class_hits/class_lines
- branches_covered = 100.0 if class_branches == 0 else 100.0*class_branch_hits/class_branches
- data['ROWS'].append( html_row(details, cdata._sourcefile, directory=data['DIRECTORY'], filename=cdata._filename, LinesExec=class_hits, LinesTotal=class_lines, LinesCoverage=lines_covered, BranchesExec=class_branch_hits, BranchesTotal=class_branches, BranchesCoverage=branches_covered ) )
+ lines_covered = 100.0 if class_lines == 0 else \
+ 100.0 * class_hits / class_lines
+ branches_covered = 100.0 if class_branches == 0 else \
+ 100.0 * class_branch_hits / class_branches
+
+ data['ROWS'].append(html_row(
+ details, cdata._sourcefile,
+ directory=data['DIRECTORY'],
+ filename=os.path.relpath(os.path.realpath(cdata._filename), data['DIRECTORY']),
+ LinesExec=class_hits,
+ LinesTotal=class_lines,
+ LinesCoverage=lines_covered,
+ BranchesExec=class_branch_hits,
+ BranchesTotal=class_branches,
+ BranchesCoverage=branches_covered
+ ))
data['ROWS'] = '\n'.join(data['ROWS'])
if data['DIRECTORY'] == '':
htmlString = root_page.substitute(**data)
if options.output is None:
- sys.stdout.write(htmlString+'\n')
+ sys.stdout.write(htmlString + '\n')
else:
OUTPUT = open(options.output, 'w')
- OUTPUT.write(htmlString +'\n')
+ OUTPUT.write(htmlString + '\n')
OUTPUT.close()
# Return, if no details are requested
branchTotal, branchCovered, tmp = cdata.coverage()
data['BRANCHES_EXEC'] = str(branchCovered)
data['BRANCHES_TOTAL'] = str(branchTotal)
- coverage = 0.0 if branchTotal == 0 else round(100.0*branchCovered / branchTotal,1)
+ coverage = calculate_coverage(branchCovered, branchTotal)
data['BRANCHES_COVERAGE'] = str(coverage)
- if coverage < medium_coverage:
- data['BRANCHES_COLOR'] = low_color
- elif coverage < high_coverage:
- data['BRANCHES_COLOR'] = medium_color
- else:
- data['BRANCHES_COLOR'] = high_color
+ data['BRANCHES_COLOR'] = coverage_to_color(coverage)
options.show_branch = False
lineTotal, lineCovered, tmp = cdata.coverage()
data['LINES_EXEC'] = str(lineCovered)
data['LINES_TOTAL'] = str(lineTotal)
- coverage = 0.0 if lineTotal == 0 else round(100.0*lineCovered / lineTotal,1)
+ coverage = calculate_coverage(lineCovered, lineTotal)
data['LINES_COVERAGE'] = str(coverage)
- if coverage < medium_coverage:
- data['LINES_COLOR'] = low_color
- elif coverage < high_coverage:
- data['LINES_COLOR'] = medium_color
- else:
- data['LINES_COLOR'] = high_color
+ data['LINES_COLOR'] = coverage_to_color(coverage)
data['ROWS'] = []
currdir = os.getcwd()
INPUT = open(data['FILENAME'], 'r')
ctr = 1
for line in INPUT:
- data['ROWS'].append( source_row(ctr, line.rstrip(), cdata) )
+ data['ROWS'].append(
+ source_row(ctr, line.rstrip(), cdata)
+ )
ctr += 1
INPUT.close()
os.chdir(currdir)
data['ROWS'] = '\n'.join(data['ROWS'])
-
+
htmlString = source_page.substitute(**data)
OUTPUT = open(cdata._sourcefile, 'w')
- OUTPUT.write(htmlString +'\n')
+ OUTPUT.write(htmlString + '\n')
OUTPUT.close()
-
+
def source_row(lineno, source, cdata):
- rowstr=Template('''
+ rowstr = Template('''
<tr>
<td align="right" class="lineno"><pre>${lineno}</pre></td>
+ <td align="right" class="linebranch">${linebranch}</td>
<td align="right" class="linecount ${covclass}"><pre>${linecount}</pre></td>
<td align="left" class="src ${covclass}"><pre>${source}</pre></td>
</tr>''')
kwargs['lineno'] = str(lineno)
if lineno in cdata.covered:
kwargs['covclass'] = 'coveredLine'
- kwargs['linecount'] = str(cdata.covered.get(lineno,0))
+ kwargs['linebranch'] = ''
+ # If line has branches them show them with ticks or crosses
+ if lineno in cdata.branches.keys():
+ branches = cdata.branches.get(lineno)
+ branchcounter = 0
+ for branch in branches:
+ if branches[branch] > 0:
+ kwargs['linebranch'] += '<span class="takenBranch" title="Branch ' + str(branch) + ' taken ' + str(
+ branches[branch]) + ' times">✓</span>'
+ else:
+ kwargs['linebranch'] += '<span class="notTakenBranch" title="Branch ' + str(
+ branch) + ' not taken">✗</span>'
+ branchcounter += 1
+ # Wrap at 4 branches to avoid too wide column
+ if (branchcounter > 0) and ((branchcounter % 4) == 0):
+ kwargs['linebranch'] += '<br/>'
+ kwargs['linecount'] = str(cdata.covered.get(lineno, 0))
elif lineno in cdata.uncovered:
kwargs['covclass'] = 'uncoveredLine'
+ kwargs['linebranch'] = ''
kwargs['linecount'] = ''
else:
kwargs['covclass'] = ''
+ kwargs['linebranch'] = ''
kwargs['linecount'] = ''
kwargs['source'] = html.escape(source)
return rowstr.substitute(**kwargs)
+
#
# Generate the table row for a single file
#
nrows = 0
+
+
def html_row(details, sourcefile, **kwargs):
if options.relative_anchors:
sourcefile = os.path.basename(sourcefile)
- rowstr=Template('''
+ rowstr = Template('''
<tr>
<td class="coverFile" ${altstyle}>${filename}</td>
<td class="coverBar" align="center" ${altstyle}>
else:
kwargs['altstyle'] = ''
if details:
- kwargs['filename'] = '<a href="%s">%s</a>' % (sourcefile, kwargs['filename'][len(kwargs['directory']):])
- else:
- kwargs['filename'] = kwargs['filename'][len(kwargs['directory']):]
- kwargs['LinesCoverage'] = round(kwargs['LinesCoverage'],1)
+ kwargs['filename'] = '<a href="%s">%s</a>' % (
+ sourcefile, kwargs['filename']
+ )
+ kwargs['LinesCoverage'] = round(kwargs['LinesCoverage'], 1)
# Disable the border if the bar is too short to see the color
if kwargs['LinesCoverage'] < 1e-7:
kwargs['BarBorder'] = "border:white; "
kwargs['LinesColor'] = high_color
kwargs['LinesBar'] = 'green'
- kwargs['BranchesCoverage'] = round(kwargs['BranchesCoverage'],1)
+ kwargs['BranchesCoverage'] = round(kwargs['BranchesCoverage'], 1)
if kwargs['BranchesCoverage'] < medium_coverage:
kwargs['BranchesColor'] = low_color
kwargs['BranchesBar'] = 'red'
kwargs['BranchesBar'] = 'green'
return rowstr.substitute(**kwargs)
-
+
#
# Produce an XML report in the Cobertura format
(total, covered, percent) = covdata[key].coverage()
lineTotal += total
lineCovered += covered
-
+
impl = xml.dom.minidom.getDOMImplementation()
docType = impl.createDocumentType(
"coverage", None,
- "http://cobertura.sourceforge.net/xml/coverage-03.dtd" )
+ "http://cobertura.sourceforge.net/xml/coverage-03.dtd"
+ )
doc = impl.createDocument(None, "coverage", docType)
root = doc.documentElement
- root.setAttribute( "line-rate", lineTotal == 0 and '0.0' or
- str(float(lineCovered) / lineTotal) )
- root.setAttribute( "branch-rate", branchTotal == 0 and '0.0' or
- str(float(branchCovered) / branchTotal) )
- root.setAttribute( "timestamp", str(int(time.time())) )
- root.setAttribute( "version", "gcovr %s" % (version_str(),) )
+ root.setAttribute(
+ "line-rate", lineTotal == 0 and '0.0' or
+ str(float(lineCovered) / lineTotal)
+ )
+ root.setAttribute(
+ "branch-rate", branchTotal == 0 and '0.0' or
+ str(float(branchCovered) / branchTotal)
+ )
+ root.setAttribute(
+ "timestamp", str(int(time.time()))
+ )
+ root.setAttribute(
+ "version", "gcovr %s" % (version_str(),)
+ )
# Generate the <sources> element: this is either the root directory
# (specified by --root), or the CWD.
keys.sort()
for f in keys:
data = covdata[f]
- dir = options.root_filter.sub('',f)
- if f.endswith(dir):
- src_path = f[:-1*len(dir)]
+ directory = options.root_filter.sub('', f)
+ if f.endswith(directory):
+ src_path = f[:-1 * len(directory)]
if len(src_path) > 0:
- while dir.startswith(os.path.sep):
+ while directory.startswith(os.path.sep):
src_path += os.path.sep
- dir = dir[len(os.path.sep):]
+ directory = directory[len(os.path.sep):]
source_dirs.add(src_path)
else:
# Do no truncation if the filter does not start matching at
# the beginning of the string
- dir = f
- (dir, fname) = os.path.split(dir)
-
+ directory = f
+ directory, fname = os.path.split(directory)
+
package = packages.setdefault(
- dir, [ doc.createElement("package"), {},
- 0, 0, 0, 0 ] )
+ directory, [doc.createElement("package"), {}, 0, 0, 0, 0]
+ )
c = doc.createElement("class")
# The Cobertura DTD requires a methods section, which isn't
# trivial to get from gcov (so we will leave it blank)
class_hits = 0
class_branches = 0
class_branch_hits = 0
- for line in data.all_lines:
+ for line in sorted(data.all_lines):
hits = data.covered.get(line, 0)
class_lines += 1
if hits > 0:
for v in branches.values():
if v > 0:
b_hits += 1
- coverage = 100*b_hits/len(branches)
+ coverage = 100 * b_hits / len(branches)
l.setAttribute("branch", "true")
- l.setAttribute( "condition-coverage",
- "%i%% (%i/%i)" %
- (coverage, b_hits, len(branches)) )
+ l.setAttribute(
+ "condition-coverage",
+ "%i%% (%i/%i)" % (coverage, b_hits, len(branches))
+ )
cond = doc.createElement('condition')
cond.setAttribute("number", "0")
cond.setAttribute("type", "jump")
- cond.setAttribute("coverage", "%i%%" % ( coverage ) )
+ cond.setAttribute("coverage", "%i%%" % (coverage))
class_branch_hits += b_hits
class_branches += float(len(branches))
conditions = doc.createElement("conditions")
conditions.appendChild(cond)
l.appendChild(conditions)
-
+
lines.appendChild(l)
className = fname.replace('.', '_')
c.setAttribute("name", className)
- c.setAttribute("filename", os.path.join(dir, fname))
- c.setAttribute("line-rate", str(class_hits / (1.0*class_lines or 1.0)))
- c.setAttribute( "branch-rate",
- str(class_branch_hits / (1.0*class_branches or 1.0)) )
+ c.setAttribute("filename", os.path.join(directory, fname))
+ c.setAttribute(
+ "line-rate",
+ str(class_hits / (1.0 * class_lines or 1.0))
+ )
+ c.setAttribute(
+ "branch-rate",
+ str(class_branch_hits / (1.0 * class_branches or 1.0))
+ )
c.setAttribute("complexity", "0.0")
package[1][className] = c
keys.sort()
for packageName in keys:
packageData = packages[packageName]
- package = packageData[0];
+ package = packageData[0]
packageXml.appendChild(package)
classes = doc.createElement("classes")
package.appendChild(classes)
for className in classNames:
classes.appendChild(packageData[1][className])
package.setAttribute("name", packageName.replace(os.sep, '.'))
- package.setAttribute("line-rate", str(packageData[2]/(1.0*packageData[3] or 1.0)))
- package.setAttribute( "branch-rate", str(packageData[4] / (1.0*packageData[5] or 1.0) ))
+ package.setAttribute(
+ "line-rate", str(packageData[2] / (1.0 * packageData[3] or 1.0))
+ )
+ package.setAttribute(
+ "branch-rate", str(packageData[4] / (1.0 * packageData[5] or 1.0))
+ )
package.setAttribute("complexity", "0.0")
-
# Populate the <sources> element: this is either the root directory
# (specified by --root), or relative directories based
# on the filter, or the CWD
reldir = d[len(cwd):].lstrip(os.path.sep)
elif cwd.startswith(d):
i = 1
- while normpath(d) != normpath(os.path.join(*tuple([cwd]+['..']*i))):
+ while normpath(d) != \
+ normpath(os.path.join(*tuple([cwd] + ['..'] * i))):
i += 1
- reldir = os.path.join(*tuple(['..']*i))
+ reldir = os.path.join(*tuple(['..'] * i))
else:
reldir = d
source.appendChild(doc.createTextNode(reldir.strip()))
import textwrap
lines = doc.toprettyxml(" ").split('\n')
for i in xrange(len(lines)):
- n=0
+ n = 0
while n < len(lines[i]) and lines[i][n] == " ":
n += 1
- lines[i] = "\n".join(textwrap.wrap(lines[i], 78, break_long_words=False, break_on_hyphens=False, subsequent_indent=" "+ n*" "))
+ lines[i] = "\n".join(textwrap.wrap(
+ lines[i], 78,
+ break_long_words=False,
+ break_on_hyphens=False,
+ subsequent_indent=" " + n * " "
+ ))
xmlString = "\n".join(lines)
- #print textwrap.wrap(doc.toprettyxml(" "), 80)
+ # print textwrap.wrap(doc.toprettyxml(" "), 80)
else:
xmlString = doc.toprettyxml(indent="")
if options.output is None:
- sys.stdout.write(xmlString+'\n')
+ sys.stdout.write(xmlString + '\n')
else:
OUTPUT = open(options.output, 'w')
- OUTPUT.write(xmlString +'\n')
+ OUTPUT.write(xmlString + '\n')
OUTPUT.close()
# Create option parser
#
parser = OptionParser()
-parser.add_option("--version",
- help="Print the version number, then exit",
- action="store_true",
- dest="version",
- default=False)
-parser.add_option("-v","--verbose",
- help="Print progress messages",
- action="store_true",
- dest="verbose",
- default=False)
-parser.add_option('--object-directory',
- help="Specify the directory that contains the gcov data files. gcovr must be able to identify the path between the *.gcda files and the directory where gcc was originally run. Normally, gcovr can guess correctly. This option overrides gcovr's normal path detection and can specify either the path from gcc to the gcda file (i.e. what was passed to gcc's '-o' option), or the path from the gcda file to gcc's original working directory.",
- action="store",
- dest="objdir",
- default=None)
-parser.add_option("-o","--output",
- help="Print output to this filename",
- action="store",
- dest="output",
- default=None)
-parser.add_option("-k","--keep",
- help="Keep the temporary *.gcov files generated by gcov. By default, these are deleted.",
- action="store_true",
- dest="keep",
- default=False)
-parser.add_option("-d","--delete",
- help="Delete the coverage files after they are processed. These are generated by the users's program, and by default gcovr does not remove these files.",
- action="store_true",
- dest="delete",
- default=False)
-parser.add_option("-f","--filter",
- help="Keep only the data files that match this regular expression",
- action="append",
- dest="filter",
- default=[])
-parser.add_option("-e","--exclude",
- help="Exclude data files that match this regular expression",
- action="append",
- dest="exclude",
- default=[])
-parser.add_option("--gcov-filter",
- help="Keep only gcov data files that match this regular expression",
- action="store",
- dest="gcov_filter",
- default=None)
-parser.add_option("--gcov-exclude",
- help="Exclude gcov data files that match this regular expression",
- action="append",
- dest="gcov_exclude",
- default=[])
-parser.add_option("-r","--root",
- help="Defines the root directory for source files. This is also used to filter the files, and to standardize the output.",
- action="store",
- dest="root",
- default=None)
-parser.add_option("-x","--xml",
- help="Generate XML instead of the normal tabular output.",
- action="store_true",
- dest="xml",
- default=False)
-parser.add_option("--xml-pretty",
- help="Generate pretty XML instead of the normal dense format.",
- action="store_true",
- dest="prettyxml",
- default=False)
-parser.add_option("--html",
- help="Generate HTML instead of the normal tabular output.",
- action="store_true",
- dest="html",
- default=False)
-parser.add_option("--html-details",
- help="Generate HTML output for source file coverage.",
- action="store_true",
- dest="html_details",
- default=False)
-parser.add_option("--html-absolute-paths",
- help="Set the paths in the HTML report to be absolute instead of relative",
- action="store_false",
- dest="relative_anchors",
- default=True)
-parser.add_option("-b","--branches",
- help="Tabulate the branch coverage instead of the line coverage.",
- action="store_true",
- dest="show_branch",
- default=None)
-parser.add_option("-u","--sort-uncovered",
- help="Sort entries by increasing number of uncovered lines.",
- action="store_true",
- dest="sort_uncovered",
- default=None)
-parser.add_option("-p","--sort-percentage",
- help="Sort entries by decreasing percentage of covered lines.",
- action="store_true",
- dest="sort_percent",
- default=None)
-parser.add_option("--gcov-executable",
- help="Defines the name/path to the gcov executable [defaults to the "
- "GCOV environment variable, if present; else 'gcov'].",
- action="store",
- dest="gcov_cmd",
- default=os.environ.get('GCOV', 'gcov') )
-parser.add_option("--exclude-unreachable-branches",
- help="Exclude from coverage branches which are marked to be excluded by LCOV/GCOV markers "
- "or are determined to be from lines containing only compiler-generated \"dead\" code.",
- action="store_true",
- dest="exclude_unreachable_branches",
- default=False)
-parser.add_option("-g", "--use-gcov-files",
- help="Use preprocessed gcov files for analysis.",
- action="store_true",
- dest="gcov_files",
- default=False)
-parser.add_option("-s", "--print-summary",
- help="Prints a small report to stdout with line & branch percentage coverage",
- action="store_true",
- dest="print_summary",
- default=False)
-parser.usage="gcovr [options]"
-parser.description="A utility to run gcov and generate a simple report that summarizes the coverage"
+parser.add_option(
+ "--version",
+ help="Print the version number, then exit",
+ action="store_true",
+ dest="version",
+ default=False
+)
+parser.add_option(
+ "-v", "--verbose",
+ help="Print progress messages",
+ action="store_true",
+ dest="verbose",
+ default=False
+)
+parser.add_option(
+ '--object-directory',
+ help="Specify the directory that contains the gcov data files. gcovr "
+ "must be able to identify the path between the *.gcda files and the "
+ "directory where gcc was originally run. Normally, gcovr can guess "
+ "correctly. This option overrides gcovr's normal path detection and "
+ "can specify either the path from gcc to the gcda file (i.e. what "
+ "was passed to gcc's '-o' option), or the path from the gcda file to "
+ "gcc's original working directory.",
+ action="store",
+ dest="objdir",
+ default=None
+)
+parser.add_option(
+ "-o", "--output",
+ help="Print output to this filename",
+ action="store",
+ dest="output",
+ default=None
+)
+parser.add_option(
+ "-k", "--keep",
+ help="Keep the temporary *.gcov files generated by gcov. "
+ "By default, these are deleted.",
+ action="store_true",
+ dest="keep",
+ default=False
+)
+parser.add_option(
+ "-d", "--delete",
+ help="Delete the coverage files after they are processed. "
+ "These are generated by the users's program, and by default gcovr "
+ "does not remove these files.",
+ action="store_true",
+ dest="delete",
+ default=False
+)
+parser.add_option(
+ "-f", "--filter",
+ help="Keep only the data files that match this regular expression",
+ action="append",
+ dest="filter",
+ default=[]
+)
+parser.add_option(
+ "-e", "--exclude",
+ help="Exclude data files that match this regular expression",
+ action="append",
+ dest="exclude",
+ default=[]
+)
+parser.add_option(
+ "--gcov-filter",
+ help="Keep only gcov data files that match this regular expression",
+ action="store",
+ dest="gcov_filter",
+ default=None
+)
+parser.add_option(
+ "--gcov-exclude",
+ help="Exclude gcov data files that match this regular expression",
+ action="append",
+ dest="gcov_exclude",
+ default=[]
+)
+parser.add_option(
+ "-r", "--root",
+ help="Defines the root directory for source files. "
+ "This is also used to filter the files, and to standardize "
+ "the output.",
+ action="store",
+ dest="root",
+ default=None
+)
+parser.add_option(
+ "-x", "--xml",
+ help="Generate XML instead of the normal tabular output.",
+ action="store_true",
+ dest="xml",
+ default=False
+)
+parser.add_option(
+ "--xml-pretty",
+ help="Generate pretty XML instead of the normal dense format.",
+ action="store_true",
+ dest="prettyxml",
+ default=False
+)
+parser.add_option(
+ "--html",
+ help="Generate HTML instead of the normal tabular output.",
+ action="store_true",
+ dest="html",
+ default=False
+)
+parser.add_option(
+ "--html-details",
+ help="Generate HTML output for source file coverage.",
+ action="store_true",
+ dest="html_details",
+ default=False
+)
+parser.add_option(
+ "--html-absolute-paths",
+ help="Set the paths in the HTML report to be absolute instead of relative",
+ action="store_false",
+ dest="relative_anchors",
+ default=True
+)
+parser.add_option(
+ "-b", "--branches",
+ help="Tabulate the branch coverage instead of the line coverage.",
+ action="store_true",
+ dest="show_branch",
+ default=None
+)
+parser.add_option(
+ "-u", "--sort-uncovered",
+ help="Sort entries by increasing number of uncovered lines.",
+ action="store_true",
+ dest="sort_uncovered",
+ default=None
+)
+parser.add_option(
+ "-p", "--sort-percentage",
+ help="Sort entries by decreasing percentage of covered lines.",
+ action="store_true",
+ dest="sort_percent",
+ default=None
+)
+parser.add_option(
+ "--gcov-executable",
+ help="Defines the name/path to the gcov executable [defaults to the "
+ "GCOV environment variable, if present; else 'gcov'].",
+ action="store",
+ dest="gcov_cmd",
+ default=os.environ.get('GCOV', 'gcov')
+)
+parser.add_option(
+ "--exclude-unreachable-branches",
+ help="Exclude from coverage branches which are marked to be excluded by "
+ "LCOV/GCOV markers or are determined to be from lines containing "
+ "only compiler-generated \"dead\" code.",
+ action="store_true",
+ dest="exclude_unreachable_branches",
+ default=False
+)
+parser.add_option(
+ "--exclude-directories",
+ help="Exclude directories from search path that match this regular expression",
+ action="append",
+ dest="exclude_dirs",
+ default=[]
+)
+parser.add_option(
+ "-g", "--use-gcov-files",
+ help="Use preprocessed gcov files for analysis.",
+ action="store_true",
+ dest="gcov_files",
+ default=False
+)
+parser.add_option(
+ "-s", "--print-summary",
+ help="Prints a small report to stdout with line & branch "
+ "percentage coverage",
+ action="store_true",
+ dest="print_summary",
+ default=False
+)
+parser.usage = "gcovr [options]"
+parser.description = \
+ "A utility to run gcov and generate a simple report that summarizes " \
+ "the coverage"
#
# Process options
#
-(options, args) = parser.parse_args(args=sys.argv)
+options, args = parser.parse_args(args=sys.argv)
+
if options.version:
sys.stdout.write(
"gcovr %s\n"
"Copyright (2013) Sandia Corporation. Under the terms of Contract\n"
"DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government\n"
"retains certain rights in this software.\n"
- % (version_str(),) )
+ % (version_str(),)
+ )
sys.exit(0)
-if options.objdir:
- tmp = options.objdir.replace('/',os.sep).replace('\\',os.sep)
- while os.sep+os.sep in tmp:
- tmp = tmp.replace(os.sep+os.sep, os.sep)
+
+if options.objdir is not None:
+ if not options.objdir:
+ sys.stderr.write(
+ "(ERROR) empty --object-directory option.\n"
+ "\tThis option specifies the path to the object file "
+ "directory of your project.\n"
+ "\tThis option cannot be an empty string.\n"
+ )
+ sys.exit(1)
+ tmp = options.objdir.replace('/', os.sep).replace('\\', os.sep)
+ while os.sep + os.sep in tmp:
+ tmp = tmp.replace(os.sep + os.sep, os.sep)
if normpath(options.objdir) != tmp:
sys.stderr.write(
"(WARNING) relative referencing in --object-directory.\n"
"(ERROR) Bad --object-directory option.\n"
"\tThe specified directory does not exist.\n")
sys.exit(1)
-#
-# Setup filters
-#
-for i in range(0,len(options.exclude)):
- options.exclude[i] = re.compile(options.exclude[i])
+
+if options.output is not None:
+ options.output = os.path.abspath(options.output)
if options.root is not None:
if not options.root:
sys.stderr.write(
"(ERROR) empty --root option.\n"
- "\tRoot specifies the path to the root directory of your project.\n"
- "\tThis option cannot be an empty string.\n")
+ "\tRoot specifies the path to the root "
+ "directory of your project.\n"
+ "\tThis option cannot be an empty string.\n"
+ )
sys.exit(1)
root_dir = os.path.abspath(options.root)
- options.root_filter = re.compile(re.escape(root_dir+os.sep))
else:
- options.root_filter = re.compile('')
root_dir = starting_dir
-for i in range(0,len(options.filter)):
+#
+# Setup filters
+#
+options.root_filter = re.compile(re.escape(root_dir + os.sep))
+for i in range(0, len(options.exclude)):
+ options.exclude[i] = re.compile(options.exclude[i])
+
+if options.exclude_dirs is not None:
+ for i in range(0, len(options.exclude_dirs)):
+ options.exclude_dirs[i] = re.compile(options.exclude_dirs[i])
+
+for i in range(0, len(options.filter)):
options.filter[i] = re.compile(options.filter[i])
if len(options.filter) == 0:
options.filter.append(options.root_filter)
-for i in range(0,len(options.gcov_exclude)):
+for i in range(0, len(options.gcov_exclude)):
options.gcov_exclude[i] = re.compile(options.gcov_exclude[i])
if options.gcov_filter is not None:
options.gcov_filter = re.compile(options.gcov_filter)
#
if len(args) == 1:
if options.root is None:
- datafiles = get_datafiles(["."], options)
+ search_paths = ["."]
else:
- datafiles = get_datafiles([options.root], options)
+ search_paths = [options.root]
+
+ if options.objdir is not None:
+ search_paths.append(options.objdir)
+
+ datafiles = get_datafiles(search_paths, options)
else:
datafiles = get_datafiles(args[1:], options)
#
covdata = {}
for file_ in datafiles:
if options.gcov_files:
- process_existing_gcov_file(file_,covdata,options)
+ process_existing_gcov_file(file_, covdata, options)
else:
- process_datafile(file_,covdata,options)
+ process_datafile(file_, covdata, options)
if options.verbose:
- sys.stdout.write("Gathered coveraged data for "+str(len(covdata))+" files\n")
+ sys.stdout.write(
+ "Gathered coveraged data for " + str(len(covdata)) + " files\n"
+ )
#
# Print report
#
print_text_report(covdata)
if options.print_summary:
- print_summary(covdata)
+ print_summary(covdata)
\ No newline at end of file