Skip to content

Commit 5580cf8

Browse files
committed
mypy: xmlreport.py
1 parent 0c9b5e0 commit 5580cf8

File tree

2 files changed

+48
-27
lines changed

2 files changed

+48
-27
lines changed

coverage/xmlreport.py

+47-26
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,57 @@
33

44
"""XML reporting for coverage.py"""
55

6+
from __future__ import annotations
7+
68
import os
79
import os.path
810
import sys
911
import time
1012
import xml.dom.minidom
1113

14+
from dataclasses import dataclass
15+
from typing import Dict, IO, Iterable, Optional, TYPE_CHECKING, cast
16+
1217
from coverage import __url__, __version__, files
1318
from coverage.misc import isolate_module, human_sorted, human_sorted_items
19+
from coverage.plugin import FileReporter
1420
from coverage.report import get_analysis_to_report
21+
from coverage.results import Analysis
22+
from coverage.types import TMorf
23+
24+
if TYPE_CHECKING:
25+
from coverage import Coverage
1526

1627
os = isolate_module(os)
1728

1829

1930
DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd'
2031

2132

22-
def rate(hit, num):
33+
def rate(hit: int, num: int) -> str:
2334
"""Return the fraction of `hit`/`num`, as a string."""
2435
if num == 0:
2536
return "1"
2637
else:
27-
return "%.4g" % (float(hit) / num)
38+
return "%.4g" % (hit / num)
39+
40+
41+
@dataclass
42+
class PackageData:
43+
"""Data we keep about each "package" (in Java terms)."""
44+
elements: Dict[str, xml.dom.minidom.Element]
45+
hits: int
46+
lines: int
47+
br_hits: int
48+
branches: int
2849

2950

3051
class XmlReporter:
3152
"""A reporter for writing Cobertura-style XML coverage results."""
3253

3354
report_type = "XML report"
3455

35-
def __init__(self, coverage):
56+
def __init__(self, coverage: Coverage) -> None:
3657
self.coverage = coverage
3758
self.config = self.coverage.config
3859

@@ -43,10 +64,10 @@ def __init__(self, coverage):
4364
if not self.config.relative_files:
4465
src = files.canonical_filename(src)
4566
self.source_paths.add(src)
46-
self.packages = {}
47-
self.xml_out = None
67+
self.packages: Dict[str, PackageData] = {}
68+
self.xml_out: xml.dom.minidom.Document
4869

