| OLD | NEW |
| (Empty) |
| 1 """Source file annotation for Coverage.""" | |
| 2 | |
| 3 import os, re | |
| 4 | |
| 5 from coverage.report import Reporter | |
| 6 | |
| 7 class AnnotateReporter(Reporter): | |
| 8 """Generate annotated source files showing line coverage. | |
| 9 | |
| 10 This reporter creates annotated copies of the measured source files. Each | |
| 11 .py file is copied as a .py,cover file, with a left-hand margin annotating | |
| 12 each line:: | |
| 13 | |
| 14 > def h(x): | |
| 15 - if 0: #pragma: no cover | |
| 16 - pass | |
| 17 > if x == 1: | |
| 18 ! a = 1 | |
| 19 > else: | |
| 20 > a = 2 | |
| 21 | |
| 22 > h(2) | |
| 23 | |
| 24 Executed lines use '>', lines not executed use '!', lines excluded from | |
| 25 consideration use '-'. | |
| 26 | |
| 27 """ | |
| 28 | |
| 29 def __init__(self, coverage, config): | |
| 30 super(AnnotateReporter, self).__init__(coverage, config) | |
| 31 self.directory = None | |
| 32 | |
| 33 blank_re = re.compile(r"\s*(#|$)") | |
| 34 else_re = re.compile(r"\s*else\s*:\s*(#|$)") | |
| 35 | |
| 36 def report(self, morfs, directory=None): | |
| 37 """Run the report. | |
| 38 | |
| 39 See `coverage.report()` for arguments. | |
| 40 | |
| 41 """ | |
| 42 self.report_files(self.annotate_file, morfs, directory) | |
| 43 | |
| 44 def annotate_file(self, cu, analysis): | |
| 45 """Annotate a single file. | |
| 46 | |
| 47 `cu` is the CodeUnit for the file to annotate. | |
| 48 | |
| 49 """ | |
| 50 if not cu.relative: | |
| 51 return | |
| 52 | |
| 53 filename = cu.filename | |
| 54 source = cu.source_file() | |
| 55 if self.directory: | |
| 56 dest_file = os.path.join(self.directory, cu.flat_rootname()) | |
| 57 dest_file += ".py,cover" | |
| 58 else: | |
| 59 dest_file = filename + ",cover" | |
| 60 dest = open(dest_file, 'w') | |
| 61 | |
| 62 statements = analysis.statements | |
| 63 missing = analysis.missing | |
| 64 excluded = analysis.excluded | |
| 65 | |
| 66 lineno = 0 | |
| 67 i = 0 | |
| 68 j = 0 | |
| 69 covered = True | |
| 70 while True: | |
| 71 line = source.readline() | |
| 72 if line == '': | |
| 73 break | |
| 74 lineno += 1 | |
| 75 while i < len(statements) and statements[i] < lineno: | |
| 76 i += 1 | |
| 77 while j < len(missing) and missing[j] < lineno: | |
| 78 j += 1 | |
| 79 if i < len(statements) and statements[i] == lineno: | |
| 80 covered = j >= len(missing) or missing[j] > lineno | |
| 81 if self.blank_re.match(line): | |
| 82 dest.write(' ') | |
| 83 elif self.else_re.match(line): | |
| 84 # Special logic for lines containing only 'else:'. | |
| 85 if i >= len(statements) and j >= len(missing): | |
| 86 dest.write('! ') | |
| 87 elif i >= len(statements) or j >= len(missing): | |
| 88 dest.write('> ') | |
| 89 elif statements[i] == missing[j]: | |
| 90 dest.write('! ') | |
| 91 else: | |
| 92 dest.write('> ') | |
| 93 elif lineno in excluded: | |
| 94 dest.write('- ') | |
| 95 elif covered: | |
| 96 dest.write('> ') | |
| 97 else: | |
| 98 dest.write('! ') | |
| 99 dest.write(line) | |
| 100 source.close() | |
| 101 dest.close() | |
| OLD | NEW |