@@ -14,21 +14,37 @@ const getParsedXml = (options) => {
14
14
return '' ;
15
15
} ;
16
16
17
+ const coveragePct = ( num , denom ) => {
18
+ // follow the same logic as coverage.py: only return 0 when there are no hits,
19
+ // only return 100% when all lines are covered, otherwise round.
20
+ // https://github.com/nedbat/coveragepy/blob/0957c07064f8cd89a2a81a0ff1e51ca4bab77c69/coverage/results.py#L276
21
+ if ( num === denom ) return '100%' ;
22
+ if ( num === 0 ) return '0%' ;
23
+ const cover = Math . max ( 1 , Math . min ( 99 , Math . round ( ( 100 * num ) / denom ) ) ) ;
24
+ return `${ cover } %` ;
25
+ } ;
26
+
17
27
const getTotalCoverage = ( parsedXml ) => {
18
28
if ( ! parsedXml ) {
19
29
return null ;
20
30
}
21
31
22
32
const coverage = parsedXml [ '$' ] ;
23
- const cover = parseInt ( parseFloat ( coverage [ 'line-rate' ] ) * 100 ) ;
24
33
const linesValid = parseInt ( coverage [ 'lines-valid' ] ) ;
25
34
const linesCovered = parseInt ( coverage [ 'lines-covered' ] ) ;
35
+ const branchesValid = parseInt ( coverage [ 'branches-valid' ] ) ;
36
+ const branchesCovered = parseInt ( coverage [ 'branches-covered' ] ) ;
26
37
27
38
return {
28
39
name : 'TOTAL' ,
29
40
stmts : linesValid ,
30
41
miss : linesValid - linesCovered ,
31
- cover : cover !== '0' ? `${ cover } %` : '0' ,
42
+ branches : branchesValid ,
43
+ partBranches : branchesValid - branchesCovered ,
44
+ cover : coveragePct (
45
+ linesCovered + branchesCovered ,
46
+ linesValid + branchesValid ,
47
+ ) ,
32
48
} ;
33
49
} ;
34
50
@@ -51,8 +67,6 @@ const getCoverageXmlReport = (options) => {
51
67
try {
52
68
const parsedXml = getParsedXml ( options ) ;
53
69
54
- const coverage = getTotalCoverage ( parsedXml ) ;
55
- // const coverage = getCoverageReportXml(getContent(options.covXmlFile));
56
70
const isValid = isValidCoverageContent ( parsedXml ) ;
57
71
58
72
if ( parsedXml && ! isValid ) {
@@ -62,6 +76,7 @@ const getCoverageXmlReport = (options) => {
62
76
63
77
if ( parsedXml && isValid ) {
64
78
const coverageObj = coverageXmlToFiles ( parsedXml ) ;
79
+ const coverage = getTotalCoverage ( parsedXml ) ;
65
80
const dataFromXml = { coverage : coverageObj , total : coverage } ;
66
81
const html = toHtml ( null , options , dataFromXml ) ;
67
82
const color = getCoverageColor ( coverage ? coverage . cover : '0' ) ;
@@ -126,32 +141,61 @@ const parseClass = (classObj) => {
126
141
return null ;
127
142
}
128
143
129
- const { stmts, missing, totalMissing : miss } = parseLines ( classObj . lines ) ;
130
- const { filename : name , 'line-rate' : lineRate } = classObj [ '$' ] ;
131
-
132
- const isFullCoverage = lineRate === '1' ;
133
- const cover = isFullCoverage
134
- ? '100%'
135
- : `${ parseInt ( parseFloat ( lineRate ) * 100 ) } %` ;
144
+ const { stmts, missingStmts, branches, partBranches, missing } = parseLines (
145
+ classObj . lines ,
146
+ ) ;
147
+ const { filename : name } = classObj [ '$' ] ;
148
+ const cover = coveragePct (
149
+ stmts + branches - missingStmts - partBranches ,
150
+ stmts + branches ,
151
+ ) ;
136
152
137
- return { name, stmts, miss, cover, missing } ;
153
+ return {
154
+ name,
155
+ stmts,
156
+ miss : missingStmts ,
157
+ branches,
158
+ partBranches,
159
+ cover,
160
+ missing,
161
+ } ;
138
162
} ;
139
163
140
164
const parseLines = ( lines ) => {
141
165
if ( ! lines || ! lines . length || ! lines [ 0 ] . line ) {
142
- return { stmts : '0' , missing : '' , totalMissing : '0' } ;
166
+ return {
167
+ stmts : 0 ,
168
+ missingStmts : 0 ,
169
+ branches : 0 ,
170
+ partBranches : 0 ,
171
+ missing : [ ] ,
172
+ } ;
143
173
}
144
174
145
- let stmts = 0 ;
146
- const missingLines = [ ] ;
175
+ let stmts = 0 ,
176
+ branches = 0 ;
177
+ const missingLines = [ ] ,
178
+ partBranchesText = [ ] ;
147
179
148
180
lines [ 0 ] . line . forEach ( ( line ) => {
149
181
stmts ++ ;
150
- const { hits, number } = line [ '$' ] ;
182
+ const { hits, number, branch } = line [ '$' ] ;
151
183
152
184
if ( hits === '0' ) {
153
185
missingLines . push ( parseInt ( number ) ) ;
154
186
}
187
+ if ( branch === 'true' ) {
188
+ const { 'condition-coverage' : cc , 'missing-branches' : mb } = line [ '$' ] ;
189
+ // 'condition-coverage' is usually formatted like "50% (1/2)". Parse the total number
190
+ // of targets (the second number in parentheses). Default to 2 if not available.
191
+ const numTargets = cc && cc . match ( / \( \d + \/ ( \d + ) \) / ) ;
192
+ branches += numTargets ? + numTargets [ 1 ] : 2 ;
193
+ if ( mb ) {
194
+ for ( const target of mb . split ( ',' ) ) {
195
+ partBranchesText . push ( `${ number } ->${ target } ` ) ;
196
+ }
197
+ }
198
+ }
155
199
} ) ;
156
200
157
201
const missing = missingLines . reduce ( ( arr , val , i , a ) => {
@@ -168,11 +212,14 @@ const parseLines = (lines) => {
168
212
missingText . push ( `${ m [ 0 ] } -${ m [ m . length - 1 ] } ` ) ;
169
213
}
170
214
} ) ;
215
+ missingText . push ( ...partBranchesText ) ;
171
216
172
217
return {
173
- stmts : stmts . toString ( ) ,
218
+ stmts,
219
+ missingStmts : missingLines . length ,
220
+ branches,
221
+ partBranches : partBranchesText . length ,
174
222
missing : missingText ,
175
- totalMissing : missingLines . length . toString ( ) ,
176
223
} ;
177
224
} ;
178
225
0 commit comments