49-
def report(self, morfs, outfile=None):
70+
def report(self, morfs: Optional[Iterable[TMorf]], outfile: Optional[IO[str]]=None) -> float:
5071
"""Generate a Cobertura-compatible XML report for `morfs`.
5172
5273
`morfs` is a list of modules or file names.
@@ -60,6 +81,7 @@ def report(self, morfs, outfile=None):
6081

6182
# Create the DOM that will store the data.
6283
impl = xml.dom.minidom.getDOMImplementation()
84+
assert impl is not None
6385
self.xml_out = impl.createDocument(None, "coverage", None)
6486

6587
# Write header stuff.
@@ -93,26 +115,25 @@ def report(self, morfs, outfile=None):
93115

94116
# Populate the XML DOM with the package info.
95117
for pkg_name, pkg_data in human_sorted_items(self.packages.items()):
96-
class_elts, lhits, lnum, bhits, bnum = pkg_data
97118
xpackage = self.xml_out.createElement("package")
98119
xpackages.appendChild(xpackage)
99120
xclasses = self.xml_out.createElement("classes")
100121
xpackage.appendChild(xclasses)
101-
for _, class_elt in human_sorted_items(class_elts.items()):
122+
for _, class_elt in human_sorted_items(pkg_data.elements.items()):
102123
xclasses.appendChild(class_elt)
103124
xpackage.setAttribute("name", pkg_name.replace(os.sep, '.'))
104-
xpackage.setAttribute("line-rate", rate(lhits, lnum))
125+
xpackage.setAttribute("line-rate", rate(pkg_data.hits, pkg_data.lines))
105126
if has_arcs:
106-
branch_rate = rate(bhits, bnum)
127+
branch_rate = rate(pkg_data.br_hits, pkg_data.branches)
107128
else:
108129
branch_rate = "0"
109130
xpackage.setAttribute("branch-rate", branch_rate)
110131
xpackage.setAttribute("complexity", "0")
111132

112-
lnum_tot += lnum
113-
lhits_tot += lhits
114-
bnum_tot += bnum
115-
bhits_tot += bhits
133+
lhits_tot += pkg_data.hits
134+
lnum_tot += pkg_data.lines
135+
bhits_tot += pkg_data.br_hits
136+
bnum_tot += pkg_data.branches
116137

117138
xcoverage.setAttribute("lines-valid", str(lnum_tot))
118139
xcoverage.setAttribute("lines-covered", str(lhits_tot))
@@ -138,7 +159,7 @@ def report(self, morfs, outfile=None):
138159
pct = 100.0 * (lhits_tot + bhits_tot) / denom
139160
return pct
140161

141-
def xml_file(self, fr, analysis, has_arcs):
162+
def xml_file(self, fr: FileReporter, analysis: Analysis, has_arcs: bool) -> None:
142163
"""Add to the XML report for a single file."""
143164

144165
if self.config.skip_empty:
@@ -162,9 +183,9 @@ def xml_file(self, fr, analysis, has_arcs):
162183
dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth])
163184
package_name = dirname.replace("/", ".")
164185

165-
package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0])
186+
package = self.packages.setdefault(package_name, PackageData({}, 0, 0, 0, 0))
166187

167-
xclass = self.xml_out.createElement("class")
188+
xclass: xml.dom.minidom.Element = self.xml_out.createElement("class")
168189

169190
xclass.appendChild(self.xml_out.createElement("methods"))
170191

@@ -208,8 +229,8 @@ def xml_file(self, fr, analysis, has_arcs):
208229
missing_branches = sum(t - k for t, k in branch_stats.values())
209230
class_br_hits = class_branches - missing_branches
210231
else:
211-
class_branches = 0.0
212-
class_br_hits = 0.0
232+
class_branches = 0
233+
class_br_hits = 0
213234

214235
# Finalize the statistics that are collected in the XML DOM.
215236
xclass.setAttribute("line-rate", rate(class_hits, class_lines))
@@ -219,13 +240,13 @@ def xml_file(self, fr, analysis, has_arcs):
219240
branch_rate = "0"
220241
xclass.setAttribute("branch-rate", branch_rate)
221242

222-
package[0][rel_name] = xclass
223-
package[1] += class_hits
224-
package[2] += class_lines
225-
package[3] += class_br_hits
226-
package[4] += class_branches
243+
package.elements[rel_name] = xclass
244+
package.hits += class_hits
245+
package.lines += class_lines
246+
package.br_hits += class_br_hits
247+
package.branches += class_branches
227248

228249

229-
def serialize_xml(dom):
250+
def serialize_xml(dom: xml.dom.minidom.Document) -> str:
230251
"""Serialize a minidom node to XML."""
231-
return dom.toprettyxml()
252+
return cast(str, dom.toprettyxml())

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ setenv =
100100
C_DE=coverage/data.py coverage/disposition.py coverage/env.py coverage/exceptions.py
101101
C_FN=coverage/files.py coverage/inorout.py coverage/jsonreport.py coverage/lcovreport.py coverage/multiproc.py coverage/numbits.py
102102
C_OP=coverage/parser.py coverage/phystokens.py coverage/plugin.py coverage/plugin_support.py coverage/python.py
103-
C_QZ=coverage/report.py coverage/results.py coverage/sqldata.py coverage/tomlconfig.py coverage/types.py coverage/version.py
103+
C_QZ=coverage/report.py coverage/results.py coverage/sqldata.py coverage/tomlconfig.py coverage/types.py coverage/version.py coverage/xmlreport.py
104104
T_AN=tests/test_api.py tests/test_cmdline.py tests/goldtest.py tests/helpers.py tests/test_html.py
105105
TYPEABLE={env:C__B} {env:C_CC} {env:C_DE} {env:C_FN} {env:C_OP} {env:C_QZ} {env:T_AN}
106106

0 commit comments

Comments
 (0)