Index: third_party/cherrypy/lib/covercp.py |
=================================================================== |
--- third_party/cherrypy/lib/covercp.py (revision 0) |
+++ third_party/cherrypy/lib/covercp.py (revision 0) |
@@ -0,0 +1,365 @@ |
+"""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) |
+ |
+DO NOT subscribe anything on the 'start_thread' channel, as previously |
+recommended. Calling start once in the main thread should be sufficient |
+to start coverage on all threads. Calling start again in each thread |
+effectively clears any coverage data gathered up to that point. |
+ |
+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 |
+from cherrypy._cpcompat import quote_plus |
+import os, os.path |
+localFile = os.path.join(os.path.dirname(__file__), "coverage.cache") |
+ |
+the_coverage = None |
+try: |
+ from coverage import coverage |
+ the_coverage = coverage(data_file=localFile) |
+ def start(): |
+ the_coverage.start() |
+except ImportError: |
+ # Setting the_coverage to None will raise errors |
+ # that need to be trapped downstream. |
+ the_coverage = None |
+ |
+ import warnings |
+ warnings.warn("No code coverage will be performed; coverage.py could not be imported.") |
+ |
+ def start(): |
+ pass |
+start.priority = 20 |
+ |
+TEMPLATE_MENU = """<html> |
+<head> |
+ <title>CherryPy Coverage Menu</title> |
+ <style> |
+ body {font: 9pt Arial, serif;} |
+ #tree { |
+ font-size: 8pt; |
+ font-family: Andale Mono, monospace; |
+ white-space: pre; |
+ } |
+ #tree a:active, a:focus { |
+ background-color: black; |
+ padding: 1px; |
+ color: white; |
+ border: 0px solid #9999FF; |
+ -moz-outline-style: none; |
+ } |
+ .fail { color: red;} |
+ .pass { color: #888;} |
+ #pct { text-align: right;} |
+ h3 { |
+ font-size: small; |
+ font-weight: bold; |
+ font-style: italic; |
+ margin-top: 5px; |
+ } |
+ input { border: 1px solid #ccc; padding: 2px; } |
+ .directory { |
+ color: #933; |
+ font-style: italic; |
+ font-weight: bold; |
+ font-size: 10pt; |
+ } |
+ .file { |
+ color: #400; |
+ } |
+ a { text-decoration: none; } |
+ #crumbs { |
+ color: white; |
+ font-size: 8pt; |
+ font-family: Andale Mono, monospace; |
+ width: 100%; |
+ background-color: black; |
+ } |
+ #crumbs a { |
+ color: #f88; |
+ } |
+ #options { |
+ line-height: 2.3em; |
+ border: 1px solid black; |
+ background-color: #eee; |
+ padding: 4px; |
+ } |
+ #exclude { |
+ width: 100%; |
+ margin-bottom: 3px; |
+ border: 1px solid #999; |
+ } |
+ #submit { |
+ background-color: black; |
+ color: white; |
+ border: 0; |
+ margin-bottom: -9px; |
+ } |
+ </style> |
+</head> |
+<body> |
+<h2>CherryPy Coverage</h2>""" |
+ |
+TEMPLATE_FORM = """ |
+<div id="options"> |
+<form action='menu' method=GET> |
+ <input type='hidden' name='base' value='%(base)s' /> |
+ Show percentages <input type='checkbox' %(showpct)s name='showpct' value='checked' /><br /> |
+ Hide files over <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br /> |
+ Exclude files matching<br /> |
+ <input type='text' id='exclude' name='exclude' value='%(exclude)s' size='20' /> |
+ <br /> |
+ |
+ <input type='submit' value='Change view' id="submit"/> |
+</form> |
+</div>""" |
+ |
+TEMPLATE_FRAMESET = """<html> |
+<head><title>CherryPy coverage data</title></head> |
+<frameset cols='250, 1*'> |
+ <frame src='menu?base=%s' /> |
+ <frame name='main' src='' /> |
+</frameset> |
+</html> |
+""" |
+ |
+TEMPLATE_COVERAGE = """<html> |
+<head> |
+ <title>Coverage for %(name)s</title> |
+ <style> |
+ h2 { margin-bottom: .25em; } |
+ p { margin: .25em; } |
+ .covered { color: #000; background-color: #fff; } |
+ .notcovered { color: #fee; background-color: #500; } |
+ .excluded { color: #00f; background-color: #fff; } |
+ table .covered, table .notcovered, table .excluded |
+ { font-family: Andale Mono, monospace; |
+ font-size: 10pt; white-space: pre; } |
+ |
+ .lineno { background-color: #eee;} |
+ .notcovered .lineno { background-color: #000;} |
+ table { border-collapse: collapse; |
+ </style> |
+</head> |
+<body> |
+<h2>%(name)s</h2> |
+<p>%(fullpath)s</p> |
+<p>Coverage: %(pc)s%%</p>""" |
+ |
+TEMPLATE_LOC_COVERED = """<tr class="covered"> |
+ <td class="lineno">%s </td> |
+ <td>%s</td> |
+</tr>\n""" |
+TEMPLATE_LOC_NOT_COVERED = """<tr class="notcovered"> |
+ <td class="lineno">%s </td> |
+ <td>%s</td> |
+</tr>\n""" |
+TEMPLATE_LOC_EXCLUDED = """<tr class="excluded"> |
+ <td class="lineno">%s </td> |
+ <td>%s</td> |
+</tr>\n""" |
+ |
+TEMPLATE_ITEM = "%s%s<a class='file' href='report?name=%s' target='main'>%s</a>\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="", |
+ coverage=the_coverage): |
+ |
+ # Show the directory name and any of our children |
+ dirs = [k for k, v in root.items() 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 "<a class='directory' href='menu?base=%s&exclude=%s'>%s</a>\n" % \ |
+ (newpath, quote_plus(exclude), name) |
+ |
+ for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude, coverage=coverage): |
+ yield chunk |
+ |
+ # Now list the files |
+ if path.lower().startswith(base): |
+ relpath = path[len(base):] |
+ files = [k for k, v in root.items() 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 = "<span class='fail'>%s</span>" % pc_str |
+ else: |
+ pc_str = "<span class='pass'>%s</span>" % 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, coverage=the_coverage): |
+ """Return covered module names as a nested dict.""" |
+ tree = {} |
+ runs = coverage.data.executed_files() |
+ 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 __init__(self, coverage, root=None): |
+ self.coverage = coverage |
+ if root is None: |
+ # Guess initial depth. Files outside this path will not be |
+ # reachable from the web interface. |
+ import cherrypy |
+ root = os.path.dirname(cherrypy.__file__) |
+ self.root = root |
+ |
+ def index(self): |
+ return TEMPLATE_FRAMESET % self.root.lower() |
+ 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 "<div id='crumbs'>" |
+ path = "" |
+ atoms = base.split(os.sep) |
+ atoms.pop() |
+ for atom in atoms: |
+ path += atom + os.sep |
+ yield ("<a href='menu?base=%s&exclude=%s'>%s</a> %s" |
+ % (path, quote_plus(exclude), atom, os.sep)) |
+ yield "</div>" |
+ |
+ yield "<div id='tree'>" |
+ |
+ # Then display the tree |
+ tree = get_tree(base, exclude, self.coverage) |
+ if not tree: |
+ yield "<p>No modules covered.</p>" |
+ else: |
+ for chunk in _show_branch(tree, base, "/", pct, |
+ showpct=='checked', exclude, coverage=self.coverage): |
+ yield chunk |
+ |
+ yield "</div>" |
+ yield "</body></html>" |
+ 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): |
+ filename, statements, excluded, missing, _ = self.coverage.analysis2(name) |
+ pc = _percent(statements, missing) |
+ yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name), |
+ fullpath=name, |
+ pc=pc) |
+ yield '<table>\n' |
+ for line in self.annotated_file(filename, statements, excluded, |
+ missing): |
+ yield line |
+ yield '</table>' |
+ yield '</body>' |
+ yield '</html>' |
+ report.exposed = True |
+ |
+ |
+def serve(path=localFile, port=8080, root=None): |
+ if coverage is None: |
+ raise ImportError("The coverage module could not be imported.") |
+ from coverage import coverage |
+ cov = coverage(data_file = path) |
+ cov.load() |
+ |
+ import cherrypy |
+ cherrypy.config.update({'server.socket_port': int(port), |
+ 'server.thread_pool': 10, |
+ 'environment': "production", |
+ }) |
+ cherrypy.quickstart(CoverStats(cov, root)) |
+ |
+if __name__ == "__main__": |
+ serve(*tuple(sys.argv[1:])) |
+ |
Property changes on: third_party/cherrypy/lib/covercp.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |