"""Code-coverage tools for CherryPy. To use this module, or the coverage tools in the test suite, you need to download 'coverage.py', either Gareth Rees' original implementation: http://www.garethrees.org/2001/12/04/python-coverage/ or Ned Batchelder's enhanced version: http://www.nedbatchelder.com/code/modules/coverage.html To turn on coverage tracing, use the following code: cherrypy.engine.subscribe('start', covercp.start) cherrypy.engine.subscribe('start_thread', covercp.start) Run your code, then use the covercp.serve() function to browse the results in a web browser. If you run this module from the command line, it will call serve() for you. """ import re import sys import cgi import urllib import os, os.path localFile = os.path.join(os.path.dirname(__file__), "coverage.cache") try: import cStringIO as StringIO except ImportError: import StringIO try: from coverage import the_coverage as coverage def start(threadid=None): coverage.start() except ImportError: # Setting coverage to None will raise errors # that need to be trapped downstream. coverage = None import warnings warnings.warn("No code coverage will be performed; coverage.py could not be imported.") def start(threadid=None): pass start.priority = 20 # Guess initial depth to hide FIXME this doesn't work for non-cherrypy stuff import cherrypy initial_base = os.path.dirname(cherrypy.__file__) TEMPLATE_MENU = """ CherryPy Coverage Menu

CherryPy Coverage

""" TEMPLATE_FORM = """
Show percentages
Hide files over %%
Exclude files matching

""" TEMPLATE_FRAMESET = """ CherryPy coverage data """ % initial_base.lower() TEMPLATE_COVERAGE = """ Coverage for %(name)s

%(name)s

%(fullpath)s

Coverage: %(pc)s%%

""" TEMPLATE_LOC_COVERED = """ %s  %s \n""" TEMPLATE_LOC_NOT_COVERED = """ %s  %s \n""" TEMPLATE_LOC_EXCLUDED = """ %s  %s \n""" TEMPLATE_ITEM = "%s%s%s\n" def _percent(statements, missing): s = len(statements) e = s - len(missing) if s > 0: return int(round(100.0 * e / s)) return 0 def _show_branch(root, base, path, pct=0, showpct=False, exclude=""): # Show the directory name and any of our children dirs = [k for k, v in root.iteritems() if v] dirs.sort() for name in dirs: newpath = os.path.join(path, name) if newpath.lower().startswith(base): relpath = newpath[len(base):] yield "| " * relpath.count(os.sep) yield "%s\n" % \ (newpath, urllib.quote_plus(exclude), name) for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude): yield chunk # Now list the files if path.lower().startswith(base): relpath = path[len(base):] files = [k for k, v in root.iteritems() if not v] files.sort() for name in files: newpath = os.path.join(path, name) pc_str = "" if showpct: try: _, statements, _, missing, _ = coverage.analysis2(newpath) except: # Yes, we really want to pass on all errors. pass else: pc = _percent(statements, missing) pc_str = ("%3d%% " % pc).replace(' ',' ') if pc < float(pct) or pc == -1: pc_str = "%s" % pc_str else: pc_str = "%s" % pc_str yield TEMPLATE_ITEM % ("| " * (relpath.count(os.sep) + 1), pc_str, newpath, name) def _skip_file(path, exclude): if exclude: return bool(re.search(exclude, path)) def _graft(path, tree): d = tree p = path atoms = [] while True: p, tail = os.path.split(p) if not tail: break atoms.append(tail) atoms.append(p) if p != "/": atoms.append("/") atoms.reverse() for node in atoms: if node: d = d.setdefault(node, {}) def get_tree(base, exclude): """Return covered module names as a nested dict.""" tree = {} coverage.get_ready() runs = coverage.cexecuted.keys() if runs: for path in runs: if not _skip_file(path, exclude) and not os.path.isdir(path): _graft(path, tree) return tree class CoverStats(object): def index(self): return TEMPLATE_FRAMESET index.exposed = True def menu(self, base="/", pct="50", showpct="", exclude=r'python\d\.\d|test|tut\d|tutorial'): # The coverage module uses all-lower-case names. base = base.lower().rstrip(os.sep) yield TEMPLATE_MENU yield TEMPLATE_FORM % locals() # Start by showing links for parent paths yield "
" path = "" atoms = base.split(os.sep) atoms.pop() for atom in atoms: path += atom + os.sep yield ("%s %s" % (path, urllib.quote_plus(exclude), atom, os.sep)) yield "
" yield "
" # Then display the tree tree = get_tree(base, exclude) if not tree: yield "

No modules covered.

" else: for chunk in _show_branch(tree, base, "/", pct, showpct=='checked', exclude): yield chunk yield "
" yield "" menu.exposed = True def annotated_file(self, filename, statements, excluded, missing): source = open(filename, 'r') buffer = [] for lineno, line in enumerate(source.readlines()): lineno += 1 line = line.strip("\n\r") empty_the_buffer = True if lineno in excluded: template = TEMPLATE_LOC_EXCLUDED elif lineno in missing: template = TEMPLATE_LOC_NOT_COVERED elif lineno in statements: template = TEMPLATE_LOC_COVERED else: empty_the_buffer = False buffer.append((lineno, line)) if empty_the_buffer: for lno, pastline in buffer: yield template % (lno, cgi.escape(pastline)) buffer = [] yield template % (lineno, cgi.escape(line)) def report(self, name): coverage.get_ready() filename, statements, excluded, missing, _ = coverage.analysis2(name) pc = _percent(statements, missing) yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name), fullpath=name, pc=pc) yield '\n' for line in self.annotated_file(filename, statements, excluded, missing): yield line yield '
' yield '' yield '' report.exposed = True def serve(path=localFile, port=8080): if coverage is None: raise ImportError("The coverage module could not be imported.") coverage.cache_default = path import cherrypy cherrypy.config.update({'server.socket_port': port, 'server.thread_pool': 10, 'environment': "production", }) cherrypy.quickstart(CoverStats()) if __name__ == "__main__": serve(*tuple(sys.argv[1:]))