source: gcovr/trunk/scripts/gcovr @ 2756

Revision 2756, 29.9 KB checked in by jdsiiro, 2 years ago (diff)

Cleanup in how we parse/split gcov result lines. This resolves path
issues on Windows (see #3913, item 1).

  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision
Line 
1#! /usr/bin/env python
2#
3# A report generator for gcov 3.4
4#
5# This routine generates a format that is similar to the format generated
6# by the Python coverage.py module.  This code is similar to the
7# data processing performed by lcov's geninfo command.  However, we
8# don't worry about parsing the *.gcna files, and backwards compatibility for
9# older versions of gcov is not supported.
10#
11# Outstanding issues
12#   - verify that gcov 3.4 or newer is being used
13#   - verify support for symbolic links
14#
15# gcovr is a FAST project.  For documentation, bug reporting, and
16# updates, see https://software.sandia.gov/trac/fast/wiki/gcovr
17#
18# _________________________________________________________________________
19#
20# FAST: Utilities for Agile Software Development
21# Copyright (c) 2008 Sandia Corporation.
22# This software is distributed under the BSD License.
23# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
24# the U.S. Government retains certain rights in this software.
25# For more information, see the FAST README.txt file.
26#
27# $Revision 2756 $
28# $Date 2012-04-05 11:32:20 -0700 (Thu, 05 Apr 2012) $
29# _________________________________________________________________________
30#
31
32import copy
33import glob
34import os
35import re
36import subprocess
37import sys
38import time
39import xml.dom.minidom
40
41from optparse import OptionParser
42from string import Template
43from os.path import normpath
44
45__version__ = "2.4-prerelease"
46src_revision = "$Revision 2756 $"
47gcov_cmd = "gcov"
48
49output_re = re.compile("[Cc]reating [`'](.*)'$")
50source_re = re.compile("cannot open (source|graph) file")
51
52def version_str():
53    ans = __version__
54    m = re.match('\$Revision:\s*(\S+)\s*\$', src_revision)
55    if m:
56        ans = ans + " (r%s)" % (m.group(1))
57    return ans
58
59#
60# Container object for coverage statistics
61#
62class CoverageData(object):
63
64    def __init__(self, fname, uncovered, covered, branches, noncode):
65        self.fname=fname
66        # Shallow copies are cheap & "safe" because the caller will
67        # throw away their copies of covered & uncovered after calling
68        # us exactly *once*
69        self.uncovered = copy.copy(uncovered)
70        self.covered   = copy.copy(covered)
71        self.noncode   = copy.copy(noncode)
72        # But, a deep copy is required here
73        self.all_lines = copy.deepcopy(uncovered)
74        self.all_lines.update(covered.keys())
75        self.branches = copy.deepcopy(branches)
76
77    def update(self, uncovered, covered, branches, noncode):
78        self.all_lines.update(uncovered)
79        self.all_lines.update(covered.keys())
80        self.uncovered.update(uncovered)
81        self.noncode.intersection_update(noncode)
82        for k in covered.keys():
83            self.covered[k] = self.covered.get(k,0) + covered[k]
84        for k in branches.keys():
85            for b in branches[k]:
86                d = self.branches.setdefault(k, {})
87                d[b] = d.get(b, 0) + branches[k][b]
88        self.uncovered.difference_update(self.covered.keys())
89
90    def uncovered_str(self):
91        if options.show_branch:
92            # Don't do any aggregation on branch results
93            tmp = []
94            for line in self.branches.keys():
95                for branch in self.branches[line]:
96                    if self.branches[line][branch] == 0:
97                        tmp.append(line)
98                        break
99
100            tmp.sort()
101            return ",".join([str(x) for x in tmp]) or ""
102       
103        tmp = list(self.uncovered)
104        if len(tmp) == 0:
105            return ""
106
107        tmp.sort()
108        first = None
109        last = None
110        ranges=[]
111        for item in tmp:
112            if last is None:
113                first=item
114                last=item
115            elif item == (last+1):
116                last=item
117            else:
118                if len(self.noncode.intersection(range(last+1,item))) \
119                       == item - last - 1:
120                    last = item
121                    continue
122               
123                if first==last:
124                    ranges.append(str(first))
125                else:
126                    ranges.append(str(first)+"-"+str(last))
127                first=item
128                last=item
129        if first==last:
130            ranges.append(str(first))
131        else:
132            ranges.append(str(first)+"-"+str(last))
133        return ",".join(ranges)
134
135    def coverage(self):
136        if ( options.show_branch ):
137            total = 0
138            cover = 0
139            for line in self.branches.keys():
140                for branch in self.branches[line].keys():
141                    total += 1
142                    cover += self.branches[line][branch] > 0 and 1 or 0
143        else:
144            total = len(self.all_lines)
145            cover = len(self.covered)
146           
147        percent = total and str(int(100.0*cover/total)) or "--"
148        return (total, cover, percent)
149
150    def summary(self):
151        tmp = options.filter.sub('',self.fname)
152        if not self.fname.endswith(tmp):
153            # Do no truncation if the filter does not start matching at
154            # the beginning of the string
155            tmp = self.fname
156        tmp = tmp.ljust(40)
157        if len(tmp) > 40:
158            tmp=tmp+"\n"+" "*40
159
160        (total, cover, percent) = self.coverage()
161        return ( total, cover,
162                 tmp + str(total).rjust(8) + str(cover).rjust(8) + \
163                 percent.rjust(6) + "%   " + self.uncovered_str() )
164
165
166def search_file(expr, path=None, abspath=False, follow_links=False):
167    """
168    Given a search path, recursively descend to find files that match a
169    regular expression.
170
171    Can specify the following options:
172       path - The directory that is searched recursively
173       executable_extension - This string is used to see if there is an
174           implicit extension in the filename
175       executable - Test if the file is an executable (default=False)
176       isfile - Test if the file is file (default=True)
177    """
178    ans = []
179    pattern = re.compile(expr)
180    if path is None or path == ".":
181        path = os.getcwd()
182    elif not os.path.exists(path):
183        raise IOError("Unknown directory '"+path+"'")
184    for root, dirs, files in os.walk(path, topdown=True):
185        for name in files:
186           if pattern.match(name):
187                name = os.path.join(root,name)
188                if follow_links and os.path.islink(name):
189                    ans.append( os.path.abspath(os.readlink(name)) )
190                elif abspath:
191                    ans.append( os.path.abspath(name) )
192                else:
193                    ans.append( name )
194    return ans
195
196
197#
198# Get the list of datafiles in the directories specified by the user
199#
200def get_datafiles(flist, options, ext="gcda"):
201    allfiles=[]
202    for dir in flist:
203        if options.verbose:
204            sys.stdout.write("Scanning directory "+dir+" for "+ext+" files...\n")
205        files = search_file(".*\."+ext, dir, abspath=True, follow_links=True)
206        if options.verbose:
207            sys.stdout.write("Found %d files \n" % len(files))
208        allfiles += files
209    return allfiles
210
211
212def process_gcov_data(file, covdata, options):
213    INPUT = open(file,"r")
214    #
215    # Get the filename
216    #
217    line = INPUT.readline()
218    segments=line.split(':',3)
219    if len(segments) != 4 or not segments[2].lower().strip().endswith('source'):
220        raise RuntimeError('Fatal error parsing gcov file, line 1: \n\t"%s"' % line.rstrip())
221    fname = os.path.abspath((segments[-1]).strip())
222    if options.verbose:
223        sys.stdout.write("Parsing coverage data for file %s\n" % fname)
224    #
225    # Return if the filename does not match the filter
226    #
227    if not options.filter.match(fname):
228        if options.verbose:
229            sys.stdout.write("  Filtering coverage data for file %s\n" % fname)
230        return
231    #
232    # Return if the filename matches the exclude pattern
233    #
234    for i in range(0,len(options.exclude)):
235        if options.exclude[i].match(options.filter.sub('',fname)) or \
236               options.exclude[i].match(fname) or \
237               options.exclude[i].match(os.path.abspath(fname)):
238            if options.verbose:
239                sys.stdout.write("  Excluding coverage data for file %s\n" % fname)
240            return
241    #
242    # Parse each line, and record the lines
243    # that are uncovered
244    #
245    noncode   = set()
246    uncovered = set()
247    covered   = {}
248    branches  = {}
249    #first_record=True
250    lineno = 0
251    for line in INPUT:
252        segments=line.split(":",2)
253        tmp = segments[0].strip()
254        if len(segments) > 1:
255            try:
256                lineno = int(segments[1].strip())
257            except:
258                pass # keep previous line number!
259           
260        if tmp[0] == '#':
261            uncovered.add( lineno )
262        elif tmp[0] in "0123456789":
263            covered[lineno] = int(segments[0].strip())
264        elif tmp[0] == '-':
265            # remember certain non-executed lines
266            code = segments[2].strip()
267            if len(code) == 0 or code == "{" or code == "}" or \
268               code.startswith("//") or code == 'else':
269                noncode.add( lineno )
270        elif tmp.startswith('branch'):
271            fields = line.split()
272            try:
273                count = int(fields[3])
274                branches.setdefault(lineno, {})[int(fields[1])] = count
275            except:
276                # We ignore branches that were "never executed"
277                pass
278        elif tmp.startswith('call'):
279            pass
280        elif tmp.startswith('function'):
281            pass
282        elif tmp[0] == 'f':
283            pass
284            #if first_record:
285                #first_record=False
286                #uncovered.add(prev)
287            #if prev in uncovered:
288                #tokens=re.split('[ \t]+',tmp)
289                #if tokens[3] != "0":
290                    #uncovered.remove(prev)
291            #prev = int(segments[1].strip())
292            #first_record=True
293        else:
294            sys.stdout.write("UNKNOWN LINE DATA: %s\n" % tmp)
295    #
296    # If the file is already in covdata, then we
297    # remove lines that are covered here.  Otherwise,
298    # initialize covdata
299    #
300    if not fname in covdata:
301        covdata[fname] = CoverageData(fname,uncovered,covered,branches,noncode)
302    else:
303        covdata[fname].update(uncovered,covered,branches,noncode)
304    INPUT.close()
305
306#
307# Process a datafile (generated by running the instrumented application)
308# and run gcov with the corresponding arguments
309#
310# This is trickier than it sounds: The gcda/gcno files are stored in the
311# same directory as the object files; however, gcov must be run from the
312# same directory where gcc/g++ was run.  Normally, the user would know
313# where gcc/g++ was invoked from and could tell gcov the path to the
314# object (and gcda) files with the --object-directory command.
315# Unfortunately, we do everything backwards: gcovr looks for the gcda
316# files and then has to infer the original gcc working directory.
317#
318# In general, (but not always) we can assume that the gcda file is in a
319# subdirectory of the original gcc working directory, so we will first
320# try ".", and on error, move up the directory tree looking for the
321# correct working directory (letting gcov's own error codes dictate when
322# we hit the right directory).  This covers 90+% of the "normal" cases.
323# The exception to this is if gcc was invoked with "-o ../[...]" (i.e.,
324# the object directory was a peer (not a parent/child) of the cwd.  In
325# this case, things are really tough.  We accept an argument
326# (--object-directory) that SHOULD BE THE SAME as the one povided to
327# gcc.  We will then walk that path (backwards) in the hopes of
328# identifying the original gcc working directory (there is a bit of
329# trial-and-error here)
330#
331def process_datafile(filename, covdata, options):
332    #
333    # Launch gcov
334    #
335    abs_filename = os.path.abspath(filename)
336    (dirname,fname) = os.path.split(abs_filename)
337    #(name,ext) = os.path.splitext(base)
338
339    potential_wd = []
340    starting_dir = os.getcwd()
341    errors=[]
342    Done = False
343
344    if options.objdir:
345        src_components = abs_filename.split(os.sep)
346        components = normpath(options.objdir).split(os.sep)
347        idx = 1
348        while idx <= len(components):
349            if idx > len(src_components):
350                break
351            if components[-1*idx] != src_components[-1*idx]:
352                break
353            idx += 1
354        if idx > len(components):
355            pass # a parent dir; the normal process will find it
356        elif components[-1*idx] == '..':
357            dirs = [ os.path.join(src_components[:len(src_components)-idx+1]) ]
358            while idx <= len(components) and components[-1*idx] == '..':
359                tmp = []
360                for d in dirs:
361                    for f in os.listdir(d):
362                        x = os.path.join(d,f)
363                        if os.path.isdir(x):
364                            tmp.append(x)
365                dirs = tmp
366                idx += 1
367            potential_wd = dirs
368        else:
369            if components[0] == '':
370                # absolute path
371                tmp = [ options.objdir ]
372            else:
373                # relative path: check relative to both the cwd and the
374                # gcda file
375                tmp = [ os.path.join(x, options.objdir) for x in
376                        [os.path.dirname(abs_filename), os.getcwd()] ]
377            potential_wd = [ testdir for testdir in tmp
378                             if os.path.isdir(testdir) ]
379            if len(potential_wd) == 0:
380                errors.append("ERROR: cannot identify the location where GCC "
381                              "was run using --object-directory=%s\n" %
382                              options.objdir)
383            # Revert to the normal
384            #sys.exit(1)
385
386    # no objdir was specified (or it was a parent dir); walk up the dir tree
387    if len(potential_wd) == 0:
388        wd = os.path.split(abs_filename)[0]
389        while True:
390            potential_wd.append(wd)
391            wd = os.path.split(wd)[0]
392            if wd == potential_wd[-1]:
393                break
394
395    cmd = [ gcov_cmd, abs_filename,
396            "--branch-counts", "--branch-probabilities", "--preserve-paths",
397            '--object-directory', dirname ]
398
399    while len(potential_wd) > 0 and not Done:
400        # NB: either len(potential_wd) == 1, or all entires are absolute
401        # paths, so we don't have to chdir(starting_dir) at every
402        # iteration.
403        os.chdir(potential_wd.pop(0))
404       
405       
406        #if options.objdir:
407        #    cmd.extend(["--object-directory", Template(options.objdir).substitute(filename=filename, head=dirname, tail=base, root=name, ext=ext)])
408
409        if options.verbose:
410            sys.stdout.write("Running gcov: '%s' in '%s'\n" % ( ' '.join(cmd), os.getcwd() ))
411        (out, err) = subprocess.Popen( cmd,
412                                       stdout=subprocess.PIPE,
413                                       stderr=subprocess.PIPE ).communicate()
414        out=out.decode('utf-8')
415        err=err.decode('utf-8')
416
417        # find the files that gcov created
418        gcov_files = {'active':[], 'filter':[], 'exclude':[]}
419        for line in out.split(os.linesep):
420            found = output_re.search(line)
421            if found is not None:
422                fname = found.group(1)
423                if not options.gcov_filter.match(fname):
424                    if options.verbose:
425                        sys.stdout.write("Filtering gcov file %s\n" % fname)
426                    gcov_files['filter'].append(fname)
427                    continue
428                exclude=False
429                for i in range(0,len(options.gcov_exclude)):
430                    if options.gcov_exclude[i].match(options.gcov_filter.sub('',fname)) or \
431                           options.gcov_exclude[i].match(fname) or \
432                           options.gcov_exclude[i].match(os.path.abspath(fname)):
433                        exclude=True
434                        break
435                if not exclude:
436                    gcov_files['active'].append(fname)
437                elif options.verbose:
438                    sys.stdout.write("Excluding gcov file %s\n" % fname)
439                    gcov_files['exclude'].append(fname)
440
441        if source_re.search(err):
442            # gcov tossed errors: try the next potential_wd
443            errors.append(err)
444        else:
445            # Process *.gcov files
446            for fname in gcov_files['active']:
447                process_gcov_data(fname, covdata, options)
448            Done = True
449
450        if not options.keep:
451            for group in gcov_files.values():
452                for fname in group:
453                    os.remove(fname)
454
455    os.chdir(starting_dir)
456    if options.delete:
457        os.remove(abs_filename)
458       
459    if not Done:
460        sys.stdout.write("GCOV produced the following errors processing %s:\n   %s" \
461              "(gcovr could not infer a working directory " \
462              "that resolved it.)\n" % ( filename, "   ".join(errors) ))
463
464#
465# Produce the classic gcovr text report
466#
467def print_text_report(covdata):
468    def _num_uncovered(key):
469        (total, covered, percent) = covdata[key].coverage()
470        return total - covered
471    def _percent_uncovered(key):
472        (total, covered, percent) = covdata[key].coverage()
473        if covered:
474            return -1.0*covered/total
475        else:
476            return total or 1e6
477    def _alpha(key):
478        return key
479
480    if options.output:
481        OUTPUT = open(options.output,'w')
482    else:
483        OUTPUT = sys.stdout
484    total_lines=0
485    total_covered=0
486    # Header
487    OUTPUT.write("-"*78 + '\n')
488    a = options.show_branch and "Branch" or "Lines"
489    b = options.show_branch and "Taken" or "Exec"
490    OUTPUT.write("File".ljust(40) + a.rjust(8) + b.rjust(8)+ "  Cover   Missing\n")
491    OUTPUT.write("-"*78 + '\n')
492
493    # Data
494    keys = list(covdata.keys())
495    keys.sort(key=options.sort_uncovered and _num_uncovered or \
496              options.sort_percent and _percent_uncovered or _alpha)
497    for key in keys:
498        (t, n, txt) = covdata[key].summary()
499        total_lines += t
500        total_covered += n
501        OUTPUT.write(txt + '\n')
502
503    # Footer & summary
504    OUTPUT.write("-"*78 + '\n')
505    percent = total_lines and str(int(100.0*total_covered/total_lines)) or "--"
506    OUTPUT.write("TOTAL".ljust(40) + str(total_lines).rjust(8) + \
507          str(total_covered).rjust(8) + str(percent).rjust(6)+"%" + '\n')
508    OUTPUT.write("-"*78 + '\n')
509
510    # Close logfile
511    if options.output:
512        OUTPUT.close()
513
514#
515# Produce an XML report in the Cobertura format
516#
517def print_xml_report(covdata):
518    branchTotal = 0
519    branchCovered = 0
520    lineTotal = 0
521    lineCovered = 0
522
523    options.show_branch = True
524    for key in covdata.keys():
525        (total, covered, percent) = covdata[key].coverage()
526        branchTotal += total
527        branchCovered += covered
528
529    options.show_branch = False
530    for key in covdata.keys():
531        (total, covered, percent) = covdata[key].coverage()
532        lineTotal += total
533        lineCovered += covered
534   
535    impl = xml.dom.minidom.getDOMImplementation()
536    docType = impl.createDocumentType(
537        "coverage", None,
538        "http://cobertura.sourceforge.net/xml/coverage-03.dtd" )
539    doc = impl.createDocument(None, "coverage", docType)
540    root = doc.documentElement
541    root.setAttribute( "line-rate", lineTotal == 0 and '0.0' or
542                       str(float(lineCovered) / lineTotal) )
543    root.setAttribute( "branch-rate", branchTotal == 0 and '0.0' or
544                       str(float(branchCovered) / branchTotal) )
545    root.setAttribute( "timestamp", str(int(time.time())) )
546    root.setAttribute( "version", "gcovr %s" % (version_str(),) )
547
548    # Generate the <sources> element: this is either the root directory
549    # (specified by --root), or the CWD.
550    sources = doc.createElement("sources")
551    root.appendChild(sources)
552
553    # Generate the coverage output (on a per-package basis)
554    packageXml = doc.createElement("packages")
555    root.appendChild(packageXml)
556    packages = {}
557    source_dirs = set()
558
559    keys = list(covdata.keys())
560    keys.sort()
561    for f in keys:
562        data = covdata[f]
563        dir = options.filter.sub('',f)
564        if f.endswith(dir):
565            src_path = f[:-1*len(dir)]
566            if len(src_path) > 0:
567                while dir.startswith(os.path.sep):
568                    src_path += os.path.sep
569                    dir = dir[len(os.path.sep):]
570                source_dirs.add(src_path)
571        else:
572            # Do no truncation if the filter does not start matching at
573            # the beginning of the string
574            dir = f
575        (dir, fname) = os.path.split(dir)
576       
577        package = packages.setdefault(
578            dir, [ doc.createElement("package"), {},
579                   0, 0, 0, 0 ] )
580        c = doc.createElement("class")
581        lines = doc.createElement("lines")
582        c.appendChild(lines)
583
584        class_lines = 0
585        class_hits = 0
586        class_branches = 0
587        class_branch_hits = 0
588        for line in data.all_lines:
589            hits = data.covered.get(line, 0)
590            class_lines += 1
591            if hits > 0:
592                class_hits += 1
593            l = doc.createElement("line")
594            l.setAttribute("number", str(line))
595            l.setAttribute("hits", str(hits))
596            branches = data.branches.get(line)
597            if branches is None:
598                l.setAttribute("branch", "false")
599            else:
600                b_hits = 0
601                for v in branches.values():
602                    if v > 0:
603                        b_hits += 1
604                coverage = 100*b_hits/len(branches)
605                l.setAttribute("branch", "true")
606                l.setAttribute( "condition-coverage",
607                                "%i%% (%i/%i)" %
608                                (coverage, b_hits, len(branches)) )
609                cond = doc.createElement('condition')
610                cond.setAttribute("number", "0")
611                cond.setAttribute("type", "jump")
612                cond.setAttribute("coverage", "%i%%" % ( coverage ) )
613                class_branch_hits += b_hits
614                class_branches += float(len(branches))
615                conditions = doc.createElement("conditions")
616                conditions.appendChild(cond)
617                l.appendChild(conditions)
618               
619            lines.appendChild(l)
620
621        className = fname.replace('.', '_')
622        c.setAttribute("name", className)
623        c.setAttribute("filename", os.path.join(dir, fname))
624        c.setAttribute("line-rate", str(class_hits / (1.0*class_lines or 1.0)))
625        c.setAttribute( "branch-rate",
626                        str(class_branch_hits / (1.0*class_branches or 1.0)) )
627        c.setAttribute("complexity", "0.0")
628
629        package[1][className] = c
630        package[2] += class_hits
631        package[3] += class_lines
632        package[4] += class_branch_hits
633        package[5] += class_branches
634
635    for packageName, packageData in packages.items():
636        package = packageData[0];
637        packageXml.appendChild(package)
638        classes = doc.createElement("classes")
639        package.appendChild(classes)
640        classNames = list(packageData[1].keys())
641        classNames.sort()
642        for className in classNames:
643            classes.appendChild(packageData[1][className])
644        package.setAttribute("name", packageName.replace(os.sep, '.'))
645        package.setAttribute("line-rate", str(packageData[2]/(1.0*packageData[3] or 1.0)))
646        package.setAttribute( "branch-rate", str(packageData[4] / (1.0*packageData[5] or 1.0) ))
647        package.setAttribute("complexity", "0.0")
648
649
650    # Populate the <sources> element: this is either the root directory
651    # (specified by --root), or relative directories based
652    # on the filter, or the CWD
653    if options.root is not None:
654        source = doc.createElement("source")
655        source.appendChild(doc.createTextNode(options.root))
656        sources.appendChild(source)
657    elif len(source_dirs) > 0:
658        cwd = os.getcwd()
659        for d in source_dirs:
660            source = doc.createElement("source")
661            if d.startswith(cwd):
662                reldir = d[len(cwd):].lstrip(os.path.sep)
663            elif cwd.startswith(d):
664                i = 1
665                sys.stdout.write(d+'\n')
666                sys.stdout.write(os.path.join(*tuple([cwd]+['..']*i)) + '\n')
667                while normpath(d) != \
668                          normpath(os.path.join(*tuple([cwd]+['..']*i))):
669                    i += 1
670                reldir = os.path.join(*tuple(['..']*i))
671            else:
672                reldir = d
673            source.appendChild(doc.createTextNode(reldir))
674            sources.appendChild(source)
675    else:
676        source = doc.createElement("source")
677        source.appendChild(doc.createTextNode('.'))
678        sources.appendChild(source)
679
680    xmlString = doc.toprettyxml()
681    #xml.dom.ext.PrettyPrint(doc)
682    if options.output is None:
683        sys.stdout.write(xmlString+'\n')
684    else:
685        OUTPUT = open(options.output, 'w')
686        OUTPUT.write(xmlString +'\n')
687        OUTPUT.close()
688
689
690##
691## MAIN
692##
693
694#
695# Create option parser
696#
697parser = OptionParser()
698parser.add_option("--version",
699        help="Print the version number, then exit",
700        action="store_true",
701        dest="version",
702        default=False)
703parser.add_option("-v","--verbose",
704        help="Print progress messages",
705        action="store_true",
706        dest="verbose",
707        default=False)
708parser.add_option('--object-directory',
709        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.",
710        action="store",
711        dest="objdir",
712        default=None)
713parser.add_option("-o","--output",
714        help="Print output to this filename",
715        action="store",
716        dest="output",
717        default=None)
718parser.add_option("-k","--keep",
719        help="Keep temporary gcov files",
720        action="store_true",
721        dest="keep",
722        default=False)
723parser.add_option("-d","--delete",
724        help="Delete the coverage files after they are processed",
725        action="store_true",
726        dest="delete",
727        default=False)
728parser.add_option("-f","--filter",
729        help="Keep only the data files that match this regular expression",
730        action="store",
731        dest="filter",
732        default=None)
733parser.add_option("-e","--exclude",
734        help="Exclude data files that match this regular expression",
735        action="append",
736        dest="exclude",
737        default=[])
738parser.add_option("--gcov-filter",
739        help="Keep only gcov data files that match this regular expression",
740        action="store",
741        dest="gcov_filter",
742        default=None)
743parser.add_option("--gcov-exclude",
744        help="Exclude gcov data files that match this regular expression",
745        action="append",
746        dest="gcov_exclude",
747        default=[])
748parser.add_option("-r","--root",
749        help="Defines the root directory.  This is used to filter the files, and to standardize the output.",
750        action="store",
751        dest="root",
752        default=None)
753parser.add_option("-x","--xml",
754        help="Generate XML instead of the normal tabular output.",
755        action="store_true",
756        dest="xml",
757        default=None)
758parser.add_option("-b","--branches",
759        help="Tabulate the branch coverage instead of the line coverage.",
760        action="store_true",
761        dest="show_branch",
762        default=None)
763parser.add_option("-u","--sort-uncovered",
764        help="Sort entries by increasing number of uncovered lines.",
765        action="store_true",
766        dest="sort_uncovered",
767        default=None)
768parser.add_option("-p","--sort-percentage",
769        help="Sort entries by decreasing percentage of covered lines.",
770        action="store_true",
771        dest="sort_percent",
772        default=None)
773parser.usage="gcovr [options]"
774parser.description="A utility to run gcov and generate a simple report that summarizes the coverage"
775#
776# Process options
777#
778(options, args) = parser.parse_args(args=sys.argv)
779if options.version:
780    sys.stdout.write("gcovr %s\n" % (version_str(),))
781    sys.stdout.write("\n")
782    sys.stdout.write("Copyright (2008) Sandia Corporation. Under the terms of Contract \n")
783    sys.stdout.write("DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government \n")
784    sys.stdout.write("retains certain rights in this software.\n")
785    sys.exit(0)
786if options.objdir:
787    if normpath(options.objdir) != options.objdir.replace('/',os.sep):
788        sys.stdout.write("WARNING: relative referencing in --object-directory; this could\n")
789        sys.stdout.write("         cause strange errors when gcovr attempts to identify\n")
790        sys.stdout.write("         the original gcc working directory.\n")
791#
792# Setup filters
793#
794for i in range(0,len(options.exclude)):
795    options.exclude[i] = re.compile(options.exclude[i])
796if options.filter is not None:
797    options.filter = re.compile(options.filter)
798elif options.root is not None:
799    if not options.root:
800        sys.stderr.write("ERROR: empty --root option.\n"
801                         "   Root specifies the path to the root directory of your project: "
802                         "cannot be an empty string.\n")
803        sys.exit(1)
804    options.filter = re.compile(re.escape(os.path.abspath(options.root)+os.sep))
805if options.filter is None:
806    options.filter = re.compile('')
807#
808for i in range(0,len(options.gcov_exclude)):
809    options.gcov_exclude[i] = re.compile(options.gcov_exclude[i])
810if options.gcov_filter is not None:
811    options.gcov_filter = re.compile(options.gcov_filter)
812else:
813    options.gcov_filter = re.compile('')
814#
815# Get data files
816#
817if len(args) == 1:
818    datafiles = get_datafiles(["."], options)
819else:
820    datafiles = get_datafiles(args[1:], options)
821#
822# Get coverage data
823#
824covdata = {}
825for file in datafiles:
826    process_datafile(file,covdata,options)
827if options.verbose:
828    sys.stdout.write("Gathered coveraged data for "+str(len(covdata))+" files\n")
829#
830# Print report
831#
832if options.xml:
833    print_xml_report(covdata)
834else:
835    print_text_report(covdata)
Note: See TracBrowser for help on using the repository browser.