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