3
3
4
4
"""XML reporting for coverage.py"""
5
5
6
+ from __future__ import annotations
7
+
6
8
import os
7
9
import os .path
8
10
import sys
9
11
import time
10
12
import xml .dom .minidom
11
13
14
+ from dataclasses import dataclass
15
+ from typing import Dict , IO , Iterable , Optional , TYPE_CHECKING , cast
16
+
12
17
from coverage import __url__ , __version__ , files
13
18
from coverage .misc import isolate_module , human_sorted , human_sorted_items
19
+ from coverage .plugin import FileReporter
14
20
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
15
26
16
27
os = isolate_module (os )
17
28
18
29
19
30
DTD_URL = 'https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd'
20
31
21
32
22
- def rate (hit , num ) :
33
+ def rate (hit : int , num : int ) -> str :
23
34
"""Return the fraction of `hit`/`num`, as a string."""
24
35
if num == 0 :
25
36
return "1"
26
37
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
28
49
29
50
30
51
class XmlReporter :
31
52
"""A reporter for writing Cobertura-style XML coverage results."""
32
53
33
54
report_type = "XML report"
34
55
35
- def __init__ (self , coverage ) :
56
+ def __init__ (self , coverage : Coverage ) -> None :
36
57
self .coverage = coverage
37
58
self .config = self .coverage .config
38
59
@@ -43,10 +64,10 @@ def __init__(self, coverage):
43
64
if not self .config .relative_files :
44
65
src = files .canonical_filename (src )
45
66
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
48
69
49
- def report (self , morfs , outfile = None ):
70
+ def report (self , morfs : Optional [ Iterable [ TMorf ]] , outfile : Optional [ IO [ str ]] = None ) -> float :
50
71
"""Generate a Cobertura-compatible XML report for `morfs`.
51
72
52
73
`morfs` is a list of modules or file names.
@@ -60,6 +81,7 @@ def report(self, morfs, outfile=None):
60
81
61
82
# Create the DOM that will store the data.
62
83
impl = xml .dom .minidom .getDOMImplementation ()
84
+ assert impl is not None
63
85
self .xml_out = impl .createDocument (None , "coverage" , None )
64
86
65
87
# Write header stuff.
@@ -93,26 +115,25 @@ def report(self, morfs, outfile=None):
93
115
94
116
# Populate the XML DOM with the package info.
95
117
for pkg_name , pkg_data in human_sorted_items (self .packages .items ()):
96
- class_elts , lhits , lnum , bhits , bnum = pkg_data
97
118
xpackage = self .xml_out .createElement ("package" )
98
119
xpackages .appendChild (xpackage )
99
120
xclasses = self .xml_out .createElement ("classes" )
100
121
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 ()):
102
123
xclasses .appendChild (class_elt )
103
124
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 ))
105
126
if has_arcs :
106
- branch_rate = rate (bhits , bnum )
127
+ branch_rate = rate (pkg_data . br_hits , pkg_data . branches )
107
128
else :
108
129
branch_rate = "0"
109
130
xpackage .setAttribute ("branch-rate" , branch_rate )
110
131
xpackage .setAttribute ("complexity" , "0" )
111
132
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
116
137
117
138
xcoverage .setAttribute ("lines-valid" , str (lnum_tot ))
118
139
xcoverage .setAttribute ("lines-covered" , str (lhits_tot ))
@@ -138,7 +159,7 @@ def report(self, morfs, outfile=None):
138
159
pct = 100.0 * (lhits_tot + bhits_tot ) / denom
139
160
return pct
140
161
141
- def xml_file (self , fr , analysis , has_arcs ) :
162
+ def xml_file (self , fr : FileReporter , analysis : Analysis , has_arcs : bool ) -> None :
142
163
"""Add to the XML report for a single file."""
143
164
144
165
if self .config .skip_empty :
@@ -162,9 +183,9 @@ def xml_file(self, fr, analysis, has_arcs):
162
183
dirname = "/" .join (dirname .split ("/" )[:self .config .xml_package_depth ])
163
184
package_name = dirname .replace ("/" , "." )
164
185
165
- package = self .packages .setdefault (package_name , [ {}, 0 , 0 , 0 , 0 ] )
186
+ package = self .packages .setdefault (package_name , PackageData ( {}, 0 , 0 , 0 , 0 ) )
166
187
167
- xclass = self .xml_out .createElement ("class" )
188
+ xclass : xml . dom . minidom . Element = self .xml_out .createElement ("class" )
168
189
169
190
xclass .appendChild (self .xml_out .createElement ("methods" ))
170
191
@@ -208,8 +229,8 @@ def xml_file(self, fr, analysis, has_arcs):
208
229
missing_branches = sum (t - k for t , k in branch_stats .values ())
209
230
class_br_hits = class_branches - missing_branches
210
231
else :
211
- class_branches = 0.0
212
- class_br_hits = 0.0
232
+ class_branches = 0
233
+ class_br_hits = 0
213
234
214
235
# Finalize the statistics that are collected in the XML DOM.
215
236
xclass .setAttribute ("line-rate" , rate (class_hits , class_lines ))
@@ -219,13 +240,13 @@ def xml_file(self, fr, analysis, has_arcs):
219
240
branch_rate = "0"
220
241
xclass .setAttribute ("branch-rate" , branch_rate )
221
242
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
227
248
228
249
229
- def serialize_xml (dom ) :
250
+ def serialize_xml (dom : xml . dom . minidom . Document ) -> str :
230
251
"""Serialize a minidom node to XML."""
231
- return dom .toprettyxml ()
252
+ return cast ( str , dom .toprettyxml () )
0 commit comments