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

root/branches/cherrypy-2.x/cherrypy/lib/covercp.py

Revision 856 (checked in by rdelon, 3 years ago)

Big change: camelCase to lower_with_underscore names (still need to update the book)

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

Hosted by WebFaction

Log in as guest/cpguest to create tickets