15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Crocodile - compute coverage numbers for Chrome coverage dashboard."""
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import platform
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import croc_html
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import croc_scan
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CrocError(Exception):
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Coverage error."""
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CrocStatError(CrocError):
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Error evaluating coverage stat."""
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#------------------------------------------------------------------------------
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CoverageStats(dict):
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Coverage statistics."""
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Default dictionary values for this stat.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DEFAULTS = { 'files_covered': 0,
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               'files_instrumented': 0,
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               'files_executable': 0,
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               'lines_covered': 0,
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               'lines_instrumented': 0,
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               'lines_executable': 0 }
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Add(self, coverage_stats):
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a contribution from another coverage stats dict.
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      coverage_stats: Statistics to add to this one.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in coverage_stats.iteritems():
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if k in self:
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self[k] += v
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self[k] = v
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddDefaults(self):
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Add some default stats which might be assumed present.
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Do not clobber if already present.  Adds resilience when evaling a
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    croc file which expects certain stats to exist."""
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for k, v in self.DEFAULTS.iteritems():
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not k in self:
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self[k] = v
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#------------------------------------------------------------------------------
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CoveredFile(object):
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Information about a single covered file."""
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, filename, **kwargs):
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Constructor.
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Full path to file, '/'-delimited.
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kwargs: Keyword args are attributes for file.
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.filename = filename
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.attrs = dict(kwargs)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Move these to attrs?
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.local_path = None      # Local path to file
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.in_lcov = False        # Is file instrumented?
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # No coverage data for file yet
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.lines = {}     # line_no -> None=executable, 0=instrumented, 1=covered
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stats = CoverageStats()
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def UpdateCoverage(self):
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Updates the coverage summary based on covered lines."""
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exe = instr = cov = 0
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for l in self.lines.itervalues():
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exe += 1
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if l is not None:
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        instr += 1
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if l == 1:
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cov += 1
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add stats that always exist
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stats = CoverageStats(lines_executable=exe,
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               lines_instrumented=instr,
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               lines_covered=cov,
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               files_executable=1)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add conditional stats
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if cov:
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.stats['files_covered'] = 1
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if instr or self.in_lcov:
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.stats['files_instrumented'] = 1
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#------------------------------------------------------------------------------
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CoveredDir(object):
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Information about a directory containing covered files."""
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, dirpath):
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Constructor.
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dirpath: Full path of directory, '/'-delimited.
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.dirpath = dirpath
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # List of covered files directly in this dir, indexed by filename (not
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # full path)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.files = {}
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # List of subdirs, indexed by filename (not full path)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.subdirs = {}
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Dict of CoverageStats objects summarizing all children, indexed by group
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.stats_by_group = {'all': CoverageStats()}
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: by language
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetTree(self, indent=''):
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Recursively gets stats for the directory and its children.
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      indent: indent prefix string.
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The tree as a string.
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dest = []
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Compile all groupstats
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    groupstats = []
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for group in sorted(self.stats_by_group):
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      s = self.stats_by_group[group]
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not s.get('lines_executable'):
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        continue        # Skip groups with no executable lines
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      groupstats.append('%s:%d/%d/%d' % (
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          group, s.get('lines_covered', 0),
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          s.get('lines_instrumented', 0),
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          s.get('lines_executable', 0)))
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    outline = '%s%-30s   %s' % (indent,
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                os.path.split(self.dirpath)[1] + '/',
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                '   '.join(groupstats))
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dest.append(outline.rstrip())
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for d in sorted(self.subdirs):
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dest.append(self.subdirs[d].GetTree(indent=indent + '  '))
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return '\n'.join(dest)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#------------------------------------------------------------------------------
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Coverage(object):
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Code coverage for a group of files."""
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Constructor."""
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.files = {}             # Map filename --> CoverageFile
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.root_dirs = []         # (root, altname)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.rules = []             # (regexp, dict of RHS attrs)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.tree = CoveredDir('')
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.print_stats = []       # Dicts of args to PrintStat()
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Functions which need to be replaced for unit testing
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.add_files_walk = os.walk         # Walk function for AddFiles()
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.scan_file = croc_scan.ScanFile   # Source scanner for AddFiles()
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def CleanupFilename(self, filename):
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Cleans up a filename.
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Input filename.
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The cleaned up filename.
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Changes all path separators to '/'.
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Makes relative paths (those starting with '../' or './' absolute.
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Replaces all instances of root dirs with alternate names.
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Change path separators
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    filename = filename.replace('\\', '/')
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Windows doesn't care about case sensitivity.
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if platform.system() in ['Windows', 'Microsoft']:
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename = filename.lower()
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If path is relative, make it absolute
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: Perhaps we should default to relative instead, and only understand
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # absolute to be files starting with '\', '/', or '[A-Za-z]:'?
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if filename.split('/')[0] in ('.', '..'):
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename = os.path.abspath(filename).replace('\\', '/')
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Replace alternate roots
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for root, alt_name in self.root_dirs:
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Windows doesn't care about case sensitivity.
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if platform.system() in ['Windows', 'Microsoft']:
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        root = root.lower()
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename = re.sub('^' + re.escape(root) + '(?=(/|$))',
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        alt_name, filename)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return filename
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ClassifyFile(self, filename):
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Applies rules to a filename, to see if we care about it.
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Input filename.
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      A dict of attributes for the file, accumulated from the right hand sides
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          of rules which fired.
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    attrs = {}
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Process all rules
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for regexp, rhs_dict in self.rules:
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if regexp.match(filename):
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        attrs.update(rhs_dict)
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return attrs
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: Files can belong to multiple groups?
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #   (test/source)
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #   (mac/pc/win)
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #   (media_test/all_tests)
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #   (small/med/large)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # How to handle that?
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddRoot(self, root_path, alt_name='_'):
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a root directory.
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      root_path: Root directory to add.
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      alt_name: If specified, name of root dir.  Otherwise, defaults to '_'.
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ValueError: alt_name was blank.
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Alt name must not be blank.  If it were, there wouldn't be a way to
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # reverse-resolve from a root-replaced path back to the local path, since
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # '' would always match the beginning of the candidate filename, resulting
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # in an infinite loop.
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not alt_name:
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError('AddRoot alt_name must not be blank.')
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Clean up root path based on existing rules
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.root_dirs.append([self.CleanupFilename(root_path), alt_name])
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddRule(self, path_regexp, **kwargs):
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds a rule.
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      path_regexp: Regular expression to match for filenames.  These are
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          matched after root directory replacement.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kwargs: Keyword arguments are attributes to set if the rule applies.
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Keyword arguments currently supported:
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      include: If True, includes matches; if False, excludes matches.  Ignored
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if None.
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      group: If not None, sets group to apply to matches.
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      language: If not None, sets file language to apply to matches.
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Compile regexp ahead of time
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.rules.append([re.compile(path_regexp), dict(kwargs)])
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetCoveredFile(self, filename, add=False):
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Gets the CoveredFile object for the filename.
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Name of file to find.
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      add: If True, will add the file if it's not present.  This applies the
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          transformations from AddRoot() and AddRule(), and only adds the file
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if a rule includes it, and it has a group and language.
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The matching CoveredFile object, or None if not present.
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Clean filename
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    filename = self.CleanupFilename(filename)
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check for existing match
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if filename in self.files:
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self.files[filename]
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # File isn't one we know about.  If we can't add it, give up.
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not add:
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check rules to see if file can be added.  Files must be included and
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # have a group and language.
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    attrs = self.ClassifyFile(filename)
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not (attrs.get('include')
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            and attrs.get('group')
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            and attrs.get('language')):
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add the file
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f = CoveredFile(filename, **attrs)
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.files[filename] = f
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Return the newly covered file
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return f
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def RemoveCoveredFile(self, cov_file):
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Removes the file from the covered file list.
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cov_file: A file object returned by GetCoveredFile().
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.files.pop(cov_file.filename)
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ParseLcovData(self, lcov_data):
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds coverage from LCOV-formatted data.
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      lcov_data: An iterable returning lines of data in LCOV format.  For
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          example, a file or list of strings.
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov_file = None
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov_lines = None
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for line in lcov_data:
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      line = line.strip()
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if line.startswith('SF:'):
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Start of data for a new file; payload is filename
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cov_file = self.GetCoveredFile(line[3:], add=True)
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if cov_file:
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cov_lines = cov_file.lines
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cov_file.in_lcov = True       # File was instrumented
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif not cov_file:
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Inside data for a file we don't care about - so skip it
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pass
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif line.startswith('DA:'):
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Data point - that is, an executable line in current file
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        line_no, is_covered = map(int, line[3:].split(','))
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if is_covered:
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Line is covered
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cov_lines[line_no] = 1
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif cov_lines.get(line_no) != 1:
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Line is not covered, so track it as uncovered
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cov_lines[line_no] = 0
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif line == 'end_of_record':
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cov_file.UpdateCoverage()
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cov_file = None
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # (else ignore other line types)
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ParseLcovFile(self, input_filename):
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds coverage data from a .lcov file.
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      input_filename: Input filename.
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: All manner of error checking
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    lcov_file = None
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      lcov_file = open(input_filename, 'rt')
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.ParseLcovData(lcov_file)
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    finally:
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if lcov_file:
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        lcov_file.close()
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetStat(self, stat, group='all', default=None):
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Gets a statistic from the coverage object.
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      stat: Statistic to get.  May also be an evaluatable python expression,
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          using the stats.  For example, 'stat1 - stat2'.
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      group: File group to match; if 'all', matches all groups.
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default: Value to return if there was an error evaluating the stat.  For
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          example, if the stat does not exist.  If None, raises
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          CrocStatError.
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The evaluated stat, or None if error.
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      CrocStatError: Error evaluating stat.
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: specify a subdir to get the stat from, then walk the tree to
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # print the stats from just that subdir
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Make sure the group exists
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if group not in self.tree.stats_by_group:
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if default is None:
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise CrocStatError('Group %r not found.' % group)
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return default
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stats = self.tree.stats_by_group[group]
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Unit tests use real dicts, not CoverageStats objects,
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # so we can't AddDefaults() on them.
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if group == 'all' and hasattr(stats, 'AddDefaults'):
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      stats.AddDefaults()
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return eval(stat, {'__builtins__': {'S': self.GetStat}}, stats)
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except Exception, e:
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if default is None:
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise CrocStatError('Error evaluating stat %r: %s' % (stat, e))
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return default
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def PrintStat(self, stat, format=None, outfile=sys.stdout, **kwargs):
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Prints a statistic from the coverage object.
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      stat: Statistic to get.  May also be an evaluatable python expression,
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          using the stats.  For example, 'stat1 - stat2'.
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      format: Format string to use when printing stat.  If None, prints the
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          stat and its evaluation.
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      outfile: File stream to output stat to; defaults to stdout.
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kwargs: Additional args to pass to GetStat().
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    s = self.GetStat(stat, **kwargs)
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if format is None:
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      outfile.write('GetStat(%r) = %s\n' % (stat, s))
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      outfile.write(format % s + '\n')
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddFiles(self, src_dir):
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds files to coverage information.
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    LCOV files only contains files which are compiled and instrumented as part
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    of running coverage.  This function finds missing files and adds them.
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      src_dir: Directory on disk at which to start search.  May be a relative
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          path on disk starting with '.' or '..', or an absolute path, or a
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          path relative to an alt_name for one of the roots
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          (for example, '_/src').  If the alt_name matches more than one root,
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          all matches will be attempted.
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Note that dirs not underneath one of the root dirs and covered by an
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    inclusion rule will be ignored.
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check for root dir alt_names in the path and replace with the actual
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # root dirs, then recurse.
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    found_root = False
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for root, alt_name in self.root_dirs:
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      replaced_root = re.sub('^' + re.escape(alt_name) + '(?=(/|$))', root,
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                             src_dir)
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if replaced_root != src_dir:
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        found_root = True
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.AddFiles(replaced_root)
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if found_root:
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return    # Replaced an alt_name with a root_dir, so already recursed.
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (dirpath, dirnames, filenames) in self.add_files_walk(src_dir):
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Make a copy of the dirnames list so we can modify the original to
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # prune subdirs we don't need to walk.
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for d in list(dirnames):
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Add trailing '/' to directory names so dir-based regexps can match
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # '/' instead of needing to specify '(/|$)'.
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        dpath = self.CleanupFilename(dirpath + '/' + d) + '/'
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        attrs = self.ClassifyFile(dpath)
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not attrs.get('include'):
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Directory has been excluded, so don't traverse it
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # TODO: Document the slight weirdness caused by this: If you
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # AddFiles('./A'), and the rules include 'A/B/C/D' but not 'A/B',
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # then it won't recurse into './A/B' so won't find './A/B/C/D'.
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Workarounds are to AddFiles('./A/B/C/D') or AddFiles('./A/B/C').
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # The latter works because it explicitly walks the contents of the
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # path passed to AddFiles(), so it finds './A/B/C/D'.
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          dirnames.remove(d)
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for f in filenames:
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        local_path = dirpath + '/' + f
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        covf = self.GetCoveredFile(local_path, add=True)
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not covf:
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Save where we found the file, for generating line-by-line HTML output
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        covf.local_path = local_path
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if covf.in_lcov:
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # File already instrumented and doesn't need to be scanned
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not covf.attrs.get('add_if_missing', 1):
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # Not allowed to add the file
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          self.RemoveCoveredFile(covf)
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Scan file to find potentially-executable lines
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        lines = self.scan_file(covf.local_path, covf.attrs.get('language'))
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if lines:
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          for l in lines:
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            covf.lines[l] = None
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          covf.UpdateCoverage()
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        else:
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          # File has no executable lines, so don't count it
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          self.RemoveCoveredFile(covf)
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddConfig(self, config_data, lcov_queue=None, addfiles_queue=None):
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds JSON-ish config data.
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      config_data: Config data string.
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      lcov_queue: If not None, object to append lcov_files to instead of
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          parsing them immediately.
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      addfiles_queue: If not None, object to append add_files to instead of
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          processing them immediately.
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: All manner of error checking
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cfg = eval(config_data, {'__builtins__': {}}, {})
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for rootdict in cfg.get('roots', []):
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddRoot(rootdict['root'], alt_name=rootdict.get('altname', '_'))
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for ruledict in cfg.get('rules', []):
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      regexp = ruledict.pop('regexp')
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddRule(regexp, **ruledict)
5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for add_lcov in cfg.get('lcov_files', []):
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if lcov_queue is not None:
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        lcov_queue.append(add_lcov)
5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.ParseLcovFile(add_lcov)
5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for add_path in cfg.get('add_files', []):
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if addfiles_queue is not None:
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        addfiles_queue.append(add_path)
5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.AddFiles(add_path)
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.print_stats += cfg.get('print_stats', [])
5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ParseConfig(self, filename, **kwargs):
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Parses a configuration file.
5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      filename: Config filename.
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kwargs: Additional parameters to pass to AddConfig().
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO: All manner of error checking
5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    f = None
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f = open(filename, 'rt')
5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Need to strip CR's from CRLF-terminated lines or posix systems can't
5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # eval the data.
5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      config_data = f.read().replace('\r\n', '\n')
5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # TODO: some sort of include syntax.
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      #
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Needs to be done at string-time rather than at eval()-time, so that
5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # it's possible to include parts of dicts.  Path from a file to its
5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # include should be relative to the dir containing the file.
5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      #
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Or perhaps it could be done after eval.  In that case, there'd be an
5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # 'include' section with a list of files to include.  Those would be
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # eval()'d and recursively pre- or post-merged with the including file.
5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      #
5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Or maybe just don't worry about it, since multiple configs can be
5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # specified on the command line.
5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.AddConfig(config_data, **kwargs)
5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    finally:
5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if f:
5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        f.close()
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def UpdateTreeStats(self):
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Recalculates the tree stats from the currently covered files.
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Also calculates coverage summary for files.
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.tree = CoveredDir('')
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for cov_file in self.files.itervalues():
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Add the file to the tree
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      fdirs = cov_file.filename.split('/')
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parent = self.tree
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ancestors = [parent]
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for d in fdirs[:-1]:
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if d not in parent.subdirs:
5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if parent.dirpath:
5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parent.subdirs[d] = CoveredDir(parent.dirpath + '/' + d)
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          else:
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            parent.subdirs[d] = CoveredDir(d)
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        parent = parent.subdirs[d]
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ancestors.append(parent)
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Final subdir actually contains the file
5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parent.files[fdirs[-1]] = cov_file
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Now add file's contribution to coverage by dir
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for a in ancestors:
5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Add to 'all' group
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        a.stats_by_group['all'].Add(cov_file.stats)
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Add to group file belongs to
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        group = cov_file.attrs.get('group')
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if group not in a.stats_by_group:
6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          a.stats_by_group[group] = CoverageStats()
6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cbyg = a.stats_by_group[group]
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cbyg.Add(cov_file.stats)
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def PrintTree(self):
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Prints the tree stats."""
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Print the tree
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'Lines of code coverage by directory:'
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print self.tree.GetTree()
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#------------------------------------------------------------------------------
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Main(argv):
6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Main routine.
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Args:
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    argv: list of arguments
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit code, 0 for normal exit.
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Parse args
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser = optparse.OptionParser()
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-i', '--input', dest='inputs', type='string', action='append',
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      metavar='FILE',
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='read LCOV input from FILE')
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-r', '--root', dest='roots', type='string', action='append',
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      metavar='ROOT[=ALTNAME]',
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='add ROOT directory, optionally map in coverage results as ALTNAME')
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-c', '--config', dest='configs', type='string', action='append',
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      metavar='FILE',
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='read settings from configuration FILE')
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-a', '--addfiles', dest='addfiles', type='string', action='append',
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      metavar='PATH',
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='add files from PATH to coverage data')
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-t', '--tree', dest='tree', action='store_true',
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='print tree of code coverage by group')
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-u', '--uninstrumented', dest='uninstrumented', action='store_true',
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='list uninstrumented files')
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-m', '--html', dest='html_out', type='string', metavar='PATH',
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='write HTML output to PATH')
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option(
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '-b', '--base_url', dest='base_url', type='string', metavar='URL',
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      help='include URL in base tag of HTML output')
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.set_defaults(
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      inputs=[],
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      roots=[],
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      configs=[],
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      addfiles=[],
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      tree=False,
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      html_out=None,
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  )
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  options = parser.parse_args(args=argv)[0]
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cov = Coverage()
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Set root directories for coverage
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for root_opt in options.roots:
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if '=' in root_opt:
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cov.AddRoot(*root_opt.split('='))
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cov.AddRoot(root_opt)
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Read config files
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for config_file in options.configs:
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov.ParseConfig(config_file, lcov_queue=options.inputs,
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    addfiles_queue=options.addfiles)
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Parse lcov files
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for input_filename in options.inputs:
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov.ParseLcovFile(input_filename)
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Add missing files
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for add_path in options.addfiles:
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov.AddFiles(add_path)
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Print help if no files specified
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not cov.files:
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'No covered files found.'
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parser.print_help()
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Update tree stats
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cov.UpdateTreeStats()
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Print uninstrumented filenames
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.uninstrumented:
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    print 'Uninstrumented files:'
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for f in sorted(cov.files):
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      covf = cov.files[f]
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not covf.in_lcov:
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        print '  %-6s %-6s %s' % (covf.attrs.get('group'),
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                  covf.attrs.get('language'), f)
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Print tree stats
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.tree:
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov.PrintTree()
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Print stats
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for ps_args in cov.print_stats:
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cov.PrintStat(**ps_args)
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Generate HTML
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.html_out:
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    html = croc_html.CrocHtml(cov, options.html_out, options.base_url)
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    html.Write()
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Normal exit
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(Main(sys.argv))
723