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

root/trunk/cherrypy/lib/covercp.py

Revision 1219 (checked in by fumanchu, 4 years ago)

Changed server.start to server.quickstart, and server.start_all to server.start.

  • 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 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):
36         coverage.start()
37 except ImportError:
38     # Setting coverage to None will raise errors
39     # that need to be trapped downstream.
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):
46         pass
47
48 # Guess initial depth to hide FIXME this doesn't work for non-cherrypy stuff
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&nbsp;</td>
169     <td>%s</td>
170 </tr>\n"""
171 TEMPLATE_LOC_NOT_COVERED = """<tr class="notcovered">
172     <td class="lineno">%s&nbsp;</td>
173     <td>%s</td>
174 </tr>\n"""
175 TEMPLATE_LOC_EXCLUDED = """<tr class="excluded">
176     <td class="lineno">%s&nbsp;</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
182 def _percent(statements, missing):
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     # Show the directory name and any of our children
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     # Now list the files
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                     # Yes, we really want to pass on all errors.
220                     pass
221                 else:
222                     pc = _percent(statements, missing)
223                     pc_str = ("%3d%% " % pc).replace(' ','&nbsp;')
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
232 def _skip_file(path, exclude):
233     if exclude:
234         return bool(re.search(exclude, path))
235
236 def _graft(path, tree):
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
255 def get_tree(base, exclude):
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
266 class CoverStats(object):
267    
268     def index(self):
269         return TEMPLATE_FRAMESET
270     index.exposed = True
271    
272     def menu(self, base="/", pct="50", showpct="",
273              exclude=r'python\d\.\d|test|tut\d|tutorial'):
274        
275         # The coverage module uses all-lower-case names.
276         base = base.lower().rstrip(os.sep)
277        
278         yield TEMPLATE_MENU
279         yield TEMPLATE_FORM % locals()
280        
281         # Start by showing links for parent paths
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         # Then display the tree
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    
307     def annotated_file(self, filename, statements, excluded, missing):
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    
329     def report(self, name):
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
346 def serve(path=localFile, port=8080):
347     if coverage is None:
348         raise ImportError("The coverage module could not be imported.")
349     coverage.cache_default = path
350    
351     import cherrypy
352     cherrypy.config.update({'server.socket_port': port,
353                             'server.thread_pool': 10,
354                             'environment': "production",
355                             })
356     cherrypy.quickstart(CoverStats())
357
358 if __name__ == "__main__":
359     serve(*tuple(sys.argv[1:]))
360
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets