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

Changeset 550

Show
Ignore:
Timestamp:
08/23/05 16:26:17
Author:
fumanchu
Message:

Upgrades to coverage browser (thanks to Keir Mierle). See ticket #262.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/lib/covercp.py

    r543 r550  
    4444import re 
    4545import sys 
     46import cgi 
     47import urllib 
    4648import os, os.path 
    4749localFile = os.path.join(os.path.dirname(__file__), "coverage.cache") 
     
    5153except ImportError: 
    5254    import StringIO 
    53  
    5455 
    5556try: 
     
    6869        pass 
    6970 
    70  
    71 class CoverStats(object): 
    72      
    73     def index(self): 
    74         return """<html> 
    75         <head><title>CherryPy coverage data</title></head> 
    76         <frameset cols='250, 1*'> 
    77             <frame src='menu' /> 
    78             <frame name='main' src='' /> 
    79         </frameset> 
    80         </html> 
    81         """ 
    82     index.exposed = True 
    83      
    84     def menu(self, base="", pct=""): 
    85         yield """<html> 
     71# Guess initial depth to hide FIXME this doesn't work for non-cherrypy stuff 
     72import cherrypy 
     73initial_base = os.path.dirname(cherrypy.__file__) 
     74 
     75TEMPLATE_MENU = """<html> 
    8676<head> 
    8777    <title>CherryPy Coverage Menu</title> 
     
    9686        } 
    9787        .fail {color: red;} 
     88        .pass {color: #888;} 
    9889        #pct {text-align: right;} 
     90        h3 { font-size: small; font-weight: bold; font-style: italic; margin-top: 5px;} 
     91        input { border: 1px solid #ccc; padding: 2px; } 
    9992    </style> 
    10093</head> 
    10194<body> 
    10295<h2>CherryPy Coverage</h2>""" 
    103          
    104         coverage.get_ready() 
    105         runs = coverage.cexecuted.keys() 
    106         if runs: 
    107             yield """<form action='menu'> 
    108     <input type='hidden' name='base' value='%s' /> 
    109     <input type='submit' value='Show %%' /> 
    110     threshold: <input type='text' id='pct' name='pct' value='%s' size='3' />%% 
    111 </form>""" % (base, pct or "50") 
     96 
     97TEMPLATE_FORM = """ 
     98<form action='menu' method=GET> 
     99    <input type='hidden' name='base' value='%(base)s' /> 
     100    <h3>Options</h3> 
     101    <input type='checkbox' %(showpct)s name='showpct' value='checked'/> 
     102    show percentages <br /> 
     103    Hide files over <input type='text' id='pct' name='pct' value='%(pct)s' size='3' />%%<br /> 
     104    Exclude files matching<br /> 
     105    <input type='text' id='exclude' name='exclude' value='%(exclude)s' size='20' /> 
     106    <br /> 
     107 
     108    <input type='submit' value='Change view' /> 
     109</form>"""  
     110 
     111TEMPLATE_FRAMESET = """<html> 
     112<head><title>CherryPy coverage data</title></head> 
     113<frameset cols='250, 1*'> 
     114    <frame src='menu?base=%s' /> 
     115    <frame name='main' src='' /> 
     116</frameset> 
     117</html> 
     118""" % initial_base.lower() 
     119 
     120TEMPLATE_COVERAGE = """<html> 
     121<head> 
     122    <title>Coverage for %(name)s</title> 
     123    <style> 
     124        h2 { margin-bottom: .25em; } 
     125        p { margin: .25em; } 
     126        .covered { color: #000; background-color: #fff; } 
     127        .notcovered { color: #fee; background-color: #500; } 
     128        .excluded { color: #00f; background-color: #fff; } 
     129         table .covered, table .notcovered, table .excluded 
     130             { font-family: Andale Mono, monospace; 
     131               font-size: 10pt; white-space: pre; } 
     132 
     133         .lineno { background-color: #eee;} 
     134         .notcovered .lineno { background-color: #000;} 
     135         table { border-collapse: collapse; 
     136    </style> 
     137</head> 
     138<body> 
     139<h2>%(name)s</h2> 
     140<p>%(fullpath)s</p> 
     141<p>Coverage: %(pc)s%%</p>""" 
     142 
     143TEMPLATE_LOC_COVERED = """<tr class="covered"> 
     144    <td class="lineno">%s&nbsp;</td> 
     145    <td>%s</td> 
     146</tr>\n""" 
     147TEMPLATE_LOC_NOT_COVERED = """<tr class="notcovered"> 
     148    <td class="lineno">%s&nbsp;</td> 
     149    <td>%s</td> 
     150</tr>\n""" 
     151TEMPLATE_LOC_EXCLUDED = """<tr class="excluded"> 
     152    <td class="lineno">%s&nbsp;</td> 
     153    <td>%s</td> 
     154</tr>\n""" 
     155 
     156 
     157def _skip_file(path, exclude): 
     158    if exclude: 
     159        return bool(re.search(exclude, path)) 
     160 
     161def _percent(statements, missing): 
     162    s = len(statements) 
     163    e = s - len(missing) 
     164    if s > 0: 
     165        return int(round(100.0 * e / s)) 
     166    return 0 
     167 
     168def _show_branch(root, base="", path="", pct=0, showpct=False, exclude=""): 
     169     
     170    # Show the directory name and any of our children 
     171    dirs = [k for k, v in root.iteritems() if v is not None] 
     172    dirs.sort() 
     173    for name in dirs: 
     174        if path: 
     175            newpath = os.sep.join((path, name)) 
     176        else: 
     177            newpath = name 
     178         
     179        if newpath.startswith(base): 
     180            relpath = newpath[len(base):] 
     181            yield "<nobr>" + ("|&nbsp;" * relpath.count(os.sep)) + "<b>" 
     182            yield ("<a href='menu?base=%s&exclude=%s'>%s</a>" % 
     183                   (newpath, urllib.quote_plus(exclude), name)) 
     184            yield "</b></nobr><br />\n" 
     185         
     186        for chunk in _show_branch(root[name], base, newpath, pct, showpct, exclude): 
     187            yield chunk 
     188     
     189    # Now list the files 
     190    if path.startswith(base): 
     191        relpath = path[len(base):] 
     192        files = [k for k, v in root.iteritems() if v is None] 
     193        files.sort() 
     194        for name in files: 
     195            if path: 
     196                newpath = os.sep.join((path, name)) 
     197            else: 
     198                newpath = name 
    112199             
    113             yield "<div id='tree'>" 
    114             tree = {} 
    115             def graft(path): 
    116                 b, n = os.path.split(path) 
    117                 if n: 
    118                     return graft(b).setdefault(n, {}) 
     200            pc_str = "" 
     201            if showpct: 
     202                try: 
     203                    _, statements, _, missing, _ = coverage.analysis2(newpath) 
     204                except: 
     205                    # Yes, we really want to pass on all errors. 
     206                    pass 
    119207                else: 
    120                     return tree.setdefault(b.strip(r"\/"), {}
    121             for path in runs: 
    122                 if not os.path.isdir(path)
    123                     b, n = os.path.split(path) 
    124                     if b.startswith(base)
    125                         graft(b)[n] = None 
     208                    pc = _percent(statements, missing
     209                    pc_str = ("%3d%% " % pc).replace(' ','&nbsp;') 
     210                    if pc < float(pct) or pc == -1
     211                        pc_str = "<span class='fail'>%s</span>" % pc_str 
     212                    else
     213                        pc_str = "<span class='pass'>%s</span>" % pc_str 
    126214             
    127             def show(root, depth=0, path=""): 
    128                 dirs = [k for k, v in root.iteritems() if v is not None] 
    129                 dirs.sort() 
    130                 for name in dirs: 
    131                     if path: 
    132                         newpath = os.sep.join((path, name)) 
    133                     else: 
    134                         newpath = name 
    135                      
    136                     yield "<nobr>" + ("|&nbsp;" * depth) + "<b>" 
    137                     yield "<a href='menu?base=%s'>%s</a>" % (newpath, name) 
    138                     yield "</b></nobr><br />\n" 
    139                     for chunk in show(root[name], depth + 1, newpath): 
    140                         yield chunk 
    141                  
    142                 files = [k for k, v in root.iteritems() if v is None] 
    143                 files.sort() 
    144                 for name in files: 
    145                     if path: 
    146                         newpath = os.sep.join((path, name)) 
    147                     else: 
    148                         newpath = name 
    149                      
    150                     pc_str = "" 
    151                     if pct: 
    152                         try: 
    153                             _, statements, _, missing, _ = coverage.analysis2(newpath) 
    154                         except: 
    155                             # Yes, we really want to pass on all errors. 
    156                             pass 
    157                         else: 
    158                             s = len(statements) 
    159                             e = s - len(missing) 
    160                             if s > 0: 
    161                                 pc = 100.0 * e / s 
    162                                 pc_str = "%d%% " % pc 
    163                                 if pc < 100: 
    164                                     pc_str = "&nbsp;" + pc_str 
    165                                     if pc < 10: 
    166                                         pc_str = "&nbsp;" + pc_str 
    167                                 if pc < float(pct): 
    168                                     pc_str = "<span class='fail'>%s</span>" % pc_str 
    169                     yield ("<nobr>%s%s<a href='report?name=%s' target='main'>%s</a></nobr><br />\n" 
    170                            % ("|&nbsp;" * depth, pc_str, newpath, name)) 
    171              
    172             for chunk in show(tree): 
     215            yield ("<nobr>%s%s<a href='report?name=%s' target='main'>%s</a></nobr><br />\n" 
     216                   % ("|&nbsp;" * (relpath.count(os.sep) + 1), pc_str, newpath, name)) 
     217 
     218def get_tree(base, exclude): 
     219    """Return covered module names as a nested dict.""" 
     220    tree = {} 
     221    coverage.get_ready() 
     222    runs = coverage.cexecuted.keys() 
     223    if runs: 
     224        tree = {} 
     225        def graft(path): 
     226            head, tail = os.path.split(path) 
     227            if tail: 
     228                return graft(head).setdefault(tail, {}) 
     229            else: 
     230                return tree.setdefault(head.strip(r"\/"), {}) 
     231         
     232        for path in runs: 
     233            if not _skip_file(path, exclude) and not os.path.isdir(path): 
     234                head, tail = os.path.split(path) 
     235                if head.startswith(base): 
     236                    graft(head)[tail] = None 
     237    return tree 
     238 
     239 
     240class CoverStats(object): 
     241     
     242    def index(self): 
     243        return TEMPLATE_FRAMESET 
     244    index.exposed = True 
     245     
     246    def menu(self, base="", pct="50", showpct="", 
     247             exclude=r'python\d\.\d|test|tut\d|tutorial'): 
     248         
     249        # The coverage module uses all-lower-case names. 
     250        base = base.lower().rstrip(os.sep) 
     251         
     252        yield TEMPLATE_MENU 
     253        yield TEMPLATE_FORM % locals() 
     254         
     255        yield "<div id='tree'>" 
     256         
     257        # Start by showing links for parent paths 
     258        path = "" 
     259        atoms = base.split(os.sep) 
     260        atoms.pop() 
     261        for atom in atoms: 
     262            path += atom + os.sep 
     263            yield ("<nobr><b><a href='menu?base=%s&exclude=%s'>%s</a></b></nobr>%s\n" 
     264                   % (path, urllib.quote_plus(exclude), atom, os.sep)) 
     265         
     266        tree = get_tree(base, exclude) 
     267        if not tree: 
     268            yield "<p>No modules covered.</p>" 
     269        else: 
     270            # Now show all visible branches 
     271            yield "<br />" 
     272            for chunk in _show_branch(tree, base, "", pct, showpct=='checked', exclude): 
    173273                yield chunk 
    174              
    175             yield "</div>" 
    176         else: 
    177             yield "<p>No modules covered.</p>" 
     274         
     275        yield "</div>" 
    178276        yield "</body></html>" 
    179277    menu.exposed = True 
     
    181279    def annotated_file(self, filename, statements, excluded, missing): 
    182280        source = open(filename, 'r') 
    183         dest = StringIO.StringIO() 
    184281        lineno = 0 
    185         i = 0 
    186         j = 0 
    187         covered = 1 
    188282        while 1: 
    189283            line = source.readline() 
    190284            if line == '': 
    191285                break 
     286            line = line[:-1] 
    192287            lineno = lineno + 1 
    193             while i < len(statements) and statements[i] < lineno: 
    194                 i = i + 1 
    195             while j < len(missing) and missing[j] < lineno: 
    196                 j = j + 1 
    197             if i < len(statements) and statements[i] == lineno: 
    198                 covered = j >= len(missing) or missing[j] > lineno 
    199             if coverage.blank_re.match(line): 
    200                 dest.write('  ') 
    201             elif coverage.else_re.match(line): 
    202                 # Special logic for lines containing only 
    203                 # 'else:'.  See [GDR 2001-12-04b, 3.2]. 
    204                 if i >= len(statements) and j >= len(missing): 
    205                     dest.write('! ') 
    206                 elif i >= len(statements) or j >= len(missing): 
    207                     dest.write('> ') 
    208                 elif statements[i] == missing[j]: 
    209                     dest.write('! ') 
    210                 else: 
    211                     dest.write('> ') 
    212             elif lineno in excluded: 
    213                 dest.write('- ') 
    214             elif covered: 
    215                 dest.write('> ') 
     288            if line == '': 
     289                yield '&nbsp;' 
     290                continue 
     291            if lineno in excluded: 
     292                template = TEMPLATE_LOC_EXCLUDED 
     293            elif lineno in missing: 
     294                template = TEMPLATE_LOC_NOT_COVERED 
    216295            else: 
    217                 dest.write('! ') 
    218             dest.write(line) 
    219         source.close() 
    220         result = dest.getvalue() 
    221         dest.close() 
    222         return result 
     296                template = TEMPLATE_LOC_COVERED 
     297            yield template % (lineno, cgi.escape(line)) 
    223298     
    224299    def report(self, name): 
    225         import cherrypy 
    226         cherrypy.response.headerMap['Content-Type'] = 'text/plain' 
    227          
    228         yield name 
    229         yield "\n" 
    230          
    231300        coverage.get_ready() 
    232301        filename, statements, excluded, missing, _ = coverage.analysis2(name) 
    233         s = len(statements) 
    234         e = s - len(missing) 
    235         if s > 0: 
    236             pc = 100.0 * e / s 
    237             yield "%2d%% covered\n" % pc 
    238          
    239         yield "\n" 
    240         yield self.annotated_file(filename, statements, excluded, missing) 
     302        pc = _percent(statements, missing) 
     303        yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name), 
     304                                       fullpath=name, 
     305                                       pc=pc) 
     306        yield '<table>\n' 
     307        for line in self.annotated_file(filename, statements, excluded, 
     308                                        missing): 
     309            yield line 
     310        yield '</table>' 
     311        yield '</body>' 
     312        yield '</html>' 
    241313    report.exposed = True 
    242314 
     
    255327    cherrypy.server.start() 
    256328 
    257  
    258329if __name__ == "__main__": 
    259330    serve(*tuple(sys.argv[1:])) 

Hosted by WebFaction

Log in as guest/cpguest to create tickets