1#!/usr/bin/env python 2 3# Copyright (C) 2004, 2005, 2006 Nathaniel Smith 4# Copyright (C) 2007 Holger Hans Peter Freyther 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 10# 1. Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# 2. Redistributions in binary form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in the 14# documentation and/or other materials provided with the distribution. 15# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 16# its contributors may be used to endorse or promote products derived 17# from this software without specific prior written permission. 18# 19# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30import os, sys 31 32# from BitBake 33def mkdirhier(dir): 34 """Create a directory like 'mkdir -p', but does not complain if 35 directory already exists like os.makedirs 36 """ 37 try: 38 os.makedirs(dir) 39 except OSError, e: 40 if e.errno != 17: raise e 41 42def collect_base(src,match_array): 43 """ 44 Collect all files that match the match_array. 45 """ 46 47 sources = [] 48 for root, dirs, files in os.walk(src): 49 if ".svn" in root: 50 continue 51 52 for file in files: 53 base,ext = os.path.splitext(file) 54 if ext in match_array: 55 sources.append( os.path.join(root, file) ) 56 57 return sources 58 59def collect_depends(src): 60 return collect_base(src, [".d"]) 61 62def parse_dependency_file(src, base_dir, black_list): 63 """ 64 Parse the .d files of the gcc 65 66 Wow, the first time os.path.join is doing the right thing. We might 67 have a relative path in the depends using os.path.join(dirname of .d, dep) 68 we will end up in 69 """ 70 file = open(src) 71 file = file.read() 72 file = file.replace('\\', '').replace('\n', '') 73 74 # We now have object: dependencies splitted 75 ar = file.split(':', 1) 76 obj = ar[0].strip() 77 dir = os.path.dirname(obj) 78 deps = ar[1].split(' ') 79 80 # Remove files outside WebKit, make path absolute 81 deps = filter(lambda x: base_dir in x, deps) 82 deps = map(lambda x: os.path.abspath(os.path.join(dir, x)), deps) 83 return (obj, dir, deps) 84 85def collect_cov(base_path,targets): 86 """ 87 Collect gcov files, collect_sources is not used as it also creates 88 dirs and needs to do substituting. 89 Actually we will build a mapping from source file to gcov files of 90 interest. This is because we could have bytestream.h in many different 91 subdirectories. And we would endup with bla.cpp##bytestream.h and we 92 do not know which bytestream file was tested 93 """ 94 def find_source_file(root,cov_file): 95 """ Find a Source line or crash 96 97 '#Users#ich#projekte#src#threadmessage.cpp###space#dports#include#qt3#qstring.h.gcov' 98 '#Users#ich#projekte#src#threadmessage.cpp##..#^#src#threadmessage.cpp.gcov' 99 100 ### is absolute path 101 ##..#^# is relative path... well a gcov bug as well 102 ## normal split file in the same directory 103 """ 104 if '###' in cov_file: 105 split = cov_file.split('###') 106 if not len(split) == 2: 107 raise "Unexpected split result" 108 filepath = split[1][:-5].replace('#',os.path.sep) 109 return os.path.join(os.path.sep,filepath) 110 elif '##..#^#' in cov_file: 111 split = cov_file.split('##..#^#') 112 if not len(split) == 2: 113 raise "Unexpected split result" 114 filepath = split[1][:-5].replace('#',os.path.sep) 115 return os.path.abspath(os.path.join(root,os.path.pardir,os.path.pardir,filepath)) 116 elif '##' in cov_file: 117 split = cov_file.split('##') 118 if not len(split) == 2: 119 raise "Unexpected split result" 120 filepath = split[1][:-5].replace('#',os.path.sep) 121 return os.path.abspath(os.path.join(root,filepath)) 122 elif '#' in cov_file: 123 # wow a not broken gcov on OSX 124 basename=os.path.basename(cov_file).replace('#',os.path.sep)[:-5] 125 return os.path.abspath(os.path.join(root,basename)) 126 127 else: 128 raise "No source found %s" % cov_file 129 130 def sanitize_path(path): 131 """ 132 Well fix up paths once again /usr/lib/gcc/i486-linux-gnu/4.1.2/^/^/^/^/include/c++/4.1.2/bits/stl_pair.h 133 according to gcov '^' is a relative path, we will now build one from this one. Somehow it depends 134 on the gcov version if .. really gets replaced to ^.... 135 """ 136 import os 137 split = path.split(os.path.sep) 138 str = "" 139 for part in split: 140 if part == '': 141 str = os.path.sep 142 elif part == '^': 143 str = "%s..%s" % (str,os.path.sep) 144 else: 145 str = "%s%s%s" % (str,part,os.path.sep) 146 return os.path.abspath(str) 147 148 149 gcov = {} 150 for root, dirs, files in os.walk(base_path): 151 if ".svn" in root: 152 continue 153 for file in files: 154 base,ext = os.path.splitext(file) 155 if ext in [".gcov"]: 156 try: 157 cov = os.path.join(root, file) 158 src = find_source_file( root, cov ) 159 src = sanitize_path( src ) 160 161 if not src in gcov: 162 gcov[src] = [] 163 gcov[src].append( cov ) 164 except Exception,e: 165 print "Exception on ", e 166 #import sys 167 #sys.exit(0) 168 pass 169 170 #print gcov 171 return gcov 172 173def generate_covs(candidates): 174 """ 175 Generate gcov files in the right directory 176 177 candidtaes contains the directories we have used when 178 building. Each directory contains a set of files we will 179 try to generate gcov files for. 180 """ 181 print candidates.keys() 182 for dir in candidates.keys(): 183 print "Trying in %s" % (dir) 184 for dep in candidates[dir].keys(): 185 cmd = "cd %s; gcov -p -l %s" % (dir, dep) 186 os.system("%s > /dev/null 2>&1 " % cmd) 187 188 189def analyze_coverage(sources,data,dirs,runid,base): 190 """ 191 sources actual source files relative to src_dir e.g kdelibs/kdecore/klibloader.cpp 192 data Where to put the stuff 193 dirs Where to take a look for gcov files 194 base The base directory for files. All files not inside base will be ignored 195 """ 196 import cov 197 print base 198 gcov = collect_cov(base,dirs) 199 result = cov.analyze_coverage(gcov, sources, runid, data, base) 200 print result 201 202if __name__ == "__main__": 203 #global targets 204 if not len(sys.argv) == 3: 205 print "This script needs three parameters" 206 print "Call it with generate_cov RUNID ResultsDir" 207 sys.exit(-1) 208 runid = sys.argv[1] 209 results = sys.argv[2] 210 211 # create directories for out result 212 mkdirhier(results) 213 214 print "Collection Sources and preparing data tree" 215 base_dir = os.path.abspath(os.path.curdir) 216 depends = collect_depends(base_dir) 217 candidates = map(lambda x: parse_dependency_file(x,base_dir,[]), depends) 218 219 # Build a number of sources from the candidates. This is a Set for the poor 220 # Two level dict. One for 221 dirs = {} 222 files = {} 223 for (_,dir,deps) in candidates: 224 if not dir in dirs: 225 dirs[dir] = {} 226 for dep in deps: 227 if not dep in dirs[dir]: 228 dirs[dir][dep] = dep 229 if not dep in files: 230 files[dep] = dep 231 232 sources = files.keys() 233 234 print "Found %d candidates" % (len(sources)) 235 print "Will run inefficient generation of gcov files now" 236 generate_covs(dirs) 237 238 print "Analyzing Gcov" 239 analyze_coverage(sources, results, dirs.keys(), runid, base_dir) 240 print "Done" 241