Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-2.1/cherrypy/lib/covercp.py

Revision 554 (checked in by fumanchu, 3 years ago)

Improved covercp's annotated file output: blank lines have been reinstated, but blank or other non-statement lines are marked "not covered" if the next valid statement line is marked "not covered".

Line 
1 """
2 Copyright (c) 2005, CherryPy Team (team@cherrypy.org)
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8     * Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10     * Redistributions in binary form must reproduce the above copyright notice,
11       this list of conditions and the following disclaimer in the documentation
12       and/or other materials provided with the distribution.
13     * Neither the name of the CherryPy Team nor the names of its contributors
14       may be used to endorse or promote products derived from this software
15       without specific prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 """
28
29 """Code-coverage tools for CherryPy.
30
31 To use this module, or the coverage tools in the test suite,
32 you need to download 'coverage.py', either Gareth Rees' original
33 implementation:
34 http://www.garethrees.org/2001/12/04/python-coverage/
35
36 or Ned Batchelder's enhanced version:
37 http://www.nedbatchelder.com/code/modules/coverage.html
38
39 Set "cherrypy.codecoverage = True" to turn on coverage tracing.
40 Then, use the serve() function to browse the results in a web browser.
41 If you run this module from the command line, it will call serve() for you.
42 """
43
44 import re
45 import sys
46 import cgi
47 import urllib
48 import os, os.path
49 localFile = os.path.join(os.path.dirname(__file__), "coverage.cache")
50
51 try:
52     import cStringIO as StringIO
53 except ImportError:
54     import StringIO
55
56 try:
57     from coverage import the_coverage as coverage
58     def start():
59         coverage.start()
60 except ImportError:
61     # Setting coverage to None will raise errors
62     # that need to be trapped downstream.
63     coverage = None
64    
65     import warnings
66     warnings.warn("No code coverage will be performed; coverage.py could not be imported.")
67    
68     def start():
69         pass
70
71 # Guess initial depth to hide FIXME this doesn't work for non-cherrypy stuff
72 import cherrypy
73 initial_base = os.path.dirname(cherrypy.__file__)
74
75 TEMPLATE_MENU = """<html>
76 <head>
77     <title>CherryPy Coverage Menu</title>
78     <style>
79         body {font: 9pt Arial, serif;}
80         #tree {
81             font-size: 8pt;
82             font-family: Andale Mono, monospace;
83             white-space: pre;
84             }
85         #tree a:active, a:focus {
86             background-color: black;
87             padding: 1px;
88             color: white;
89             border: 0px solid #9999FF;
90             -moz-outline-style: none;
91             }
92         .fail { color: red;}
93         .pass { color: #888;}
94         #pct { text-align: right;}
95         h3 {
96             font-size: small;
97             font-weight: bold;
98             font-style: italic;
99             margin-top: 5px;
100             }
101         input { border: 1px solid #ccc; padding: 2px; }
102         .directory {
103             color: #933;
104             font-style: italic;
105             font-weight: bold;
106             font-size: 10pt;
107             }
108         .file {
109             color: #400;
110             }
111         a { text-decoration: none; }
112         #crumbs {
113             color: white;
114             font-size: 8pt;
115             font-family: Andale Mono, monospace;
116             width: 100%;
117             background-color: black;
118             }
119         #crumbs a {
120             color: #f88;
121             }
122         #options {
123             line-height: 2.3em;
124             border: 1px solid black;
125             background-color: #eee;
126             padding: 4px;
127             }
128         #exclude {
129             width: 100%;
130             margin-bottom: 3px;
131             border: 1px solid #999;
132             }
133         #submit {
134             background-color: black;
135             color: white;
136             border: 0;
137             margin-bottom: -9px;
138             }
139     </style>
140 </head>
141 <body>
142 <h2>CherryPy Coverage</h2>"""
143
144 TEMPLATE_FORM = """
145 <div id="options">
146 <form action='menu' method=GET>
147     <input type='hidden' name='base' value='%(base)s' />
148     Show percentages <input type='checkbox' %(showpct)s name='showpct' value='checked' /><br />
149     Hide files over <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br />
150     Exclude files matching<br />
151     <input type='text' id='exclude' name='exclude' value='%(exclude)s' size='20' />
152     <br />
153
154     <input type='submit' value='Change view' id="submit"/>
155 </form>
156 </div>""" 
157
158 TEMPLATE_FRAMESET = """<html>
159 <head><title>CherryPy coverage data</title></head>
160 <frameset cols='250, 1*'>
161     <frame src='menu?base=%s' />
162     <frame name='main' src='' />
163 </frameset>
164 </html>
165 """ % initial_base.lower()
166
167 TEMPLATE_COVERAGE = """<html>
168 <head>
169     <title>Coverage for %(name)s</title>
170     <style>
171         h2 { margin-bottom: .25em; }
172         p { margin: .25em; }
173         .covered { color: #000; background-color: #fff; }
174         .notcovered { color: #fee; background-color: #500; }
175         .excluded { color: #00f; background-color: #fff; }
176          table .covered, table .notcovered, table .excluded
177              { font-family: Andale Mono, monospace;
178                font-size: 10pt; white-space: pre; }
179
180          .lineno { background-color: #eee;}
181          .notcovered .lineno { background-color: #000;}
182          table { border-collapse: collapse;
183     </style>
184 </head>
185 <body>
186 <h2>%(name)s</h2>
187 <p>%(fullpath)s</p>
188 <p>Coverage: %(pc)s%%</p>"""
189
190 TEMPLATE_LOC_COVERED = """<tr class="covered">
191     <td class="lineno">%s&nbsp;</td>
192     <td>%s</td>
193 </tr>\n"""
194 TEMPLATE_LOC_NOT_COVERED = """<tr class="notcovered">
195     <td class="lineno">%s&nbsp;</td>
196     <td>%s</td>
197 </tr>\n"""
198 TEMPLATE_LOC_EXCLUDED = """<tr class="excluded">
199     <td class="lineno">%s&nbsp;</td>
200     <td>%s</td>
201 </tr>\n"""
202
203 TEMPLATE_ITEM = "%s%s<a class='file' href='report?name=%s' target='main'>%s</a>\n"
204
205 def _percent(statements, missing):
206     s = len(statements)
207     e = s - len(missing)
208     if s > 0:
209         return int(round(100.0 * e / s))
210     return 0
211
212 def _show_branch(root, base, path, pct=0, showpct=False, exclude=""):
213    
214     # Show the directory name and any of our children
215     dirs = [k for k, v in root.iteritems() if v]
216     dirs.sort()
217     for name in dirs:
218         newpath = os.path.join(path, name)
219        
220         if newpath.startswith(base):
221             relpath = newpath[len(base):]
222             yield "| " * relpath.count(os.sep)
223             yield "<a class='directory' href='menu?base=%s&exclude=%s'>%s</a>\n" % \
224                    (newpath, urllib.quote_plus(exclude), name)
225        
226         for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude):
227             yield chunk
228    
229     # Now list the files
230     if path.startswith(base):
231         relpath = path[len(base):]
232         files = [k for k, v in root.iteritems() if not v]
233         files.sort()
234         for name in files:
235             newpath = os.path.join(path, name)
236            
237             pc_str = ""
238             if showpct:
239                 try:
240                     _, statements, _, missing, _ = coverage.analysis2(newpath)
241                 except:
242                     # Yes, we really want to pass on all errors.
243                     pass
244                 else:
245                     pc = _percent(statements, missing)
246                     pc_str = ("%3d%% " % pc).replace(' ','&nbsp;')
247                     if pc < float(pct) or pc == -1:
248                         pc_str = "<span class='fail'>%s</span>" % pc_str
249                     else:
250                         pc_str = "<span class='pass'>%s</span>" % pc_str
251            
252             yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1),
253                                    pc_str, newpath, name)
254
255 def _skip_file(path, exclude):
256     if exclude:
257         return bool(re.search(exclude, path))
258
259 def _graft(path, tree):
260     d = tree
261    
262     p = path
263     atoms = []
264     while True:
265         p, tail = os.path.split(p)
266         if not tail:
267             break
268         atoms.append(tail)
269     atoms.append(p)
270     if p != "/":
271         atoms.append("/")
272    
273     atoms.reverse()
274     for node in atoms:
275         if node:
276             d = d.setdefault(node, {})
277
278 def get_tree(base, exclude):
279     """Return covered module names as a nested dict."""
280     tree = {}
281     coverage.get_ready()
282     runs = coverage.cexecuted.keys()
283     if runs:
284         for path in runs:
285             if not _skip_file(path, exclude) and not os.path.isdir(path):
286                 _graft(path, tree)
287     return tree
288
289 class CoverStats(object):
290    
291     def index(self):
292         return TEMPLATE_FRAMESET
293     index.exposed = True
294    
295     def menu(self, base="/", pct="50", showpct="",
296              exclude=r'python\d\.\d|test|tut\d|tutorial'):
297        
298         # The coverage module uses all-lower-case names.
299         base = base.lower().rstrip(os.sep)
300        
301         yield TEMPLATE_MENU
302         yield TEMPLATE_FORM % locals()
303        
304         # Start by showing links for parent paths
305         yield "<div id='crumbs'>"
306         path = ""
307         atoms = base.split(os.sep)
308         atoms.pop()
309         for atom in atoms:
310             path += atom + os.sep
311             yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s"
312                    % (path, urllib.quote_plus(exclude), atom, os.sep))
313         yield "</div>"
314        
315         yield "<div id='tree'>"
316        
317         # Then display the tree
318         tree = get_tree(base, exclude)
319         if not tree:
320             yield "<p>No modules covered.</p>"
321         else:
322             for chunk in _show_branch(tree, base, "/", pct,
323                                       showpct=='checked', exclude):
324                 yield chunk
325        
326         yield "</div>"
327         yield "</body></html>"
328     menu.exposed = True
329    
330     def annotated_file(self, filename, statements, excluded, missing):
331         source = open(filename, 'r')
332         buffer = []
333         for lineno, line in enumerate(source.readlines()):
334             lineno += 1
335             line = line.strip("\n\r")
336             empty_the_buffer = True
337             if lineno in excluded:
338                 template = TEMPLATE_LOC_EXCLUDED
339             elif lineno in missing:
340                 template = TEMPLATE_LOC_NOT_COVERED
341             elif lineno in statements:
342                 template = TEMPLATE_LOC_COVERED
343             else:
344                 empty_the_buffer = False
345                 buffer.append((lineno, line))
346             if empty_the_buffer:
347                 for lno, pastline in buffer:
348                     yield template % (lno, cgi.escape(pastline))
349                 buffer = []
350                 yield template % (lineno, cgi.escape(line))
351    
352     def report(self, name):
353         coverage.get_ready()
354         filename, statements, excluded, missing, _ = coverage.analysis2(name)
355         pc = _percent(statements, missing)
356         yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name),
357                                        fullpath=name,
358                                        pc=pc)
359         yield '<table>\n'
360         for line in self.annotated_file(filename, statements, excluded,
361                                         missing):
362             yield line
363         yield '</table>'
364         yield '</body>'
365         yield '</html>'
366     report.exposed = True
367
368
369 def serve(path=localFile, port=8080):
370     if coverage is None:
371         raise ImportError("The coverage module could not be imported.")
372     coverage.cache_default = path
373    
374     import cherrypy
375     cherrypy.root = CoverStats()
376     cherrypy.config.update({'server.socketPort': port,
377                             'server.threadPool': 10,
378                             'server.environment': "production",
379                             })
380     cherrypy.server.start()
381
382 if __name__ == "__main__":
383     serve(*tuple(sys.argv[1:]))
384
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets