|
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 |
|
30 import os, sys |
|
31 |
|
32 # from BitBake |
|
33 def 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 |
|
42 def 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 |
|
59 def collect_depends(src): |
|
60 return collect_base(src, [".d"]) |
|
61 |
|
62 def 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 |
|
85 def 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 |
|
173 def 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 |
|
189 def 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 |
|
202 if __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" |