Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(230)

Side by Side Diff: tools/code_coverage/croc_html.py

Issue 113980: Major refactoring of Croc.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 11 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/usr/bin/python2.4
John Grabowski 2009/05/29 00:34:43 /usr/bin/env python? Non-Google machines won't nec
2 #
3 # Copyright 2009, Google Inc.
John Grabowski 2009/05/29 00:34:43 (c) the Chromium authors?
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
8 # met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following disclaimer
14 # in the documentation and/or other materials provided with the
15 # distribution.
16 # * Neither the name of Google Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived from
18 # this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 """Crocodile HTML output."""
33
34 import os
35 import shutil
36 import time
37 import xml.dom
38
39
40 class CrocHtmlError(Exception):
41 """Coverage HTML error."""
42
43
44 class HtmlElement(object):
45 """Node in a HTML file."""
46
47 def __init__(self, doc, element):
48 """Constructor.
49
50 Args:
51 doc: XML document object.
52 element: XML element.
53 """
54 self.doc = doc
55 self.element = element
56
57 def E(self, name, **kwargs):
58 """Adds a child element.
59
60 Args:
61 name: Name of element.
62 kwargs: Attributes for element. To use an attribute which is a python
63 reserved word (i.e. 'class'), prefix the attribute name with 'e_'.
64
65 Returns:
66 The child element.
67 """
68 he = HtmlElement(self.doc, self.doc.createElement(name))
69 element = he.element
70 self.element.appendChild(element)
71
72 for k, v in kwargs.iteritems():
73 if k.startswith('e_'):
74 # Remove prefix
75 element.setAttribute(k[2:], str(v))
76 else:
77 element.setAttribute(k, str(v))
78
79 return he
80
81 def Text(self, text):
82 """Adds a text node.
83
84 Args:
85 text: Text to add.
86
87 Returns:
88 self.
89 """
90 t = self.doc.createTextNode(str(text))
91 self.element.appendChild(t)
92 return self
93
94
95 class HtmlFile(object):
96 """HTML file."""
97
98 def __init__(self, xml_impl, filename):
99 """Constructor.
100
101 Args:
102 xml_impl: DOMImplementation to use to create document.
103 filename: Path to file.
104 """
105 self.xml_impl = xml_impl
106 doctype = xml_impl.createDocumentType(
107 'HTML', '-//W3C//DTD HTML 4.01//EN',
108 'http://www.w3.org/TR/html4/strict.dtd')
109 self.doc = xml_impl.createDocument(None, 'html', doctype)
110 self.filename = filename
111
112 # Create head and body elements
113 root = HtmlElement(self.doc, self.doc.documentElement)
114 self.head = root.E('head')
115 self.body = root.E('body')
116
117 def Write(self, cleanup=True):
118 """Writes the file.
119
120 Args:
121 cleanup: If True, calls unlink() on the internal xml document. This
122 frees up memory, but means that you can't use this file for anything
123 else.
124 """
125 f = open(self.filename, 'wt')
126 self.doc.writexml(f, encoding='UTF-8')
127 f.close()
128
129 if cleanup:
130 self.doc.unlink()
131 # Prevent future uses of the doc now that we've unlinked it
132 self.doc = None
133
134 #------------------------------------------------------------------------------
135
136 COV_TYPE_STRING = {None: 'm', 0: 'i', 1: 'E', 2: ' '}
137 COV_TYPE_CLASS = {None: 'missing', 0: 'instr', 1: 'covered', 2: ''}
138
139
140 class CrocHtml(object):
141 """Crocodile HTML output class."""
142
143 def __init__(self, cov, output_root):
144 """Constructor."""
145 self.cov = cov
146 self.output_root = output_root
147 self.xml_impl = xml.dom.getDOMImplementation()
148 self.time_string = 'Coverage information generated %s.' % time.asctime()
149
150 def CreateHtmlDoc(self, filename, title):
151 """Creates a new HTML document.
152
153 Args:
154 filename: Filename to write to, relative to self.output_root.
155 title: Title of page
156
157 Returns:
158 The document.
159 """
160 f = HtmlFile(self.xml_impl, self.output_root + '/' + filename)
161
162 f.head.E('title').Text(title)
163 f.head.E(
164 'link', rel='stylesheet', type='text/css',
165 href='../' * (len(filename.split('/')) - 1) + 'croc.css')
166
167 return f
168
169 def AddCaptionForFile(self, body, path):
170 """Adds a caption for the file, with links to each parent dir.
171
172 Args:
173 body: Body elemement.
174 path: Path to file.
175 """
176 # This is slightly different that for subdir, because it needs to have a
177 # link to the current directory's index.html.
178 hdr = body.E('h2')
179 hdr.Text('Coverage for ')
180 dirs = [''] + path.split('/')
181 num_dirs = len(dirs)
182 for i in range(num_dirs - 1):
183 hdr.E('a', href=(
184 '../' * (num_dirs - i - 2) + 'index.html')).Text(dirs[i] + '/')
185 hdr.Text(dirs[-1])
186
187 def AddCaptionForSubdir(self, body, path):
188 """Adds a caption for the subdir, with links to each parent dir.
189
190 Args:
191 body: Body elemement.
192 path: Path to subdir.
193 """
194 # Link to parent dirs
195 hdr = body.E('h2')
196 hdr.Text('Coverage for ')
197 dirs = [''] + path.split('/')
198 num_dirs = len(dirs)
199 for i in range(num_dirs - 1):
200 hdr.E('a', href=(
201 '../' * (num_dirs - i - 1) + 'index.html')).Text(dirs[i] + '/')
202 hdr.Text(dirs[-1] + '/')
203
204 def AddSectionHeader(self, table, caption, itemtype, is_file=False):
205 """Adds a section header to the coverage table.
206
207 Args:
208 table: Table to add rows to.
209 caption: Caption for section, if not None.
210 itemtype: Type of items in this section, if not None.
211 is_file: Are items in this section files?
212 """
213
214 if caption is not None:
215 table.E('tr').E('td', e_class='secdesc', colspan=8).Text(caption)
216
217 sec_hdr = table.E('tr')
218
219 if itemtype is not None:
220 sec_hdr.E('td', e_class='section').Text(itemtype)
221
222 sec_hdr.E('td', e_class='section').Text('Coverage')
223 sec_hdr.E('td', e_class='section', colspan=3).Text(
224 'Lines executed / instrumented / missing')
225
226 graph = sec_hdr.E('td', e_class='section')
227 graph.E('span', style='color:#00FF00').Text('exe')
228 graph.Text(' / ')
229 graph.E('span', style='color:#FFFF00').Text('inst')
230 graph.Text(' / ')
231 graph.E('span', style='color:#FF0000').Text('miss')
232
233 if is_file:
234 sec_hdr.E('td', e_class='section').Text('Language')
235 sec_hdr.E('td', e_class='section').Text('Group')
236 else:
237 sec_hdr.E('td', e_class='section', colspan=2)
238
239 def AddItem(self, table, itemname, stats, attrs, link=None):
240 """Adds a bar graph to the element. This is a series of <td> elements.
241
242 Args:
243 table: Table to add item to.
244 itemname: Name of item.
245 stats: Stats object.
246 attrs: Attributes dictionary; if None, no attributes will be printed.
247 link: Destination for itemname hyperlink, if not None.
248 """
249 row = table.E('tr')
250
251 # Add item name
252 if itemname is not None:
253 item_elem = row.E('td')
254 if link is not None:
255 item_elem = item_elem.E('a', href=link)
256 item_elem.Text(itemname)
257
258 # Get stats
259 stat_exe = stats.get('lines_executable', 0)
260 stat_ins = stats.get('lines_instrumented', 0)
261 stat_cov = stats.get('lines_covered', 0)
262
263 percent = row.E('td')
264
265 # Add text
266 row.E('td', e_class='number').Text(stat_cov)
267 row.E('td', e_class='number').Text(stat_ins)
268 row.E('td', e_class='number').Text(stat_exe - stat_ins)
269
270 # Add percent and graph; only fill in if there's something in there
271 graph = row.E('td', e_class='graph', width=100)
272 if stat_exe:
273 percent_cov = 100.0 * stat_cov / stat_exe
274 percent_ins = 100.0 * stat_ins / stat_exe
275
276 # Color percent based on thresholds
277 percent.Text('%.1f%%' % percent_cov)
278 if percent_cov >= 80:
279 percent.element.setAttribute('class', 'high_pct')
280 elif percent_cov >= 60:
281 percent.element.setAttribute('class', 'mid_pct')
282 else:
283 percent.element.setAttribute('class', 'low_pct')
284
285 # Graphs use integer values
286 percent_cov = int(percent_cov)
287 percent_ins = int(percent_ins)
288
289 graph.Text('.')
290 graph.E('span', style='padding-left:%dpx' % percent_cov,
291 e_class='g_covered')
292 graph.E('span', style='padding-left:%dpx' % (percent_ins - percent_cov),
293 e_class='g_instr')
294 graph.E('span', style='padding-left:%dpx' % (100 - percent_ins),
295 e_class='g_missing')
296
297 if attrs:
298 row.E('td', e_class='stat').Text(attrs.get('language'))
299 row.E('td', e_class='stat').Text(attrs.get('group'))
300 else:
301 row.E('td', colspan=2)
302
303 def WriteFile(self, cov_file):
304 """Writes the HTML for a file.
305
306 Args:
307 cov_file: croc.CoveredFile to write.
308 """
309 print ' ' + cov_file.filename
310 title = 'Coverage for ' + cov_file.filename
311
312 f = self.CreateHtmlDoc(cov_file.filename + '.html', title)
313 body = f.body
314
315 # Write header section
316 self.AddCaptionForFile(body, cov_file.filename)
317
318 # Summary for this file
319 table = body.E('table')
320 self.AddSectionHeader(table, None, None, is_file=True)
321 self.AddItem(table, None, cov_file.stats, cov_file.attrs)
322
323 body.E('h2').Text('Line-by-line coverage:')
324
325 # Print line-by-line coverage
326 if cov_file.local_path:
327 code_table = body.E('table').E('tr').E('td').E('pre')
328
329 flines = open(cov_file.local_path, 'rt')
330 lineno = 0
331
332 for line in flines:
333 lineno += 1
334 line_cov = cov_file.lines.get(lineno, 2)
335 e_class = COV_TYPE_CLASS.get(line_cov)
336
337 code_table.E('span', e_class=e_class).Text('%4d %s : %s\n' % (
338 lineno,
339 COV_TYPE_STRING.get(line_cov),
340 line.rstrip()
341 ))
342
343 else:
344 body.Text('Line-by-line coverage not available. Make sure the directory'
345 ' containing this file has been scanned via ')
346 body.E('B').Text('add_files')
347 body.Text(' in a configuration file, or the ')
348 body.E('B').Text('--addfiles')
349 body.Text(' command line option.')
350
351 # TODO: if file doesn't have a local path, try to find it by
352 # reverse-mapping roots and searching for the file.
353
354 body.E('p', e_class='time').Text(self.time_string)
355 f.Write()
356
357 def WriteSubdir(self, cov_dir):
358 """Writes the index.html for a subdirectory.
359
360 Args:
361 cov_dir: croc.CoveredDir to write.
362 """
363 print ' ' + cov_dir.dirpath + '/'
364
365 # Create the subdir if it doesn't already exist
366 subdir = self.output_root + '/' + cov_dir.dirpath
367 if not os.path.exists(subdir):
368 os.mkdir(subdir)
369
370 if cov_dir.dirpath:
371 title = 'Coverage for ' + cov_dir.dirpath + '/'
372 f = self.CreateHtmlDoc(cov_dir.dirpath + '/index.html', title)
373 else:
374 title = 'Coverage summary'
375 f = self.CreateHtmlDoc('index.html', title)
376
377 body = f.body
378
379 # Write header section
380 if cov_dir.dirpath:
381 self.AddCaptionForSubdir(body, cov_dir.dirpath)
382 else:
383 body.E('h2').Text(title)
384
385 table = body.E('table')
386
387 # Coverage by group
388 self.AddSectionHeader(table, 'Coverage by Group', 'Group')
389
390 for group in sorted(cov_dir.stats_by_group):
391 self.AddItem(table, group, cov_dir.stats_by_group[group], None)
392
393 # List subdirs
394 if cov_dir.subdirs:
395 self.AddSectionHeader(table, 'Subdirectories', 'Subdirectory')
396
397 for d in sorted(cov_dir.subdirs):
398 self.AddItem(table, d + '/', cov_dir.subdirs[d].stats_by_group['all'],
399 None, link=d + '/index.html')
400
401 # List files
402 if cov_dir.files:
403 self.AddSectionHeader(table, 'Files in This Directory', 'Filename',
404 is_file=True)
405
406 for filename in sorted(cov_dir.files):
407 cov_file = cov_dir.files[filename]
408 self.AddItem(table, filename, cov_file.stats, cov_file.attrs,
409 link=filename + '.html')
410
411 body.E('p', e_class='time').Text(self.time_string)
412 f.Write()
413
414 def WriteRoot(self):
415 """Writes the files in the output root."""
416 # Find ourselves
417 src_dir = os.path.split(self.WriteRoot.func_code.co_filename)[0]
418
419 # Files to copy into output root
420 copy_files = [
421 'croc.css',
422 ]
423
424 # Copy files from our directory into the output directory
425 for copy_file in copy_files:
426 print ' Copying %s' % copy_file
427 shutil.copyfile(os.path.join(src_dir, copy_file),
428 os.path.join(self.output_root, copy_file))
429
430 def Write(self):
431 """Writes HTML output."""
432
433 print 'Writing HTML to %s...' % self.output_root
434
435 # Loop through the tree and write subdirs, breadth-first
436 # TODO: switch to depth-first and sort values - makes nicer output?
437 todo = [self.cov.tree]
438 while todo:
439 cov_dir = todo.pop(0)
440
441 # Append subdirs to todo list
442 todo += cov_dir.subdirs.values()
443
444 # Write this subdir
445 self.WriteSubdir(cov_dir)
446
447 # Write files in this subdir
448 for cov_file in cov_dir.files.itervalues():
449 self.WriteFile(cov_file)
450
451 # Write files in root directory
452 self.WriteRoot()
453
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698