diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2023-01-01 20:32:43 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2023-01-01 20:32:43 -0500 |
commit | 5580cf8a97841f0a385ebd048aa389f942d316f5 (patch) | |
tree | ae4b135d9f29892229b035acf62240aabd7dac2f /coverage/xmlreport.py | |
parent | 0c9b5e0e9da9c2cffd50db7b28142d22d0f66cee (diff) | |
download | python-coveragepy-git-5580cf8a97841f0a385ebd048aa389f942d316f5.tar.gz |
mypy: xmlreport.py
Diffstat (limited to 'coverage/xmlreport.py')
-rw-r--r-- | coverage/xmlreport.py | 73 |
1 files changed, 47 insertions, 26 deletions
diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 5eb940bf..19a8dba5 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -3,15 +3,26 @@ """XML reporting for coverage.py""" +from __future__ import annotations + import os import os.path import sys import time import xml.dom.minidom +from dataclasses import dataclass +from typing import Dict, IO, Iterable, Optional, TYPE_CHECKING, cast + from coverage import __url__, __version__, files from coverage.misc import isolate_module, human_sorted, human_sorted_items +from coverage.plugin import FileReporter from coverage.report import get_analysis_to_report +from coverage.results import Analysis +from coverage.types import TMorf + +if TYPE_CHECKING: + from coverage import Coverage os = isolate_module(os) @@ -19,12 +30,22 @@ os = isolate_module(os) DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd' -def rate(hit, num): +def rate(hit: int, num: int) -> str: """Return the fraction of `hit`/`num`, as a string.""" if num == 0: return "1" else: - return "%.4g" % (float(hit) / num) + return "%.4g" % (hit / num) + + +@dataclass +class PackageData: + """Data we keep about each "package" (in Java terms).""" + elements: Dict[str, xml.dom.minidom.Element] + hits: int + lines: int + br_hits: int + branches: int class XmlReporter: @@ -32,7 +53,7 @@ class XmlReporter: report_type = "XML report" - def __init__(self, coverage): + def __init__(self, coverage: Coverage) -> None: self.coverage = coverage self.config = self.coverage.config @@ -43,10 +64,10 @@ class XmlReporter: if not self.config.relative_files: src = files.canonical_filename(src) self.source_paths.add(src) - self.packages = {} - self.xml_out = None + self.packages: Dict[str, PackageData] = {} + self.xml_out: xml.dom.minidom.Document - def report(self, morfs, outfile=None): + def report(self, morfs: Optional[Iterable[TMorf]], outfile: Optional[IO[str]]=None) -> float: """Generate a Cobertura-compatible XML report for `morfs`. `morfs` is a list of modules or file names. @@ -60,6 +81,7 @@ class XmlReporter: # Create the DOM that will store the data. impl = xml.dom.minidom.getDOMImplementation() + assert impl is not None self.xml_out = impl.createDocument(None, "coverage", None) # Write header stuff. @@ -93,26 +115,25 @@ class XmlReporter: # Populate the XML DOM with the package info. for pkg_name, pkg_data in human_sorted_items(self.packages.items()): - class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement("package") xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement("classes") xpackage.appendChild(xclasses) - for _, class_elt in human_sorted_items(class_elts.items()): + for _, class_elt in human_sorted_items(pkg_data.elements.items()): xclasses.appendChild(class_elt) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) - xpackage.setAttribute("line-rate", rate(lhits, lnum)) + xpackage.setAttribute("line-rate", rate(pkg_data.hits, pkg_data.lines)) if has_arcs: - branch_rate = rate(bhits, bnum) + branch_rate = rate(pkg_data.br_hits, pkg_data.branches) else: branch_rate = "0" xpackage.setAttribute("branch-rate", branch_rate) xpackage.setAttribute("complexity", "0") - lnum_tot += lnum - lhits_tot += lhits - bnum_tot += bnum - bhits_tot += bhits + lhits_tot += pkg_data.hits + lnum_tot += pkg_data.lines + bhits_tot += pkg_data.br_hits + bnum_tot += pkg_data.branches xcoverage.setAttribute("lines-valid", str(lnum_tot)) xcoverage.setAttribute("lines-covered", str(lhits_tot)) @@ -138,7 +159,7 @@ class XmlReporter: pct = 100.0 * (lhits_tot + bhits_tot) / denom return pct - def xml_file(self, fr, analysis, has_arcs): + def xml_file(self, fr: FileReporter, analysis: Analysis, has_arcs: bool) -> None: """Add to the XML report for a single file.""" if self.config.skip_empty: @@ -162,9 +183,9 @@ class XmlReporter: dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth]) package_name = dirname.replace("/", ".") - package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) + package = self.packages.setdefault(package_name, PackageData({}, 0, 0, 0, 0)) - xclass = self.xml_out.createElement("class") + xclass: xml.dom.minidom.Element = self.xml_out.createElement("class") xclass.appendChild(self.xml_out.createElement("methods")) @@ -208,8 +229,8 @@ class XmlReporter: missing_branches = sum(t - k for t, k in branch_stats.values()) class_br_hits = class_branches - missing_branches else: - class_branches = 0.0 - class_br_hits = 0.0 + class_branches = 0 + class_br_hits = 0 # Finalize the statistics that are collected in the XML DOM. xclass.setAttribute("line-rate", rate(class_hits, class_lines)) @@ -219,13 +240,13 @@ class XmlReporter: branch_rate = "0" xclass.setAttribute("branch-rate", branch_rate) - package[0][rel_name] = xclass - package[1] += class_hits - package[2] += class_lines - package[3] += class_br_hits - package[4] += class_branches + package.elements[rel_name] = xclass + package.hits += class_hits + package.lines += class_lines + package.br_hits += class_br_hits + package.branches += class_branches -def serialize_xml(dom): +def serialize_xml(dom: xml.dom.minidom.Document) -> str: """Serialize a minidom node to XML.""" - return dom.toprettyxml() + return cast(str, dom.toprettyxml()) |