1 """Source file annotation for Coverage.""" |
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 |
2 |
3 |
3 import os, re |
4 """Source file annotation for coverage.py.""" |
4 |
5 |
5 from .backward import sorted # pylint: disable=W0622 |
6 import io |
6 from .report import Reporter |
7 import os |
|
8 import re |
|
9 |
|
10 from coverage.files import flat_rootname |
|
11 from coverage.report import Reporter |
7 |
12 |
8 class AnnotateReporter(Reporter): |
13 class AnnotateReporter(Reporter): |
9 """Generate annotated source files showing line coverage. |
14 """Generate annotated source files showing line coverage. |
10 |
15 |
11 This reporter creates annotated copies of the measured source files. Each |
16 This reporter creates annotated copies of the measured source files. Each |
40 See `coverage.report()` for arguments. |
45 See `coverage.report()` for arguments. |
41 |
46 |
42 """ |
47 """ |
43 self.report_files(self.annotate_file, morfs, directory) |
48 self.report_files(self.annotate_file, morfs, directory) |
44 |
49 |
45 def annotate_file(self, cu, analysis): |
50 def annotate_file(self, fr, analysis): |
46 """Annotate a single file. |
51 """Annotate a single file. |
47 |
52 |
48 `cu` is the CodeUnit for the file to annotate. |
53 `fr` is the FileReporter for the file to annotate. |
49 |
54 |
50 """ |
55 """ |
51 if not cu.relative: |
|
52 return |
|
53 |
|
54 filename = cu.filename |
|
55 source = cu.source_file() |
|
56 if self.directory: |
|
57 dest_file = os.path.join(self.directory, cu.flat_rootname()) |
|
58 dest_file += ".py,cover" |
|
59 else: |
|
60 dest_file = filename + ",cover" |
|
61 dest = open(dest_file, 'w') |
|
62 |
|
63 statements = sorted(analysis.statements) |
56 statements = sorted(analysis.statements) |
64 missing = sorted(analysis.missing) |
57 missing = sorted(analysis.missing) |
65 excluded = sorted(analysis.excluded) |
58 excluded = sorted(analysis.excluded) |
66 |
59 |
67 lineno = 0 |
60 if self.directory: |
68 i = 0 |
61 dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename())) |
69 j = 0 |
62 if dest_file.endswith("_py"): |
70 covered = True |
63 dest_file = dest_file[:-3] + ".py" |
71 while True: |
64 dest_file += ",cover" |
72 line = source.readline() |
65 else: |
73 if line == '': |
66 dest_file = fr.filename + ",cover" |
74 break |
67 |
75 lineno += 1 |
68 with io.open(dest_file, 'w', encoding='utf8') as dest: |
76 while i < len(statements) and statements[i] < lineno: |
69 i = 0 |
77 i += 1 |
70 j = 0 |
78 while j < len(missing) and missing[j] < lineno: |
71 covered = True |
79 j += 1 |
72 source = fr.source() |
80 if i < len(statements) and statements[i] == lineno: |
73 for lineno, line in enumerate(source.splitlines(True), start=1): |
81 covered = j >= len(missing) or missing[j] > lineno |
74 while i < len(statements) and statements[i] < lineno: |
82 if self.blank_re.match(line): |
75 i += 1 |
83 dest.write(' ') |
76 while j < len(missing) and missing[j] < lineno: |
84 elif self.else_re.match(line): |
77 j += 1 |
85 # Special logic for lines containing only 'else:'. |
78 if i < len(statements) and statements[i] == lineno: |
86 if i >= len(statements) and j >= len(missing): |
79 covered = j >= len(missing) or missing[j] > lineno |
87 dest.write('! ') |
80 if self.blank_re.match(line): |
88 elif i >= len(statements) or j >= len(missing): |
81 dest.write(u' ') |
89 dest.write('> ') |
82 elif self.else_re.match(line): |
90 elif statements[i] == missing[j]: |
83 # Special logic for lines containing only 'else:'. |
91 dest.write('! ') |
84 if i >= len(statements) and j >= len(missing): |
|
85 dest.write(u'! ') |
|
86 elif i >= len(statements) or j >= len(missing): |
|
87 dest.write(u'> ') |
|
88 elif statements[i] == missing[j]: |
|
89 dest.write(u'! ') |
|
90 else: |
|
91 dest.write(u'> ') |
|
92 elif lineno in excluded: |
|
93 dest.write(u'- ') |
|
94 elif covered: |
|
95 dest.write(u'> ') |
92 else: |
96 else: |
93 dest.write('> ') |
97 dest.write(u'! ') |
94 elif lineno in excluded: |
98 |
95 dest.write('- ') |
99 dest.write(line) |
96 elif covered: |
|
97 dest.write('> ') |
|
98 else: |
|
99 dest.write('! ') |
|
100 dest.write(line) |
|
101 source.close() |
|
102 dest.close() |
|