Package cherrypy :: Package lib :: Module covercp
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.lib.covercp

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