coverage 7.13.1__cp313-cp313-musllinux_1_2_riscv64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- a1_coverage.pth +1 -0
- coverage/__init__.py +38 -0
- coverage/__main__.py +12 -0
- coverage/annotate.py +113 -0
- coverage/bytecode.py +197 -0
- coverage/cmdline.py +1220 -0
- coverage/collector.py +487 -0
- coverage/config.py +732 -0
- coverage/context.py +74 -0
- coverage/control.py +1514 -0
- coverage/core.py +139 -0
- coverage/data.py +251 -0
- coverage/debug.py +669 -0
- coverage/disposition.py +59 -0
- coverage/env.py +135 -0
- coverage/exceptions.py +85 -0
- coverage/execfile.py +329 -0
- coverage/files.py +553 -0
- coverage/html.py +860 -0
- coverage/htmlfiles/coverage_html.js +735 -0
- coverage/htmlfiles/favicon_32.png +0 -0
- coverage/htmlfiles/index.html +199 -0
- coverage/htmlfiles/keybd_closed.png +0 -0
- coverage/htmlfiles/pyfile.html +149 -0
- coverage/htmlfiles/style.css +389 -0
- coverage/htmlfiles/style.scss +844 -0
- coverage/inorout.py +590 -0
- coverage/jsonreport.py +200 -0
- coverage/lcovreport.py +218 -0
- coverage/misc.py +381 -0
- coverage/multiproc.py +120 -0
- coverage/numbits.py +146 -0
- coverage/parser.py +1215 -0
- coverage/patch.py +118 -0
- coverage/phystokens.py +197 -0
- coverage/plugin.py +617 -0
- coverage/plugin_support.py +299 -0
- coverage/pth_file.py +16 -0
- coverage/py.typed +1 -0
- coverage/python.py +272 -0
- coverage/pytracer.py +370 -0
- coverage/regions.py +127 -0
- coverage/report.py +298 -0
- coverage/report_core.py +117 -0
- coverage/results.py +502 -0
- coverage/sqldata.py +1212 -0
- coverage/sqlitedb.py +226 -0
- coverage/sysmon.py +509 -0
- coverage/templite.py +319 -0
- coverage/tomlconfig.py +212 -0
- coverage/tracer.cpython-313-riscv64-linux-musl.so +0 -0
- coverage/tracer.pyi +43 -0
- coverage/types.py +214 -0
- coverage/version.py +35 -0
- coverage/xmlreport.py +263 -0
- coverage-7.13.1.dist-info/METADATA +200 -0
- coverage-7.13.1.dist-info/RECORD +61 -0
- coverage-7.13.1.dist-info/WHEEL +5 -0
- coverage-7.13.1.dist-info/entry_points.txt +4 -0
- coverage-7.13.1.dist-info/licenses/LICENSE.txt +177 -0
- coverage-7.13.1.dist-info/top_level.txt +1 -0
coverage/report.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Summary reporting"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
from typing import IO, TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from coverage.exceptions import ConfigError, NoDataError
|
|
13
|
+
from coverage.misc import human_sorted_items, plural
|
|
14
|
+
from coverage.plugin import FileReporter
|
|
15
|
+
from coverage.report_core import get_analysis_to_report
|
|
16
|
+
from coverage.results import Analysis, Numbers
|
|
17
|
+
from coverage.types import TMorfs
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from coverage import Coverage
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SummaryReporter:
|
|
24
|
+
"""A reporter for writing the summary report."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, coverage: Coverage) -> None:
|
|
27
|
+
self.coverage = coverage
|
|
28
|
+
self.config = self.coverage.config
|
|
29
|
+
self.branches = coverage.get_data().has_arcs()
|
|
30
|
+
self.outfile: IO[str] | None = None
|
|
31
|
+
self.output_format = self.config.format or "text"
|
|
32
|
+
if self.output_format not in {"text", "markdown", "total"}:
|
|
33
|
+
raise ConfigError(f"Unknown report format choice: {self.output_format!r}")
|
|
34
|
+
self.fr_analyses: list[tuple[FileReporter, Analysis]] = []
|
|
35
|
+
self.skipped_count = 0
|
|
36
|
+
self.empty_count = 0
|
|
37
|
+
self.total = Numbers(precision=self.config.precision)
|
|
38
|
+
|
|
39
|
+
def write(self, line: str) -> None:
|
|
40
|
+
"""Write a line to the output, adding a newline."""
|
|
41
|
+
assert self.outfile is not None
|
|
42
|
+
self.outfile.write(line.rstrip())
|
|
43
|
+
self.outfile.write("\n")
|
|
44
|
+
|
|
45
|
+
def write_items(self, items: Iterable[str]) -> None:
|
|
46
|
+
"""Write a list of strings, joined together."""
|
|
47
|
+
self.write("".join(items))
|
|
48
|
+
|
|
49
|
+
def report_text(
|
|
50
|
+
self,
|
|
51
|
+
header: list[str],
|
|
52
|
+
lines_values: list[list[Any]],
|
|
53
|
+
total_line: list[Any],
|
|
54
|
+
end_lines: list[str],
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Internal method that prints report data in text format.
|
|
57
|
+
|
|
58
|
+
`header` is a list with captions.
|
|
59
|
+
`lines_values` is list of lists of sortable values.
|
|
60
|
+
`total_line` is a list with values of the total line.
|
|
61
|
+
`end_lines` is a list of ending lines with information about skipped files.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
# Prepare the formatting strings, header, and column sorting.
|
|
65
|
+
max_name = max([len(line[0]) for line in lines_values] + [5]) + 1
|
|
66
|
+
max_n = max(len(total_line[header.index("Cover")]) + 2, len(" Cover")) + 1
|
|
67
|
+
max_n = max([max_n] + [len(line[header.index("Cover")]) + 2 for line in lines_values])
|
|
68
|
+
formats = dict(
|
|
69
|
+
Name="{:{name_len}}",
|
|
70
|
+
Stmts="{:>7}",
|
|
71
|
+
Miss="{:>7}",
|
|
72
|
+
Branch="{:>7}",
|
|
73
|
+
BrPart="{:>7}",
|
|
74
|
+
Cover="{:>{n}}",
|
|
75
|
+
Missing="{:>10}",
|
|
76
|
+
)
|
|
77
|
+
header_items = [formats[item].format(item, name_len=max_name, n=max_n) for item in header]
|
|
78
|
+
header_str = "".join(header_items)
|
|
79
|
+
rule = "-" * len(header_str)
|
|
80
|
+
|
|
81
|
+
# Write the header
|
|
82
|
+
self.write(header_str)
|
|
83
|
+
self.write(rule)
|
|
84
|
+
|
|
85
|
+
# Write the data lines
|
|
86
|
+
formats.update(
|
|
87
|
+
dict(
|
|
88
|
+
Cover="{:>{n}}%",
|
|
89
|
+
Missing=" {:9}",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
for values in lines_values:
|
|
93
|
+
self.write_items(
|
|
94
|
+
(
|
|
95
|
+
formats[item].format(str(value), name_len=max_name, n=max_n - 1)
|
|
96
|
+
for item, value in zip(header, values)
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Write a TOTAL line
|
|
101
|
+
if lines_values:
|
|
102
|
+
self.write(rule)
|
|
103
|
+
|
|
104
|
+
self.write_items(
|
|
105
|
+
(
|
|
106
|
+
formats[item].format(str(value), name_len=max_name, n=max_n - 1)
|
|
107
|
+
for item, value in zip(header, total_line)
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
for end_line in end_lines:
|
|
112
|
+
self.write(end_line)
|
|
113
|
+
|
|
114
|
+
def report_markdown(
|
|
115
|
+
self,
|
|
116
|
+
header: list[str],
|
|
117
|
+
lines_values: list[list[Any]],
|
|
118
|
+
total_line: list[Any],
|
|
119
|
+
end_lines: list[str],
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Internal method that prints report data in markdown format.
|
|
122
|
+
|
|
123
|
+
`header` is a list with captions.
|
|
124
|
+
`lines_values` is a sorted list of lists containing coverage information.
|
|
125
|
+
`total_line` is a list with values of the total line.
|
|
126
|
+
`end_lines` is a list of ending lines with information about skipped files.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
# Prepare the formatting strings, header, and column sorting.
|
|
130
|
+
max_name = max((len(line[0].replace("_", "\\_")) for line in lines_values), default=0)
|
|
131
|
+
max_name = max(max_name, len("**TOTAL**")) + 1
|
|
132
|
+
formats = dict(
|
|
133
|
+
Name="| {:{name_len}}|",
|
|
134
|
+
Stmts="{:>9} |",
|
|
135
|
+
Miss="{:>9} |",
|
|
136
|
+
Branch="{:>9} |",
|
|
137
|
+
BrPart="{:>9} |",
|
|
138
|
+
Cover="{:>{n}} |",
|
|
139
|
+
Missing="{:>10} |",
|
|
140
|
+
)
|
|
141
|
+
max_n = max(len(total_line[header.index("Cover")]) + 6, len(" Cover "))
|
|
142
|
+
header_items = [formats[item].format(item, name_len=max_name, n=max_n) for item in header]
|
|
143
|
+
header_str = "".join(header_items)
|
|
144
|
+
rule_str = "|" + " ".join(
|
|
145
|
+
["- |".rjust(len(header_items[0]) - 1, "-")]
|
|
146
|
+
+ ["-: |".rjust(len(item) - 1, "-") for item in header_items[1:]],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Write the header
|
|
150
|
+
self.write(header_str)
|
|
151
|
+
self.write(rule_str)
|
|
152
|
+
|
|
153
|
+
# Write the data lines
|
|
154
|
+
for values in lines_values:
|
|
155
|
+
formats.update(
|
|
156
|
+
dict(
|
|
157
|
+
Cover="{:>{n}}% |",
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
self.write_items(
|
|
161
|
+
(
|
|
162
|
+
formats[item].format(
|
|
163
|
+
str(value).replace("_", "\\_"), name_len=max_name, n=max_n - 1
|
|
164
|
+
)
|
|
165
|
+
for item, value in zip(header, values)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Write the TOTAL line
|
|
170
|
+
formats.update(
|
|
171
|
+
dict(
|
|
172
|
+
Name="|{:{name_len}} |",
|
|
173
|
+
Cover="{:>{n}} |",
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
total_line_items: list[str] = []
|
|
177
|
+
for item, value in zip(header, total_line):
|
|
178
|
+
if value == "":
|
|
179
|
+
insert = value
|
|
180
|
+
elif item == "Cover":
|
|
181
|
+
insert = f" **{value}%**"
|
|
182
|
+
else:
|
|
183
|
+
insert = f" **{value}**"
|
|
184
|
+
total_line_items += formats[item].format(insert, name_len=max_name, n=max_n)
|
|
185
|
+
self.write_items(total_line_items)
|
|
186
|
+
|
|
187
|
+
for end_line in end_lines:
|
|
188
|
+
self.write(end_line)
|
|
189
|
+
|
|
190
|
+
def report(self, morfs: TMorfs, outfile: IO[str] | None = None) -> float:
|
|
191
|
+
"""Writes a report summarizing coverage statistics per module.
|
|
192
|
+
|
|
193
|
+
`outfile` is a text-mode file object to write the summary to.
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
self.outfile = outfile or sys.stdout
|
|
197
|
+
|
|
198
|
+
self.coverage.get_data().set_query_contexts(self.config.report_contexts)
|
|
199
|
+
for fr, analysis in get_analysis_to_report(self.coverage, morfs):
|
|
200
|
+
self.report_one_file(fr, analysis)
|
|
201
|
+
|
|
202
|
+
if not self.total.n_files and not self.skipped_count:
|
|
203
|
+
raise NoDataError("No data to report.")
|
|
204
|
+
|
|
205
|
+
if self.output_format == "total":
|
|
206
|
+
self.write(self.total.pc_covered_str)
|
|
207
|
+
else:
|
|
208
|
+
self.tabular_report()
|
|
209
|
+
|
|
210
|
+
return self.total.pc_covered
|
|
211
|
+
|
|
212
|
+
def tabular_report(self) -> None:
|
|
213
|
+
"""Writes tabular report formats."""
|
|
214
|
+
# Prepare the header line and column sorting.
|
|
215
|
+
header = ["Name", "Stmts", "Miss"]
|
|
216
|
+
if self.branches:
|
|
217
|
+
header += ["Branch", "BrPart"]
|
|
218
|
+
header += ["Cover"]
|
|
219
|
+
if self.config.show_missing:
|
|
220
|
+
header += ["Missing"]
|
|
221
|
+
|
|
222
|
+
column_order = dict(name=0, stmts=1, miss=2, cover=-1)
|
|
223
|
+
if self.branches:
|
|
224
|
+
column_order.update(dict(branch=3, brpart=4))
|
|
225
|
+
|
|
226
|
+
# `lines_values` is list of lists of sortable values.
|
|
227
|
+
lines_values = []
|
|
228
|
+
|
|
229
|
+
for fr, analysis in self.fr_analyses:
|
|
230
|
+
nums = analysis.numbers
|
|
231
|
+
args = [fr.relative_filename(), nums.n_statements, nums.n_missing]
|
|
232
|
+
if self.branches:
|
|
233
|
+
args += [nums.n_branches, nums.n_partial_branches]
|
|
234
|
+
args += [nums.pc_covered_str]
|
|
235
|
+
if self.config.show_missing:
|
|
236
|
+
args += [analysis.missing_formatted(branches=True)]
|
|
237
|
+
args += [nums.pc_covered]
|
|
238
|
+
lines_values.append(args)
|
|
239
|
+
|
|
240
|
+
# Line sorting.
|
|
241
|
+
sort_option = (self.config.sort or "name").lower()
|
|
242
|
+
reverse = False
|
|
243
|
+
if sort_option[0] == "-":
|
|
244
|
+
reverse = True
|
|
245
|
+
sort_option = sort_option[1:]
|
|
246
|
+
elif sort_option[0] == "+":
|
|
247
|
+
sort_option = sort_option[1:]
|
|
248
|
+
sort_idx = column_order.get(sort_option)
|
|
249
|
+
if sort_idx is None:
|
|
250
|
+
raise ConfigError(f"Invalid sorting option: {self.config.sort!r}")
|
|
251
|
+
if sort_option == "name":
|
|
252
|
+
lines_values = human_sorted_items(lines_values, reverse=reverse)
|
|
253
|
+
else:
|
|
254
|
+
lines_values.sort(
|
|
255
|
+
key=lambda line: (line[sort_idx], line[0]),
|
|
256
|
+
reverse=reverse,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Calculate total if we had at least one file.
|
|
260
|
+
total_line = ["TOTAL", self.total.n_statements, self.total.n_missing]
|
|
261
|
+
if self.branches:
|
|
262
|
+
total_line += [self.total.n_branches, self.total.n_partial_branches]
|
|
263
|
+
total_line += [self.total.pc_covered_str]
|
|
264
|
+
if self.config.show_missing:
|
|
265
|
+
total_line += [""]
|
|
266
|
+
|
|
267
|
+
# Create other final lines.
|
|
268
|
+
end_lines = []
|
|
269
|
+
if self.config.skip_covered and self.skipped_count:
|
|
270
|
+
files = plural(self.skipped_count, "file")
|
|
271
|
+
end_lines.append(
|
|
272
|
+
f"\n{self.skipped_count} {files} skipped due to complete coverage.",
|
|
273
|
+
)
|
|
274
|
+
if self.config.skip_empty and self.empty_count:
|
|
275
|
+
files = plural(self.empty_count, "file")
|
|
276
|
+
end_lines.append(f"\n{self.empty_count} empty {files} skipped.")
|
|
277
|
+
|
|
278
|
+
if self.output_format == "markdown":
|
|
279
|
+
formatter = self.report_markdown
|
|
280
|
+
else:
|
|
281
|
+
formatter = self.report_text
|
|
282
|
+
formatter(header, lines_values, total_line, end_lines)
|
|
283
|
+
|
|
284
|
+
def report_one_file(self, fr: FileReporter, analysis: Analysis) -> None:
|
|
285
|
+
"""Report on just one file, the callback from report()."""
|
|
286
|
+
nums = analysis.numbers
|
|
287
|
+
self.total += nums
|
|
288
|
+
|
|
289
|
+
no_missing_lines = (nums.n_missing == 0) # fmt: skip
|
|
290
|
+
no_missing_branches = (nums.n_partial_branches == 0) # fmt: skip
|
|
291
|
+
if self.config.skip_covered and no_missing_lines and no_missing_branches:
|
|
292
|
+
# Don't report on 100% files.
|
|
293
|
+
self.skipped_count += 1
|
|
294
|
+
elif self.config.skip_empty and nums.n_statements == 0:
|
|
295
|
+
# Don't report on empty files.
|
|
296
|
+
self.empty_count += 1
|
|
297
|
+
else:
|
|
298
|
+
self.fr_analyses.append((fr, analysis))
|
coverage/report_core.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Reporter foundation for coverage.py."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Callable, Iterable
|
|
10
|
+
from typing import IO, TYPE_CHECKING, Protocol
|
|
11
|
+
|
|
12
|
+
from coverage.exceptions import NoDataError, NotPython
|
|
13
|
+
from coverage.files import GlobMatcher, prep_patterns
|
|
14
|
+
from coverage.misc import ensure_dir_for_file, file_be_gone
|
|
15
|
+
from coverage.plugin import FileReporter
|
|
16
|
+
from coverage.results import Analysis
|
|
17
|
+
from coverage.types import TMorfs
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from coverage import Coverage
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Reporter(Protocol):
|
|
24
|
+
"""What we expect of reporters."""
|
|
25
|
+
|
|
26
|
+
report_type: str
|
|
27
|
+
|
|
28
|
+
def report(self, morfs: TMorfs, outfile: IO[str]) -> float:
|
|
29
|
+
"""Generate a report of `morfs`, written to `outfile`."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def render_report(
|
|
33
|
+
output_path: str,
|
|
34
|
+
reporter: Reporter,
|
|
35
|
+
morfs: TMorfs,
|
|
36
|
+
msgfn: Callable[[str], None],
|
|
37
|
+
) -> float:
|
|
38
|
+
"""Run a one-file report generator, managing the output file.
|
|
39
|
+
|
|
40
|
+
This function ensures the output file is ready to be written to. Then writes
|
|
41
|
+
the report to it. Then closes the file and cleans up.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
file_to_close = None
|
|
45
|
+
delete_file = False
|
|
46
|
+
|
|
47
|
+
if output_path == "-":
|
|
48
|
+
outfile = sys.stdout
|
|
49
|
+
else:
|
|
50
|
+
# Ensure that the output directory is created; done here because this
|
|
51
|
+
# report pre-opens the output file. HtmlReporter does this on its own
|
|
52
|
+
# because its task is more complex, being multiple files.
|
|
53
|
+
ensure_dir_for_file(output_path)
|
|
54
|
+
outfile = open(output_path, "w", encoding="utf-8")
|
|
55
|
+
file_to_close = outfile
|
|
56
|
+
delete_file = True
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
ret = reporter.report(morfs, outfile=outfile)
|
|
60
|
+
if file_to_close is not None:
|
|
61
|
+
msgfn(f"Wrote {reporter.report_type} to {output_path}")
|
|
62
|
+
delete_file = False
|
|
63
|
+
return ret
|
|
64
|
+
finally:
|
|
65
|
+
if file_to_close is not None:
|
|
66
|
+
file_to_close.close()
|
|
67
|
+
if delete_file:
|
|
68
|
+
file_be_gone(output_path) # pragma: part covered (doesn't return)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_analysis_to_report(
|
|
72
|
+
coverage: Coverage,
|
|
73
|
+
morfs: TMorfs,
|
|
74
|
+
) -> Iterable[tuple[FileReporter, Analysis]]:
|
|
75
|
+
"""Get the files to report on.
|
|
76
|
+
|
|
77
|
+
For each morf in `morfs`, if it should be reported on (based on the omit
|
|
78
|
+
and include configuration options), yield a pair, the `FileReporter` and
|
|
79
|
+
`Analysis` for the morf.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
fr_morfs = coverage._get_file_reporters(morfs)
|
|
83
|
+
config = coverage.config
|
|
84
|
+
|
|
85
|
+
if config.report_include:
|
|
86
|
+
matcher = GlobMatcher(prep_patterns(config.report_include), "report_include")
|
|
87
|
+
fr_morfs = [(fr, morf) for (fr, morf) in fr_morfs if matcher.match(fr.filename)]
|
|
88
|
+
|
|
89
|
+
if config.report_omit:
|
|
90
|
+
matcher = GlobMatcher(prep_patterns(config.report_omit), "report_omit")
|
|
91
|
+
fr_morfs = [(fr, morf) for (fr, morf) in fr_morfs if not matcher.match(fr.filename)]
|
|
92
|
+
|
|
93
|
+
if not fr_morfs:
|
|
94
|
+
raise NoDataError("No data to report.")
|
|
95
|
+
|
|
96
|
+
for fr, morf in sorted(fr_morfs):
|
|
97
|
+
try:
|
|
98
|
+
analysis = coverage._analyze(morf)
|
|
99
|
+
except NotPython:
|
|
100
|
+
# Only report errors for .py files, and only if we didn't
|
|
101
|
+
# explicitly suppress those errors.
|
|
102
|
+
# NotPython is only raised by PythonFileReporter, which has a
|
|
103
|
+
# should_be_python() method.
|
|
104
|
+
if fr.should_be_python(): # type: ignore[attr-defined]
|
|
105
|
+
if config.ignore_errors:
|
|
106
|
+
msg = f"Couldn't parse Python file '{fr.filename}'"
|
|
107
|
+
coverage._warn(msg, slug="couldnt-parse")
|
|
108
|
+
else:
|
|
109
|
+
raise
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
if config.ignore_errors:
|
|
112
|
+
msg = f"Couldn't parse '{fr.filename}': {exc}".rstrip()
|
|
113
|
+
coverage._warn(msg, slug="couldnt-parse")
|
|
114
|
+
else:
|
|
115
|
+
raise
|
|
116
|
+
else:
|
|
117
|
+
yield (fr, analysis)
|