1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
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 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
3 |
3 |
4 """Source file annotation for coverage.py.""" |
4 """Source file annotation for coverage.py.""" |
5 |
5 |
6 import io |
6 import io |
7 import os |
7 import os |
8 import re |
8 import re |
9 |
9 |
10 from coverage.files import flat_rootname |
10 from coverage.files import flat_rootname |
11 from coverage.misc import isolate_module |
11 from coverage.misc import ensure_dir, isolate_module |
12 from coverage.report import Reporter |
12 from coverage.report import get_analysis_to_report |
13 |
13 |
14 os = isolate_module(os) |
14 os = isolate_module(os) |
15 |
15 |
16 |
16 |
17 class AnnotateReporter(Reporter): |
17 class AnnotateReporter(object): |
18 """Generate annotated source files showing line coverage. |
18 """Generate annotated source files showing line coverage. |
19 |
19 |
20 This reporter creates annotated copies of the measured source files. Each |
20 This reporter creates annotated copies of the measured source files. Each |
21 .py file is copied as a .py,cover file, with a left-hand margin annotating |
21 .py file is copied as a .py,cover file, with a left-hand margin annotating |
22 each line:: |
22 each line:: |
34 Executed lines use '>', lines not executed use '!', lines excluded from |
34 Executed lines use '>', lines not executed use '!', lines excluded from |
35 consideration use '-'. |
35 consideration use '-'. |
36 |
36 |
37 """ |
37 """ |
38 |
38 |
39 def __init__(self, coverage, config): |
39 def __init__(self, coverage): |
40 super(AnnotateReporter, self).__init__(coverage, config) |
40 self.coverage = coverage |
|
41 self.config = self.coverage.config |
41 self.directory = None |
42 self.directory = None |
42 |
43 |
43 blank_re = re.compile(r"\s*(#|$)") |
44 blank_re = re.compile(r"\s*(#|$)") |
44 else_re = re.compile(r"\s*else\s*:\s*(#|$)") |
45 else_re = re.compile(r"\s*else\s*:\s*(#|$)") |
45 |
46 |
47 """Run the report. |
48 """Run the report. |
48 |
49 |
49 See `coverage.report()` for arguments. |
50 See `coverage.report()` for arguments. |
50 |
51 |
51 """ |
52 """ |
52 self.report_files(self.annotate_file, morfs, directory) |
53 self.directory = directory |
|
54 self.coverage.get_data() |
|
55 for fr, analysis in get_analysis_to_report(self.coverage, morfs): |
|
56 self.annotate_file(fr, analysis) |
53 |
57 |
54 def annotate_file(self, fr, analysis): |
58 def annotate_file(self, fr, analysis): |
55 """Annotate a single file. |
59 """Annotate a single file. |
56 |
60 |
57 `fr` is the FileReporter for the file to annotate. |
61 `fr` is the FileReporter for the file to annotate. |
60 statements = sorted(analysis.statements) |
64 statements = sorted(analysis.statements) |
61 missing = sorted(analysis.missing) |
65 missing = sorted(analysis.missing) |
62 excluded = sorted(analysis.excluded) |
66 excluded = sorted(analysis.excluded) |
63 |
67 |
64 if self.directory: |
68 if self.directory: |
|
69 ensure_dir(self.directory) |
65 dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename())) |
70 dest_file = os.path.join(self.directory, flat_rootname(fr.relative_filename())) |
66 if dest_file.endswith("_py"): |
71 if dest_file.endswith("_py"): |
67 dest_file = dest_file[:-3] + ".py" |
72 dest_file = dest_file[:-3] + ".py" |
68 dest_file += ",cover" |
73 dest_file += ",cover" |
69 else: |
74 else: |