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, 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() | |
OLD | NEW |