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