1 """Code-coverage tools for CherryPy.
2
3 To use this module, or the coverage tools in the test suite,
4 you need to download 'coverage.py', either Gareth Rees' original
5 implementation:
6 http://www.garethrees.org/2001/12/04/python-coverage/
7
8 or Ned Batchelder's enhanced version:
9 http://www.nedbatchelder.com/code/modules/coverage.html
10
11 To turn on coverage tracing, use the following code:
12
13 cherrypy.engine.on_start_engine_list.insert(0, covercp.start)
14 cherrypy.engine.on_start_thread_list.insert(0, covercp.start)
15
16 Run your code, then use the covercp.serve() function to browse the
17 results in a web browser. If you run this module from the command line,
18 it will call serve() for you.
19 """
20
21 import re
22 import sys
23 import cgi
24 import urllib
25 import os, os.path
26 localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
27
28 try:
29 import cStringIO as StringIO
30 except ImportError:
31 import StringIO
32
33 try:
34 from coverage import the_coverage as coverage
35 - def start(threadid=None):
37 except ImportError:
38
39
40 coverage = None
41
42 import warnings
43 warnings.warn("No code coverage will be performed; coverage.py could not be imported.")
44
45 - def start(threadid=None):
47
48
49 import cherrypy
50 initial_base = os.path.dirname(cherrypy.__file__)
51
52 TEMPLATE_MENU = """<html>
53 <head>
54 <title>CherryPy Coverage Menu</title>
55 <style>
56 body {font: 9pt Arial, serif;}
57 #tree {
58 font-size: 8pt;
59 font-family: Andale Mono, monospace;
60 white-space: pre;
61 }
62 #tree a:active, a:focus {
63 background-color: black;
64 padding: 1px;
65 color: white;
66 border: 0px solid #9999FF;
67 -moz-outline-style: none;
68 }
69 .fail { color: red;}
70 .pass { color: #888;}
71 #pct { text-align: right;}
72 h3 {
73 font-size: small;
74 font-weight: bold;
75 font-style: italic;
76 margin-top: 5px;
77 }
78 input { border: 1px solid #ccc; padding: 2px; }
79 .directory {
80 color: #933;
81 font-style: italic;
82 font-weight: bold;
83 font-size: 10pt;
84 }
85 .file {
86 color: #400;
87 }
88 a { text-decoration: none; }
89 #crumbs {
90 color: white;
91 font-size: 8pt;
92 font-family: Andale Mono, monospace;
93 width: 100%;
94 background-color: black;
95 }
96 #crumbs a {
97 color: #f88;
98 }
99 #options {
100 line-height: 2.3em;
101 border: 1px solid black;
102 background-color: #eee;
103 padding: 4px;
104 }
105 #exclude {
106 width: 100%;
107 margin-bottom: 3px;
108 border: 1px solid #999;
109 }
110 #submit {
111 background-color: black;
112 color: white;
113 border: 0;
114 margin-bottom: -9px;
115 }
116 </style>
117 </head>
118 <body>
119 <h2>CherryPy Coverage</h2>"""
120
121 TEMPLATE_FORM = """
122 <div id="options">
123 <form action='menu' method=GET>
124 <input type='hidden' name='base' value='%(base)s' />
125 Show percentages <input type='checkbox' %(showpct)s name='showpct' value='checked' /><br />
126 Hide files over <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br />
127 Exclude files matching<br />
128 <input type='text' id='exclude' name='exclude' value='%(exclude)s' size='20' />
129 <br />
130
131 <input type='submit' value='Change view' id="submit"/>
132 </form>
133 </div>"""
134
135 TEMPLATE_FRAMESET = """<html>
136 <head><title>CherryPy coverage data</title></head>
137 <frameset cols='250, 1*'>
138 <frame src='menu?base=%s' />
139 <frame name='main' src='' />
140 </frameset>
141 </html>
142 """ % initial_base.lower()
143
144 TEMPLATE_COVERAGE = """<html>
145 <head>
146 <title>Coverage for %(name)s</title>
147 <style>
148 h2 { margin-bottom: .25em; }
149 p { margin: .25em; }
150 .covered { color: #000; background-color: #fff; }
151 .notcovered { color: #fee; background-color: #500; }
152 .excluded { color: #00f; background-color: #fff; }
153 table .covered, table .notcovered, table .excluded
154 { font-family: Andale Mono, monospace;
155 font-size: 10pt; white-space: pre; }
156
157 .lineno { background-color: #eee;}
158 .notcovered .lineno { background-color: #000;}
159 table { border-collapse: collapse;
160 </style>
161 </head>
162 <body>
163 <h2>%(name)s</h2>
164 <p>%(fullpath)s</p>
165 <p>Coverage: %(pc)s%%</p>"""
166
167 TEMPLATE_LOC_COVERED = """<tr class="covered">
168 <td class="lineno">%s </td>
169 <td>%s</td>
170 </tr>\n"""
171 TEMPLATE_LOC_NOT_COVERED = """<tr class="notcovered">
172 <td class="lineno">%s </td>
173 <td>%s</td>
174 </tr>\n"""
175 TEMPLATE_LOC_EXCLUDED = """<tr class="excluded">
176 <td class="lineno">%s </td>
177 <td>%s</td>
178 </tr>\n"""
179
180 TEMPLATE_ITEM = "%s%s<a class='file' href='report?name=%s' target='main'>%s</a>\n"
181
183 s = len(statements)
184 e = s - len(missing)
185 if s > 0:
186 return int(round(100.0 * e / s))
187 return 0
188
189 -def _show_branch(root, base, path, pct=0, showpct=False, exclude=""):
190
191
192 dirs = [k for k, v in root.iteritems() if v]
193 dirs.sort()
194 for name in dirs:
195 newpath = os.path.join(path, name)
196
197 if newpath.startswith(base):
198 relpath = newpath[len(base):]
199 yield "| " * relpath.count(os.sep)
200 yield "<a class='directory' href='menu?base=%s&exclude=%s'>%s</a>\n" % \
201 (newpath, urllib.quote_plus(exclude), name)
202
203 for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude):
204 yield chunk
205
206
207 if path.startswith(base):
208 relpath = path[len(base):]
209 files = [k for k, v in root.iteritems() if not v]
210 files.sort()
211 for name in files:
212 newpath = os.path.join(path, name)
213
214 pc_str = ""
215 if showpct:
216 try:
217 _, statements, _, missing, _ = coverage.analysis2(newpath)
218 except:
219
220 pass
221 else:
222 pc = _percent(statements, missing)
223 pc_str = ("%3d%% " % pc).replace(' ',' ')
224 if pc < float(pct) or pc == -1:
225 pc_str = "<span class='fail'>%s</span>" % pc_str
226 else:
227 pc_str = "<span class='pass'>%s</span>" % pc_str
228
229 yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1),
230 pc_str, newpath, name)
231
233 if exclude:
234 return bool(re.search(exclude, path))
235
237 d = tree
238
239 p = path
240 atoms = []
241 while True:
242 p, tail = os.path.split(p)
243 if not tail:
244 break
245 atoms.append(tail)
246 atoms.append(p)
247 if p != "/":
248 atoms.append("/")
249
250 atoms.reverse()
251 for node in atoms:
252 if node:
253 d = d.setdefault(node, {})
254
256 """Return covered module names as a nested dict."""
257 tree = {}
258 coverage.get_ready()
259 runs = coverage.cexecuted.keys()
260 if runs:
261 for path in runs:
262 if not _skip_file(path, exclude) and not os.path.isdir(path):
263 _graft(path, tree)
264 return tree
265
267
270 index.exposed = True
271
274
275
276 base = base.lower().rstrip(os.sep)
277
278 yield TEMPLATE_MENU
279 yield TEMPLATE_FORM % locals()
280
281
282 yield "<div id='crumbs'>"
283 path = ""
284 atoms = base.split(os.sep)
285 atoms.pop()
286 for atom in atoms:
287 path += atom + os.sep
288 yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s"
289 % (path, urllib.quote_plus(exclude), atom, os.sep))
290 yield "</div>"
291
292 yield "<div id='tree'>"
293
294
295 tree = get_tree(base, exclude)
296 if not tree:
297 yield "<p>No modules covered.</p>"
298 else:
299 for chunk in _show_branch(tree, base, "/", pct,
300 showpct=='checked', exclude):
301 yield chunk
302
303 yield "</div>"
304 yield "</body></html>"
305 menu.exposed = True
306
308 source = open(filename, 'r')
309 buffer = []
310 for lineno, line in enumerate(source.readlines()):
311 lineno += 1
312 line = line.strip("\n\r")
313 empty_the_buffer = True
314 if lineno in excluded:
315 template = TEMPLATE_LOC_EXCLUDED
316 elif lineno in missing:
317 template = TEMPLATE_LOC_NOT_COVERED
318 elif lineno in statements:
319 template = TEMPLATE_LOC_COVERED
320 else:
321 empty_the_buffer = False
322 buffer.append((lineno, line))
323 if empty_the_buffer:
324 for lno, pastline in buffer:
325 yield template % (lno, cgi.escape(pastline))
326 buffer = []
327 yield template % (lineno, cgi.escape(line))
328
330 coverage.get_ready()
331 filename, statements, excluded, missing, _ = coverage.analysis2(name)
332 pc = _percent(statements, missing)
333 yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name),
334 fullpath=name,
335 pc=pc)
336 yield '<table>\n'
337 for line in self.annotated_file(filename, statements, excluded,
338 missing):
339 yield line
340 yield '</table>'
341 yield '</body>'
342 yield '</html>'
343 report.exposed = True
344
345
357
358 if __name__ == "__main__":
359 serve(*tuple(sys.argv[1:]))
360