3 # Copyright (C) 2004, 2005, 2006 Nathaniel Smith
4 # Copyright (C) 2007 Holger Hans Peter Freyther
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
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.
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.
31 # HTML output inspired by the output of lcov as found on the GStreamer
32 # site. I assume this is not copyrightable.
37 # Read all CSV files and
38 # Create an overview file
52 os
.environ
["TTFPATH"] = ":".join(["/usr/share/fonts/truetype/" + d
53 for d
in "ttf-bitstream-vera",
60 def copy_files(dest_dir
):
62 Copy the CSS and the png's to the destination directory
64 images
= ["amber.png", "emerald.png", "glass.png", "ruby.png", "snow.png"]
66 (base_path
, name
) = os
.path
.split(__file__
)
67 base_path
= os
.path
.abspath(base_path
)
69 shutil
.copyfile(os
.path
.join(base_path
,css
), os
.path
.join(dest_dir
,css
))
70 map(lambda x
: shutil
.copyfile(os
.path
.join(base_path
,x
), os
.path
.join(dest_dir
,x
)), images
)
73 return "%.2f%% (%s/%s)" % (cov
[1] * 100.0 / (cov
[0] or 1), cov
[1], cov
[0])
75 def create_page(dest_dir
, name
):
76 index
= open(os
.path
.join(dest_dir
, name
), "w")
79 <TITLE>WebKit test coverage information</TITLE>
80 <link rel="stylesheet" type="text/css" href="gcov.css">
86 def generate_header(file, last_time
, total_lines
, total_executed
, path
, image
):
88 date
= time
.strftime("%Y-%m-%d %H:%M:%S", time
.gmtime(last_time
))
89 covered_lines
= sumcov((total_lines
, total_executed
))
91 file.write("""<table width="100%%" border=0 cellspacing=0 cellpadding=0>
92 <tr><td class="title">GCOV code coverage report</td></tr>
93 <tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
97 <table cellpadding=1 border=0 width="100%%">
99 <td class="headerItem" width="20%%">Current view:</td>
100 <td class="headerValue" width="80%%" colspan=4>%(path)s</td>
103 <td class="headerItem" width="20%%">Test:</td>
104 <td class="headerValue" width="80%%" colspan=4>%(product)s</td>
107 <td class="headerItem" width="20%%">Date:</td>
108 <td class="headerValue" width="20%%">%(date)s</td>
109 <td width="20%%"></td>
110 <td class="headerItem" width="20%%">Instrumented lines:</td>
111 <td class="headerValue" width="20%%">%(total_lines)s</td>
114 <td class="headerItem" width="20%%">Code covered:</td>
115 <td class="headerValue" width="20%%">%(covered_lines)s</td>
116 <td width="20%%"></td>
117 <td class="headerItem" width="20%%">Executed lines:</td>
118 <td class="headerValue" width="20%%">%(total_executed)s</td>
123 <tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
124 </table>""" % vars())
125 # disabled for now <tr><td><img src="%(image)s"></td></tr>
127 def generate_table_item(file, name
, total_lines
, covered_lines
):
128 covered_precise
= (covered_lines
*100.0)/(total_lines
or 1.0)
129 covered
= int(round(covered_precise
))
130 remainder
= 100-covered
131 (image
,perClass
,numClass
) = coverage_icon(covered_precise
)
132 site
= "%s.html" % name
.replace(os
.path
.sep
,'__')
135 <td class="coverFile"><a href="%(site)s">%(name)s</a></td>
136 <td class="coverBar" align="center">
137 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="%(image)s" width=%(covered)s height=10 alt="%(covered_precise).2f"><img src="snow.png" width=%(remainder)s height=10 alt="%(covered_precise).2f"></td></tr></table>
139 <td class="%(perClass)s">%(covered_precise).2f %%</td>
140 <td class="%(numClass)s">%(covered_lines)s / %(total_lines)s lines</td>
144 def generate_table_header_start(file):
145 file.write("""<center>
146 <table width="80%%" cellpadding=2 cellspacing=1 border=0>
149 <td width="50%%"><br></td>
150 <td width="15%%"></td>
151 <td width="15%%"></td>
152 <td width="20%%"></td>
156 <td class="tableHead">Directory name</td>
157 <td class="tableHead" colspan=3>Coverage</td>
161 def coverage_icon(percent
):
162 if percent
< level_LOW
:
163 return ("ruby.png", "coverPerLo", "coverNumLo")
164 elif percent
< level_MEDIUM
:
165 return ("amber.png", "coverPerMed", "coverNumMed")
167 return ("emerald.png", "coverPerHi", "coverNumHi")
169 def replace(text
, *pairs
):
171 From pydoc... almost identical at least
173 from string
import split
, join
176 text
= join(split(text
, a
), b
)
182 Escape string to be conform HTML
189 def generate_table_header_end(file):
190 file.write("""</table>
193 def write_title_page(dest_dir
, last_time
, last_tot_lines
, last_tot_covered
, dir_series
):
195 Write the index.html with a overview of each directory
197 index
= create_page(dest_dir
, "index.html")
198 generate_header(index
, last_time
, last_tot_lines
, last_tot_covered
, "directory", "images/Total.png")
199 # Create the directory overview
200 generate_table_header_start(index
)
201 dirs
= dir_series
.keys()
204 (dir_files
, total_lines
, covered_lines
,_
) = dir_series
[dir][-1]
205 generate_table_item(index
, dir, total_lines
, covered_lines
)
206 generate_table_header_end(index
)
208 index
.write("""</BODY></HTML>""")
211 def write_directory_site(dest_dir
, dir_name
, last_time
, dir_series
, file_series
):
212 escaped_dir
= dir_name
.replace(os
.path
.sep
,'__')
213 site
= create_page(dest_dir
, "%s.html" % escaped_dir
)
214 (_
,tot_lines
,tot_covered
,files
) = dir_series
[dir_name
][-1]
215 generate_header(site
, last_time
, tot_lines
, tot_covered
, "directory - %s" % dir_name
, "images/%s.png" % escaped_dir
)
219 generate_table_header_start(site
)
221 (lines
,covered
) = file_series
[file][-1]
222 generate_table_item(site
, file, lines
, covered
)
224 generate_table_header_end(site
)
225 site
.write("""</BODY></HTML>""")
228 def write_file_site(dest_dir
, file_name
, last_time
, data_dir
, last_id
, file_series
):
229 escaped_name
= file_name
.replace(os
.path
.sep
,'__')
230 site
= create_page(dest_dir
, "%s.html" % escaped_name
)
231 (tot_lines
,tot_covered
) = file_series
[file_name
][-1]
232 generate_header(site
, last_time
, tot_lines
, tot_covered
, "file - %s" % file_name
, "images/%s.png" % escaped_name
)
234 path
= "%s/%s.annotated%s" % (data_dir
,last_id
,file_name
)
236 # In contrast to the lcov we want to show files that have been compiled
237 # but have not been tested at all. This means we have sourcefiles with 0
238 # lines covered in the path but they are not lcov files.
239 # To identify them we check the first line now. If we see that we can
243 file = open(path
, "r")
246 all_lines
= file.read().split("\n")
248 # Convert the gcov file to HTML if we have a chanche to do so
249 # Scan each line and see if it was covered or not and escape the
251 if len(all_lines
) == 0 or not "-: 0:Source:" in all_lines
[0]:
252 site
.write("<p>The file was not excercised</p>")
254 site
.write("""</br><table cellpadding=0 cellspacing=0 border=0>
259 <td><pre class="source">
261 for line
in all_lines
:
262 split_line
= line
.split(':',2)
264 if len(split_line
) == 1:
266 line_number
= split_line
[1].strip()
267 if line_number
== "0":
271 if "#####" in split_line
[0]:
272 covered
= '<span class="lineNoCov">%15s' % "0"
274 elif split_line
[0].strip() != "-":
275 covered
= '<span class="lineCov">%15s' % split_line
[0].strip()
278 escaped_line
= escape(split_line
[2])
279 str = '<span class="lineNum">%(line_number)10s </span>%(covered)s: %(escaped_line)s%(end)s\n' % vars()
281 site
.write("</pre></td></tr></table>")
282 site
.write("</BODY></HTML>")
285 def main(progname
, args
):
287 sys
.exit("Usage: %s DATADIR OUTDIR" % progname
)
289 branch
= "WebKit from trunk"
290 datadir
, outdir
= args
292 # First, load in all data from the data directory.
294 for datapath
in glob
.glob(os
.path
.join(datadir
, "*.csv")):
295 data
.append(read_csv(datapath
))
299 # Calculate time series for each file.
300 times
= [sample
[0] for sample
in data
]
301 times
= [datetime
.datetime
.utcfromtimestamp(t
) for t
in times
]
306 t
, i
, tot_line
, tot_cover
, per_file
, per_dir
= sample
307 all_files
.update(per_file
)
308 all_dirs
.update(per_dir
)
310 file_serieses
= dict([[k
, [(0, 0)] * len(times
)] for k
in all_files
.keys()])
311 dir_serieses
= dict([[k
, [(0, 0, 0, [])] * len(times
)] for k
in all_dirs
.keys()])
314 t
, i
, tot_line
, tot_cover
, per_file
, per_dir
= sample
315 total_series
.append([tot_line
, tot_cover
])
316 for f
, covinfo
in per_file
.items():
317 file_serieses
[f
][data_idx
] = covinfo
318 for f
, covinfo
in per_dir
.items():
319 dir_serieses
[f
][data_idx
] = covinfo
323 # Okay, ready to start outputting. First make sure our directories
325 if not os
.path
.exists(outdir
):
327 rel_imgdir
= "images"
328 imgdir
= os
.path
.join(outdir
, rel_imgdir
)
329 if not os
.path
.exists(imgdir
):
333 # And look up the latest revision id, and coverage information
334 last_time
, last_id
, last_tot_lines
, last_tot_covered
= data
[-1][:4]
336 # Now start generating our html file
338 write_title_page(outdir
, last_time
, last_tot_lines
, last_tot_covered
, dir_serieses
)
340 dir_keys
= dir_serieses
.keys()
342 for dir_name
in dir_keys
:
343 write_directory_site(outdir
, dir_name
, last_time
, dir_serieses
, file_serieses
)
345 file_keys
= file_serieses
.keys()
346 for file_name
in file_keys
:
347 write_file_site(outdir
, file_name
, last_time
, datadir
, last_id
, file_serieses
)
350 r
= csv
.reader(open(path
, "r"))
351 # First line is id, time
355 time
= int(float(time_str
))
356 # Rest of lines are path, total_lines, covered_lines
359 grand_total_lines
, grand_covered_lines
= 0, 0
361 path
, total_lines_str
, covered_lines_str
= row
362 total_lines
= int(total_lines_str
)
363 covered_lines
= int(covered_lines_str
)
364 grand_total_lines
+= total_lines
365 grand_covered_lines
+= covered_lines
366 per_file
[path
] = [total_lines
, covered_lines
]
368 # Update dir statistics
369 dirname
= os
.path
.dirname(path
)
370 if not dirname
in per_dir
:
371 per_dir
[dirname
] = (0,0,0,[])
372 (dir_files
,dir_total_lines
,dir_covered_lines
, files
) = per_dir
[dirname
]
374 dir_total_lines
+= total_lines
375 dir_covered_lines
+= covered_lines
377 per_dir
[dirname
] = (dir_files
,dir_total_lines
,dir_covered_lines
,files
)
378 return [time
, id, grand_total_lines
, grand_covered_lines
, per_file
, per_dir
]
380 if __name__
== "__main__":
382 main(sys
.argv
[0], sys
.argv
[1:])