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