1"""Source file annotation for Coverage.""" 2 3import os, re 4 5from coverage.report import Reporter 6 7class 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, ignore_errors=False): 30 super(AnnotateReporter, self).__init__(coverage, ignore_errors) 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, config, directory=None): 37 """Run the report. 38 39 See `coverage.report()` for arguments. 40 41 """ 42 self.report_files(self.annotate_file, morfs, config, 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() 102