#!/usr/bin/env python # Copyright (C) 2004, 2005, 2006 Nathaniel Smith # Copyright (C) 2007 Holger Hans Peter Freyther # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os, sys # from BitBake def mkdirhier(dir): """Create a directory like 'mkdir -p', but does not complain if directory already exists like os.makedirs """ try: os.makedirs(dir) except OSError, e: if e.errno != 17: raise e def collect_base(src,match_array): """ Collect all files that match the match_array. """ sources = [] for root, dirs, files in os.walk(src): if ".svn" in root: continue for file in files: base,ext = os.path.splitext(file) if ext in match_array: sources.append( os.path.join(root, file) ) return sources def collect_depends(src): return collect_base(src, [".d"]) def parse_dependency_file(src, base_dir, black_list): """ Parse the .d files of the gcc Wow, the first time os.path.join is doing the right thing. We might have a relative path in the depends using os.path.join(dirname of .d, dep) we will end up in """ file = open(src) file = file.read() file = file.replace('\\', '').replace('\n', '') # We now have object: dependencies splitted ar = file.split(':', 1) obj = ar[0].strip() dir = os.path.dirname(obj) deps = ar[1].split(' ') # Remove files outside WebKit, make path absolute deps = filter(lambda x: base_dir in x, deps) deps = map(lambda x: os.path.abspath(os.path.join(dir, x)), deps) return (obj, dir, deps) def collect_cov(base_path,targets): """ Collect gcov files, collect_sources is not used as it also creates dirs and needs to do substituting. Actually we will build a mapping from source file to gcov files of interest. This is because we could have bytestream.h in many different subdirectories. And we would endup with bla.cpp##bytestream.h and we do not know which bytestream file was tested """ def find_source_file(root,cov_file): """ Find a Source line or crash '#Users#ich#projekte#src#threadmessage.cpp###space#dports#include#qt3#qstring.h.gcov' '#Users#ich#projekte#src#threadmessage.cpp##..#^#src#threadmessage.cpp.gcov' ### is absolute path ##..#^# is relative path... well a gcov bug as well ## normal split file in the same directory """ if '###' in cov_file: split = cov_file.split('###') if not len(split) == 2: raise "Unexpected split result" filepath = split[1][:-5].replace('#',os.path.sep) return os.path.join(os.path.sep,filepath) elif '##..#^#' in cov_file: split = cov_file.split('##..#^#') if not len(split) == 2: raise "Unexpected split result" filepath = split[1][:-5].replace('#',os.path.sep) return os.path.abspath(os.path.join(root,os.path.pardir,os.path.pardir,filepath)) elif '##' in cov_file: split = cov_file.split('##') if not len(split) == 2: raise "Unexpected split result" filepath = split[1][:-5].replace('#',os.path.sep) return os.path.abspath(os.path.join(root,filepath)) elif '#' in cov_file: # wow a not broken gcov on OSX basename=os.path.basename(cov_file).replace('#',os.path.sep)[:-5] return os.path.abspath(os.path.join(root,basename)) else: raise "No source found %s" % cov_file def sanitize_path(path): """ Well fix up paths once again /usr/lib/gcc/i486-linux-gnu/4.1.2/^/^/^/^/include/c++/4.1.2/bits/stl_pair.h according to gcov '^' is a relative path, we will now build one from this one. Somehow it depends on the gcov version if .. really gets replaced to ^.... """ import os split = path.split(os.path.sep) str = "" for part in split: if part == '': str = os.path.sep elif part == '^': str = "%s..%s" % (str,os.path.sep) else: str = "%s%s%s" % (str,part,os.path.sep) return os.path.abspath(str) gcov = {} for root, dirs, files in os.walk(base_path): if ".svn" in root: continue for file in files: base,ext = os.path.splitext(file) if ext in [".gcov"]: try: cov = os.path.join(root, file) src = find_source_file( root, cov ) src = sanitize_path( src ) if not src in gcov: gcov[src] = [] gcov[src].append( cov ) except Exception,e: print "Exception on ", e #import sys #sys.exit(0) pass #print gcov return gcov def generate_covs(candidates): """ Generate gcov files in the right directory candidtaes contains the directories we have used when building. Each directory contains a set of files we will try to generate gcov files for. """ print candidates.keys() for dir in candidates.keys(): print "Trying in %s" % (dir) for dep in candidates[dir].keys(): cmd = "cd %s; gcov -p -l %s" % (dir, dep) os.system("%s > /dev/null 2>&1 " % cmd) def analyze_coverage(sources,data,dirs,runid,base): """ sources actual source files relative to src_dir e.g kdelibs/kdecore/klibloader.cpp data Where to put the stuff dirs Where to take a look for gcov files base The base directory for files. All files not inside base will be ignored """ import cov print base gcov = collect_cov(base,dirs) result = cov.analyze_coverage(gcov, sources, runid, data, base) print result if __name__ == "__main__": #global targets if not len(sys.argv) == 3: print "This script needs three parameters" print "Call it with generate_cov RUNID ResultsDir" sys.exit(-1) runid = sys.argv[1] results = sys.argv[2] # create directories for out result mkdirhier(results) print "Collection Sources and preparing data tree" base_dir = os.path.abspath(os.path.curdir) depends = collect_depends(base_dir) candidates = map(lambda x: parse_dependency_file(x,base_dir,[]), depends) # Build a number of sources from the candidates. This is a Set for the poor # Two level dict. One for dirs = {} files = {} for (_,dir,deps) in candidates: if not dir in dirs: dirs[dir] = {} for dep in deps: if not dep in dirs[dir]: dirs[dir][dep] = dep if not dep in files: files[dep] = dep sources = files.keys() print "Found %d candidates" % (len(sources)) print "Will run inefficient generation of gcov files now" generate_covs(dirs) print "Analyzing Gcov" analyze_coverage(sources, results, dirs.keys(), runid, base_dir) print "Done"