[SECIOTSRK-232] Update GCOVR script. Add custom targets "runtests", "coverage" to...
authorOleksandr Samoylov <o.samoylov@samsung.com>
Thu, 27 Jul 2017 13:56:51 +0000 (16:56 +0300)
committerOleksandr Samoylov <o.samoylov@samsung.com>
Thu, 27 Jul 2017 13:56:51 +0000 (16:56 +0300)
device_core/CMakeLists.txt
device_core/packaging/ioswsec.spec
device_core/scripts/build_and_coverage_ubuntu.sh
device_core/scripts/gcovr

index 4ceef01..f480546 100644 (file)
@@ -124,3 +124,29 @@ add_subdirectory(iotivity_lib)
 add_subdirectory(utest)
 add_subdirectory(nmdaemon)
 add_subdirectory(smack_test)
+
+add_custom_target(runtests
+                  COMMAND utest 52.71.167.178
+                  COMMENT "Run unit tests.\n")
+
+find_program(LCOV NAMES lcov)
+message(STATUS "LCOV: " ${LCOV})
+string(TIMESTAMP TIME "%Y-%m-%d_%H:%M:%S")
+SET(COV_FOLDER "coverage/${TIME}")
+if(${LCOV} STREQUAL "LCOV-NOTFOUND")
+    message(STATUS "LCOV not found, using GCOVR")
+    add_custom_target(coverage
+                  COMMAND mkdir -p ${COV_FOLDER}
+                  COMMAND ../scripts/gcovr -r ../ --html --html-details -o ${COV_FOLDER}/report.html -eutest.* -eagent_lib/rmi.* -s
+                  COMMAND echo "Coverage calculated."
+                  COMMENT "Coverage stat with GCOVR.\n")
+else()
+    message(STATUS "LCOV was found.")
+    add_custom_target(coverage
+                  COMMAND mkdir -p ${COV_FOLDER}
+                  COMMAND lcov -t "result" -o ${COV_FOLDER}/utest.info -c -d .. --rc lcov_branch_coverage=1 -q
+                  COMMAND lcov --remove ${COV_FOLDER}/utest.info '*utest/*' '*agent_lib/rmi*' '/usr/include/*' '*iotivity/*' --rc lcov_branch_coverage=1 -o ${COV_FOLDER}/utest_filtered.info -q
+                  COMMAND genhtml -o ${COV_FOLDER} ${COV_FOLDER}/utest_filtered.info
+                  COMMAND echo "Coverage calculated."
+                  COMMENT "Coverage stat with LCOV.\n")
+endif()
\ No newline at end of file
index d47db72..b922ec1 100644 (file)
@@ -75,10 +75,8 @@ cd build-gbs
 %clean
 %if ("%{build_type}" == "CCOV")
        cd build-gbs/
-       ./utest/utest 52.71.167.178 --gtest_filter=*.* || true
-       coverage_folder=coverage/`date +"%Y-%m-%d-%s"`
-       mkdir -p $coverage_folder
-       ../scripts/gcovr -r ../ --html --html-details -o $coverage_folder/report.html
+       make runtests
+    make coverage
 %endif
 rm -rf %{buildroot}
 
