Coverage for coverage / report.py: 100.000%
144 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
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
4"""Summary reporting"""
6from __future__ import annotations 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
8import sys 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
9from collections.abc import Iterable 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
10from typing import IO, TYPE_CHECKING, Any 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
12from coverage.exceptions import ConfigError, NoDataError 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
13from coverage.misc import human_sorted_items, plural 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
14from coverage.plugin import FileReporter 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
15from coverage.report_core import get_analysis_to_report 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
16from coverage.results import Analysis, Numbers 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
17from coverage.types import TMorf 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
19if TYPE_CHECKING: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
20 from coverage import Coverage
23class SummaryReporter: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
24 """A reporter for writing the summary report."""
26 def __init__(self, coverage: Coverage) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
27 self.coverage = coverage 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
28 self.config = self.coverage.config 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
29 self.branches = coverage.get_data().has_arcs() 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
30 self.outfile: IO[str] | None = None 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
31 self.output_format = self.config.format or "text" 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
32 if self.output_format not in {"text", "markdown", "total"}: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
33 raise ConfigError(f"Unknown report format choice: {self.output_format!r}") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
34 self.fr_analyses: list[tuple[FileReporter, Analysis]] = [] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
35 self.skipped_count = 0 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
36 self.empty_count = 0 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
37 self.total = Numbers(precision=self.config.precision) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
39 def write(self, line: str) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
40 """Write a line to the output, adding a newline."""
41 assert self.outfile is not None 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
42 self.outfile.write(line.rstrip()) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
43 self.outfile.write("\n") 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
45 def write_items(self, items: Iterable[str]) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
46 """Write a list of strings, joined together."""
47 self.write("".join(items)) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
49 def report_text( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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.
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.
63 """
64 # Prepare the formatting strings, header, and column sorting.
65 max_name = max([len(line[0]) for line in lines_values] + [5]) + 1 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
66 max_n = max(len(total_line[header.index("Cover")]) + 2, len(" Cover")) + 1 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
67 max_n = max([max_n] + [len(line[header.index("Cover")]) + 2 for line in lines_values]) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
68 formats = dict( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
78 header_str = "".join(header_items) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
79 rule = "-" * len(header_str) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
81 # Write the header
82 self.write(header_str) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
83 self.write(rule) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
85 # Write the data lines
86 formats.update( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
87 dict(
88 Cover="{:>{n}}%",
89 Missing=" {:9}",
90 )
91 )
92 for values in lines_values: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
93 self.write_items( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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 )
100 # Write a TOTAL line
101 if lines_values: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
102 self.write(rule) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
104 self.write_items( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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 )
111 for end_line in end_lines: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
112 self.write(end_line) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
114 def report_markdown( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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.
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.
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) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
131 max_name = max(max_name, len("**TOTAL**")) + 1 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
132 formats = dict( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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 ")) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
142 header_items = [formats[item].format(item, name_len=max_name, n=max_n) for item in header] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
143 header_str = "".join(header_items) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
144 rule_str = "|" + " ".join( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
145 ["- |".rjust(len(header_items[0]) - 1, "-")]
146 + ["-: |".rjust(len(item) - 1, "-") for item in header_items[1:]],
147 )
149 # Write the header
150 self.write(header_str) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
151 self.write(rule_str) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
153 # Write the data lines
154 for values in lines_values: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
155 formats.update( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
156 dict(
157 Cover="{:>{n}}% |",
158 )
159 )
160 self.write_items( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
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 )
169 # Write the TOTAL line
170 formats.update( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
171 dict(
172 Name="|{:{name_len}} |",
173 Cover="{:>{n}} |",
174 ),
175 )
176 total_line_items: list[str] = [] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
177 for item, value in zip(header, total_line): 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
178 if value == "": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
179 insert = value 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST5UVWXYZ01234
180 elif item == "Cover": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
181 insert = f" **{value}%**" 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
182 else:
183 insert = f" **{value}**" 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
184 total_line_items += formats[item].format(insert, name_len=max_name, n=max_n) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
185 self.write_items(total_line_items) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
187 for end_line in end_lines: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
188 self.write(end_line) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
190 def report(self, morfs: Iterable[TMorf] | None, outfile: IO[str] | None = None) -> float: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
191 """Writes a report summarizing coverage statistics per module.
193 `outfile` is a text-mode file object to write the summary to.
195 """
196 self.outfile = outfile or sys.stdout 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
198 self.coverage.get_data().set_query_contexts(self.config.report_contexts) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
199 for fr, analysis in get_analysis_to_report(self.coverage, morfs): 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
200 self.report_one_file(fr, analysis) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
202 if not self.total.n_files and not self.skipped_count: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
203 raise NoDataError("No data to report.") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
205 if self.output_format == "total": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
206 self.write(self.total.pc_covered_str) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
207 else:
208 self.tabular_report() 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
210 return self.total.pc_covered 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
212 def tabular_report(self) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
213 """Writes tabular report formats."""
214 # Prepare the header line and column sorting.
215 header = ["Name", "Stmts", "Miss"] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
216 if self.branches: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
217 header += ["Branch", "BrPart"] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
218 header += ["Cover"] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
219 if self.config.show_missing: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
220 header += ["Missing"] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
222 column_order = dict(name=0, stmts=1, miss=2, cover=-1) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
223 if self.branches: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
224 column_order.update(dict(branch=3, brpart=4)) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
226 # `lines_values` is list of lists of sortable values.
227 lines_values = [] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
229 for fr, analysis in self.fr_analyses: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
230 nums = analysis.numbers 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
231 args = [fr.relative_filename(), nums.n_statements, nums.n_missing] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
232 if self.branches: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
233 args += [nums.n_branches, nums.n_partial_branches] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
234 args += [nums.pc_covered_str] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
235 if self.config.show_missing: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
236 args += [analysis.missing_formatted(branches=True)] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
237 args += [nums.pc_covered] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
238 lines_values.append(args) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
240 # Line sorting.
241 sort_option = (self.config.sort or "name").lower() 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
242 reverse = False 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
243 if sort_option[0] == "-": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
244 reverse = True 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
245 sort_option = sort_option[1:] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
246 elif sort_option[0] == "+": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
247 sort_option = sort_option[1:] 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
248 sort_idx = column_order.get(sort_option) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
249 if sort_idx is None: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
250 raise ConfigError(f"Invalid sorting option: {self.config.sort!r}") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
251 if sort_option == "name": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
252 lines_values = human_sorted_items(lines_values, reverse=reverse) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
253 else:
254 lines_values.sort( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
255 key=lambda line: (line[sort_idx], line[0]),
256 reverse=reverse,
257 )
259 # Calculate total if we had at least one file.
260 total_line = ["TOTAL", self.total.n_statements, self.total.n_missing] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
261 if self.branches: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
262 total_line += [self.total.n_branches, self.total.n_partial_branches] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
263 total_line += [self.total.pc_covered_str] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
264 if self.config.show_missing: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
265 total_line += [""] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
267 # Create other final lines.
268 end_lines = [] 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
269 if self.config.skip_covered and self.skipped_count: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
270 files = plural(self.skipped_count, "file") 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
271 end_lines.append( 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
272 f"\n{self.skipped_count} {files} skipped due to complete coverage.",
273 )
274 if self.config.skip_empty and self.empty_count: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
275 files = plural(self.empty_count, "file") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
276 end_lines.append(f"\n{self.empty_count} empty {files} skipped.") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
278 if self.output_format == "markdown": 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
279 formatter = self.report_markdown 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
280 else:
281 formatter = self.report_text 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
282 formatter(header, lines_values, total_line, end_lines) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
284 def report_one_file(self, fr: FileReporter, analysis: Analysis) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
285 """Report on just one file, the callback from report()."""
286 nums = analysis.numbers 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
287 self.total += nums 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
289 no_missing_lines = (nums.n_missing == 0) # fmt: skip 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
290 no_missing_branches = (nums.n_partial_branches == 0) # fmt: skip 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
291 if self.config.skip_covered and no_missing_lines and no_missing_branches: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
292 # Don't report on 100% files.
293 self.skipped_count += 1 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
294 elif self.config.skip_empty and nums.n_statements == 0: 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234
295 # Don't report on empty files.
296 self.empty_count += 1 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
297 else:
298 self.fr_analyses.append((fr, analysis)) 1abcdefghijklmnopqrstuvwxyzABCDEF6GH7IJ8KL9MN!OP#QR$ST5UV%WX'YZ(01)234