OLD | NEW |
(Empty) | |
| 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 3 |
| 4 """Parser.py: a main for invoking code in coverage/parser.py""" |
| 5 |
| 6 from __future__ import division |
| 7 |
| 8 import glob, os, sys |
| 9 import collections |
| 10 from optparse import OptionParser |
| 11 |
| 12 import disgen |
| 13 |
| 14 from coverage.misc import CoverageException |
| 15 from coverage.parser import ByteParser, PythonParser |
| 16 from coverage.python import get_python_source |
| 17 |
| 18 opcode_counts = collections.Counter() |
| 19 |
| 20 class ParserMain(object): |
| 21 """A main for code parsing experiments.""" |
| 22 |
| 23 def main(self, args): |
| 24 """A main function for trying the code from the command line.""" |
| 25 |
| 26 parser = OptionParser() |
| 27 parser.add_option( |
| 28 "-c", action="store_true", dest="chunks", |
| 29 help="Show basic block chunks" |
| 30 ) |
| 31 parser.add_option( |
| 32 "-d", action="store_true", dest="dis", |
| 33 help="Disassemble" |
| 34 ) |
| 35 parser.add_option( |
| 36 "-H", action="store_true", dest="histogram", |
| 37 help="Count occurrences of opcodes" |
| 38 ) |
| 39 parser.add_option( |
| 40 "-R", action="store_true", dest="recursive", |
| 41 help="Recurse to find source files" |
| 42 ) |
| 43 parser.add_option( |
| 44 "-s", action="store_true", dest="source", |
| 45 help="Show analyzed source" |
| 46 ) |
| 47 parser.add_option( |
| 48 "-t", action="store_true", dest="tokens", |
| 49 help="Show tokens" |
| 50 ) |
| 51 |
| 52 options, args = parser.parse_args() |
| 53 if options.recursive: |
| 54 if args: |
| 55 root = args[0] |
| 56 else: |
| 57 root = "." |
| 58 for root, _, _ in os.walk(root): |
| 59 for f in glob.glob(root + "/*.py"): |
| 60 self.one_file(options, f) |
| 61 elif not args: |
| 62 parser.print_help() |
| 63 else: |
| 64 self.one_file(options, args[0]) |
| 65 |
| 66 if options.histogram: |
| 67 total = sum(opcode_counts.values()) |
| 68 print("{} total opcodes".format(total)) |
| 69 for opcode, number in opcode_counts.most_common(): |
| 70 print("{:20s} {:6d} {:.1%}".format(opcode, number, number/total
)) |
| 71 |
| 72 |
| 73 def one_file(self, options, filename): |
| 74 """Process just one file.""" |
| 75 |
| 76 try: |
| 77 text = get_python_source(filename) |
| 78 bp = ByteParser(text, filename=filename) |
| 79 except Exception as err: |
| 80 print("%s" % (err,)) |
| 81 return |
| 82 |
| 83 if options.dis: |
| 84 print("Main code:") |
| 85 self.disassemble(bp, histogram=options.histogram) |
| 86 |
| 87 arcs = bp._all_arcs() |
| 88 if options.chunks:# and not options.dis: |
| 89 chunks = bp._all_chunks() |
| 90 if options.recursive: |
| 91 print("%6d: %s" % (len(chunks), filename)) |
| 92 else: |
| 93 print("Chunks: %r" % chunks) |
| 94 print("Arcs: %r" % sorted(arcs)) |
| 95 |
| 96 if options.source or options.tokens: |
| 97 cp = PythonParser(filename=filename, exclude=r"no\s*cover") |
| 98 cp.show_tokens = options.tokens |
| 99 cp._raw_parse() |
| 100 |
| 101 if options.source: |
| 102 if options.chunks: |
| 103 arc_width, arc_chars = self.arc_ascii_art(arcs) |
| 104 else: |
| 105 arc_width, arc_chars = 0, {} |
| 106 |
| 107 exit_counts = cp.exit_counts() |
| 108 |
| 109 for lineno, ltext in enumerate(cp.lines, start=1): |
| 110 m0 = m1 = m2 = m3 = a = ' ' |
| 111 if lineno in cp.statement_starts: |
| 112 m0 = '-' |
| 113 exits = exit_counts.get(lineno, 0) |
| 114 if exits > 1: |
| 115 m1 = str(exits) |
| 116 if lineno in cp.docstrings: |
| 117 m2 = '"' |
| 118 if lineno in cp.classdefs: |
| 119 m2 = 'C' |
| 120 if lineno in cp.excluded: |
| 121 m3 = 'x' |
| 122 a = arc_chars[lineno].ljust(arc_width) |
| 123 print("%4d %s%s%s%s%s %s" % |
| 124 (lineno, m0, m1, m2, m3, a, ltext) |
| 125 ) |
| 126 |
| 127 def disassemble(self, byte_parser, histogram=False): |
| 128 """Disassemble code, for ad-hoc experimenting.""" |
| 129 |
| 130 for bp in byte_parser.child_parsers(): |
| 131 chunks = bp._split_into_chunks() |
| 132 chunkd = dict((chunk.byte, chunk) for chunk in chunks) |
| 133 if bp.text: |
| 134 srclines = bp.text.splitlines() |
| 135 else: |
| 136 srclines = None |
| 137 print("\n%s: " % bp.code) |
| 138 upto = None |
| 139 for disline in disgen.disgen(bp.code): |
| 140 if histogram: |
| 141 opcode_counts[disline.opcode] += 1 |
| 142 continue |
| 143 if disline.first: |
| 144 if srclines: |
| 145 upto = upto or disline.lineno-1 |
| 146 while upto <= disline.lineno-1: |
| 147 print("%100s%s" % ("", srclines[upto])) |
| 148 upto += 1 |
| 149 elif disline.offset > 0: |
| 150 print("") |
| 151 line = disgen.format_dis_line(disline) |
| 152 chunk = chunkd.get(disline.offset) |
| 153 if chunk: |
| 154 chunkstr = ":: %r" % chunk |
| 155 else: |
| 156 chunkstr = "" |
| 157 print("%-70s%s" % (line, chunkstr)) |
| 158 |
| 159 print("") |
| 160 |
| 161 def arc_ascii_art(self, arcs): |
| 162 """Draw arcs as ascii art. |
| 163 |
| 164 Returns a width of characters needed to draw all the arcs, and a |
| 165 dictionary mapping line numbers to ascii strings to draw for that line. |
| 166 |
| 167 """ |
| 168 arc_chars = collections.defaultdict(str) |
| 169 for lfrom, lto in sorted(arcs): |
| 170 if lfrom < 0: |
| 171 arc_chars[lto] += 'v' |
| 172 elif lto < 0: |
| 173 arc_chars[lfrom] += '^' |
| 174 else: |
| 175 if lfrom == lto - 1: |
| 176 # Don't show obvious arcs. |
| 177 continue |
| 178 if lfrom < lto: |
| 179 l1, l2 = lfrom, lto |
| 180 else: |
| 181 l1, l2 = lto, lfrom |
| 182 w = max(len(arc_chars[l]) for l in range(l1, l2+1)) |
| 183 for l in range(l1, l2+1): |
| 184 if l == lfrom: |
| 185 ch = '<' |
| 186 elif l == lto: |
| 187 ch = '>' |
| 188 else: |
| 189 ch = '|' |
| 190 arc_chars[l] = arc_chars[l].ljust(w) + ch |
| 191 arc_width = 0 |
| 192 |
| 193 if arc_chars: |
| 194 arc_width = max(len(a) for a in arc_chars.values()) |
| 195 else: |
| 196 arc_width = 0 |
| 197 |
| 198 return arc_width, arc_chars |
| 199 |
| 200 if __name__ == '__main__': |
| 201 ParserMain().main(sys.argv[1:]) |
| 202 |
OLD | NEW |