index d3afdd1..d3f63fd 100755 (executable)
@@ -5,7 +5,6 @@
 SCRIPT_PATH=$(readlink -m ${0})
 SCRIPT_DIR=${SCRIPT_PATH%/*}
 ROOT_DIR=${SCRIPT_DIR}/..
-DEST=${ROOT_DIR}/build
 
 #remove old build
 rm -r ${ROOT_DIR}/build
@@ -15,24 +14,7 @@ rm -r ${ROOT_DIR}/install
 cd ${ROOT_DIR}
 cmake -H./ -B./build -DFLAVOR=UBUNTU -DDEBUG=1 -DCMAKE_BUILD_TYPE=CCOV
 cd build
-make install -j4 VERBOSE=1
-
-#remove old coverage results
-rm -r ${DEST}/coverage ${DEST}/utest.info ${DEST}/utest_filtered.info
-
-#run tests
-cd ${DEST}/utest
-./utest
-cd ${SCRIPT_DIR}
-
-#capture
-lcov -t "result" -o ${DEST}/utest.info -c -d ${ROOT_DIR} --rc lcov_branch_coverage=1 -q
 
-#ignore external libraries
-lcov --remove ${DEST}/utest.info '*utest/*' '*iotivity_lib/inc/*' '*ctrl_app_lib/inc/*' '*agent_lib/inc/*' '*nmdaemon/*.h' '*secserver/*.h' '*agent_lib/rmi*' '*.h' '/usr/include/*' '*iotivity/*' --rc lcov_branch_coverage=1 -o ${DEST}/utest_filtered.info -q
-
-
-#make fancy html
-genhtml -o ${DEST}/coverage ${DEST}/utest_filtered.info #--rc lcov_branch_coverage=1 # for branch
-
-echo "Results in ${DEST}/coverage"
+make install -j4 VERBOSE=1
+make runtests
+make coverage
index 6ec9e28..bbbe692 100755 (executable)
@@ -1,4 +1,5 @@
-#! /usr/bin/python
+#!/usr/bin/python
+# -*- coding:utf-8 -*-
 #
 # A report generator for gcov 3.4
 #
@@ -34,7 +35,6 @@ try:
 except:
     import cgi as html
 import copy
-import glob
 import os
 import re
 import subprocess
@@ -43,11 +43,18 @@ import time
 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"
@@ -55,12 +62,14 @@ medium_color = "#FFFF55"
 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()
 
@@ -70,6 +79,7 @@ exclude_line_pattern = re.compile('([GL]COVR?)_EXCL_(LINE|START|STOP)')
 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)
@@ -77,27 +87,31 @@ def version_str():
         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())
@@ -105,7 +119,7 @@ class CoverageData(object):
         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, {})
@@ -115,17 +129,18 @@ class CoverageData(object):
 
     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:
@@ -133,36 +148,46 @@ class CoverageData(object):
         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():
@@ -172,29 +197,29 @@ class CoverageData(object):
         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):
@@ -204,7 +229,7 @@ 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)
@@ -215,19 +240,17 @@ def resolve_symlinks(orig_path):
             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()
@@ -235,7 +258,7 @@ class PathAliaser(object):
 
     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
@@ -252,7 +275,7 @@ class PathAliaser(object):
                 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):
@@ -274,8 +297,10 @@ class PathAliaser(object):
     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
@@ -312,35 +337,42 @@ aliases = PathAliaser()
 # 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):
@@ -353,86 +385,152 @@ 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:
@@ -441,16 +539,17 @@ def process_gcov_data(data_fname, covdata, options):
     #
     # 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()
@@ -458,19 +557,20 @@ def process_gcov_data(data_fname, covdata, options):
     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
@@ -484,14 +584,16 @@ def process_gcov_data(data_fname, covdata, options):
                             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
@@ -508,21 +610,24 @@ def process_gcov_data(data_fname, covdata, options):
             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"
@@ -532,13 +637,17 @@ def process_gcov_data(data_fname, covdata, options):
                     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:
@@ -552,20 +661,21 @@ def process_gcov_data(data_fname, covdata, options):
             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:
@@ -579,19 +689,24 @@ def process_gcov_data(data_fname, covdata, options):
         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:
@@ -600,6 +715,7 @@ def process_gcov_data(data_fname, covdata, options):
                          "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
@@ -626,37 +742,41 @@ def process_gcov_data(data_fname, covdata, options):
 # 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
@@ -665,59 +785,77 @@ def process_datafile(filename, covdata, options):
         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:
@@ -727,12 +865,12 @@ def process_datafile(filename, covdata, options):
                         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)
@@ -740,13 +878,18 @@ def process_datafile(filename, covdata, options):
                     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:
@@ -760,39 +903,52 @@ def process_datafile(filename, covdata, options):
     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
 #
@@ -800,37 +956,45 @@ def print_text_report(covdata):
     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
@@ -838,16 +1002,20 @@ def print_text_report(covdata):
         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
 #
@@ -870,15 +1038,21 @@ def print_summary(covdata):
         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
 #
@@ -905,7 +1079,7 @@ css = Template('''
       color: navy;
       text-decoration: underline;
     }
-    
+
     /*** TD formats ***/
     td
     {
@@ -918,7 +1092,7 @@ css = Template('''
       font-size: 20pt;
       font-weight: bold;
     }
-    
+
     /* TD Header Information */
     td.headerName
     {
@@ -956,7 +1130,7 @@ css = Template('''
       padding-right: 10px;
       padding-top: 2px;
     }
-    
+
     /* Color of horizontal ruler */
     td.hr
     {
@@ -971,7 +1145,7 @@ css = Template('''
       font-family: sans-serif;
     }
 
-    /* Coverage Table */    
+    /* Coverage Table */
 
     td.coverTableHead
     {
@@ -988,7 +1162,7 @@ css = Template('''
     {
       text-align: left;
       padding-left: 10px;
-      padding-right: 20px; 
+      padding-right: 20px;
       color: black;
       background-color: LightBlue;
       font-family: monospace;
@@ -1015,7 +1189,7 @@ css = Template('''
       white-space: nowrap;
       font-weight: bold;
     }
-    
+
     /* Link Details */
     a.detail:link
     {
@@ -1032,7 +1206,7 @@ css = Template('''
       color: #FFFFFF;
       font-size:80%;
     }
-    
+
     .graphcont{
         color:#000;
         font-weight:700;
@@ -1062,7 +1236,7 @@ css = Template('''
     .graph .bar span{
         position:absolute;
         left:1em
-    } 
+    }
 
     td.coveredLine,
     span.coveredLine
@@ -1076,24 +1250,38 @@ css = Template('''
         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;
@@ -1269,7 +1457,8 @@ source_page = Template('''
   <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>
 
@@ -1289,6 +1478,20 @@ source_page = Template('''
 </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
 #
@@ -1296,12 +1499,14 @@ def print_html_report(covdata, details):
     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
 
@@ -1318,7 +1523,10 @@ def print_html_report(covdata, details):
     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
@@ -1330,14 +1538,9 @@ def print_html_report(covdata, details):
         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
@@ -1348,37 +1551,49 @@ def print_html_report(covdata, details):
         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:
@@ -1386,14 +1601,13 @@ def print_html_report(covdata, details):
         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:
@@ -1406,14 +1620,26 @@ def print_html_report(covdata, details):
                 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'] == '':
@@ -1422,10 +1648,10 @@ def print_html_report(covdata, details):
     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
@@ -1445,27 +1671,17 @@ def print_html_report(covdata, details):
         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()
@@ -1473,22 +1689,25 @@ def print_html_report(covdata, details):
         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>''')
@@ -1496,24 +1715,45 @@ def source_row(lineno, source, cdata):
     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">&check;</span>'
+                else:
+                    kwargs['linebranch'] += '<span class="notTakenBranch" title="Branch ' + str(
+                        branch) + ' not taken">&cross;</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}>
@@ -1534,10 +1774,10 @@ def html_row(details, sourcefile, **kwargs):
     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; "
@@ -1553,7 +1793,7 @@ def html_row(details, sourcefile, **kwargs):
         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'
@@ -1565,7 +1805,7 @@ def html_row(details, sourcefile, **kwargs):
         kwargs['BranchesBar'] = 'green'
 
     return rowstr.substitute(**kwargs)
-    
+
 
 #
 # Produce an XML report in the Cobertura format
@@ -1587,19 +1827,28 @@ def print_xml_report(covdata):
         (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.
@@ -1616,23 +1865,23 @@ def print_xml_report(covdata):
     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)
@@ -1644,7 +1893,7 @@ def print_xml_report(covdata):
         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:
@@ -1660,29 +1909,35 @@ def print_xml_report(covdata):
                 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
@@ -1695,7 +1950,7 @@ def print_xml_report(covdata):
     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)
@@ -1704,11 +1959,14 @@ def print_xml_report(covdata):
         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
@@ -1724,9 +1982,10 @@ def print_xml_report(covdata):
                 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()))
@@ -1740,19 +1999,24 @@ def print_xml_report(covdata):
         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()
 
 
@@ -1764,129 +2028,198 @@ def print_xml_report(covdata):
 # 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"
@@ -1894,12 +2227,22 @@ if options.version:
         "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"
@@ -1910,31 +2253,40 @@ if options.objdir:
             "(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)
@@ -1945,9 +2297,14 @@ else:
 #
 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)
 #
@@ -1956,11 +2313,13 @@ else:
 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
 #
@@ -1972,4 +2331,4 @@ else:
     print_text_report(covdata)
 
 if options.print_summary:
-    print_summary(covdata)
+    print_summary(covdata)
\ No newline at end of file