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://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
3 |
3 |
4 """Reporter foundation for coverage.py.""" |
4 """Reporter foundation for coverage.py.""" |
|
5 |
5 import sys |
6 import sys |
6 |
7 |
7 from coverage import env |
8 from coverage.exceptions import CoverageException, NotPython |
8 from coverage.files import prep_patterns, FnmatchMatcher |
9 from coverage.files import prep_patterns, FnmatchMatcher |
9 from coverage.misc import CoverageException, NoSource, NotPython, ensure_dir_for_file, file_be_gone |
10 from coverage.misc import ensure_dir_for_file, file_be_gone |
10 |
11 |
11 |
12 |
12 def render_report(output_path, reporter, morfs): |
13 def render_report(output_path, reporter, morfs, msgfn): |
13 """Run the provided reporter ensuring any required setup and cleanup is done |
14 """Run a one-file report generator, managing the output file. |
14 |
15 |
15 At a high level this method ensures the output file is ready to be written to. Then writes the |
16 This function ensures the output file is ready to be written to. Then writes |
16 report to it. Then closes the file and deletes any garbage created if necessary. |
17 the report to it. Then closes the file and cleans up. |
|
18 |
17 """ |
19 """ |
18 file_to_close = None |
20 file_to_close = None |
19 delete_file = False |
21 delete_file = False |
20 if output_path: |
22 |
21 if output_path == '-': |
23 if output_path == "-": |
22 outfile = sys.stdout |
24 outfile = sys.stdout |
23 else: |
25 else: |
24 # Ensure that the output directory is created; done here |
26 # Ensure that the output directory is created; done here |
25 # because this report pre-opens the output file. |
27 # because this report pre-opens the output file. |
26 # HTMLReport does this using the Report plumbing because |
28 # HTMLReport does this using the Report plumbing because |
27 # its task is more complex, being multiple files. |
29 # its task is more complex, being multiple files. |
28 ensure_dir_for_file(output_path) |
30 ensure_dir_for_file(output_path) |
29 open_kwargs = {} |
31 outfile = open(output_path, "w", encoding="utf-8") |
30 if env.PY3: |
32 file_to_close = outfile |
31 open_kwargs['encoding'] = 'utf8' |
33 |
32 outfile = open(output_path, "w", **open_kwargs) |
|
33 file_to_close = outfile |
|
34 try: |
34 try: |
35 return reporter.report(morfs, outfile=outfile) |
35 return reporter.report(morfs, outfile=outfile) |
36 except CoverageException: |
36 except CoverageException: |
37 delete_file = True |
37 delete_file = True |
38 raise |
38 raise |
39 finally: |
39 finally: |
40 if file_to_close: |
40 if file_to_close: |
41 file_to_close.close() |
41 file_to_close.close() |
42 if delete_file: |
42 if delete_file: |
43 file_be_gone(output_path) |
43 file_be_gone(output_path) # pragma: part covered (doesn't return) |
|
44 else: |
|
45 msgfn(f"Wrote {reporter.report_type} to {output_path}") |
44 |
46 |
45 |
47 |
46 def get_analysis_to_report(coverage, morfs): |
48 def get_analysis_to_report(coverage, morfs): |
47 """Get the files to report on. |
49 """Get the files to report on. |
48 |
50 |
53 """ |
55 """ |
54 file_reporters = coverage._get_file_reporters(morfs) |
56 file_reporters = coverage._get_file_reporters(morfs) |
55 config = coverage.config |
57 config = coverage.config |
56 |
58 |
57 if config.report_include: |
59 if config.report_include: |
58 matcher = FnmatchMatcher(prep_patterns(config.report_include)) |
60 matcher = FnmatchMatcher(prep_patterns(config.report_include), "report_include") |
59 file_reporters = [fr for fr in file_reporters if matcher.match(fr.filename)] |
61 file_reporters = [fr for fr in file_reporters if matcher.match(fr.filename)] |
60 |
62 |
61 if config.report_omit: |
63 if config.report_omit: |
62 matcher = FnmatchMatcher(prep_patterns(config.report_omit)) |
64 matcher = FnmatchMatcher(prep_patterns(config.report_omit), "report_omit") |
63 file_reporters = [fr for fr in file_reporters if not matcher.match(fr.filename)] |
65 file_reporters = [fr for fr in file_reporters if not matcher.match(fr.filename)] |
64 |
66 |
65 if not file_reporters: |
67 if not file_reporters: |
66 raise CoverageException("No data to report.") |
68 raise CoverageException("No data to report.") |
67 |
69 |
68 for fr in sorted(file_reporters): |
70 for fr in sorted(file_reporters): |
69 try: |
71 try: |
70 analysis = coverage._analyze(fr) |
72 analysis = coverage._analyze(fr) |
71 except NoSource: |
|
72 if not config.ignore_errors: |
|
73 raise |
|
74 except NotPython: |
73 except NotPython: |
75 # Only report errors for .py files, and only if we didn't |
74 # Only report errors for .py files, and only if we didn't |
76 # explicitly suppress those errors. |
75 # explicitly suppress those errors. |
77 # NotPython is only raised by PythonFileReporter, which has a |
76 # NotPython is only raised by PythonFileReporter, which has a |
78 # should_be_python() method. |
77 # should_be_python() method. |
79 if fr.should_be_python(): |
78 if fr.should_be_python(): |
80 if config.ignore_errors: |
79 if config.ignore_errors: |
81 msg = "Couldn't parse Python file '{}'".format(fr.filename) |
80 msg = f"Couldn't parse Python file '{fr.filename}'" |
82 coverage._warn(msg, slug="couldnt-parse") |
81 coverage._warn(msg, slug="couldnt-parse") |
83 else: |
82 else: |
84 raise |
83 raise |
|
84 except Exception as exc: |
|
85 if config.ignore_errors: |
|
86 msg = f"Couldn't parse '{fr.filename}': {exc}".rstrip() |
|
87 coverage._warn(msg, slug="couldnt-parse") |
|
88 else: |
|
89 raise |
85 else: |
90 else: |
86 yield (fr, analysis) |
91 yield (fr, analysis) |