Coverage for pyEDAA/UCIS/Cobertura.py: 96%

134 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-19 00:50 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ _ _ ____ ___ ____ # 

3# _ __ _ _| ____| _ \ / \ / \ | | | |/ ___|_ _/ ___| # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | |\___ \ # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |___ | | ___) | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \____|___|____/ # 

7# |_| |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Artur Porebski (Aldec Inc.) # 

11# Michal Pacula (Aldec Inc.) # 

12# # 

13# License: # 

14# ==================================================================================================================== # 

15# Copyright 2021-2022 Electronic Design Automation Abstraction (EDA²) # 

16# # 

17# Licensed under the Apache License, Version 2.0 (the "License"); # 

18# you may not use this file except in compliance with the License. # 

19# You may obtain a copy of the License at # 

20# # 

21# http://www.apache.org/licenses/LICENSE-2.0 # 

22# # 

23# Unless required by applicable law or agreed to in writing, software # 

24# distributed under the License is distributed on an "AS IS" BASIS, # 

25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

26# See the License for the specific language governing permissions and # 

27# limitations under the License. # 

28# # 

29# SPDX-License-Identifier: Apache-2.0 # 

30# ==================================================================================================================== # 

31# 

32""" 

33Data model of the Cobertura format. 

34 

35.. mermaid:: 

36 

37 flowchart LR 

38 Coverage --> Package --> Class --> Statement 

39 

40""" 

41from time import time 

42from typing import Dict, Set 

43 

44from lxml import etree 

45from pyTooling.Decorators import export 

46 

47 

48@export 

49class CoberturaException(Exception): 

50 """Base-class for other Cobertura exceptions""" 

51 

52 

53@export 

54class DuplicatedLineNumber(CoberturaException): 

55 """Raised when statement with specified line number already exists in Cobertura class""" 

56 

57 

58@export 

59class DuplicatedClassName(CoberturaException): 

60 """Raised when class with specified name already exists in Cobertura package""" 

61 

62 

63@export 

64class DuplicatedPackageName(CoberturaException): 

65 """Raised when package with specified name already exists in Cobertura coverage""" 

66 

67 

68@export 

69class Class: 

70 """Represents a code element in the Cobertura coverage data model (Java-focused).""" 

71 

72 name: str 

73 sourceFile: str 

74 lines: Dict[int, int] 

75 linesValid: int 

76 linesCovered: int 

77 

78 def __init__(self, name: str, sourceFile: str): 

79 self.name = name 

80 self.sourceFile = sourceFile 

81 self.lines = {} 

82 self.linesValid = 0 

83 self.linesCovered = 0 

84 

85 def addStatement(self, line: int, hits: int) -> None: 

86 if line in self.lines.keys(): 86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true

87 raise DuplicatedLineNumber(f"Duplicated line number: {line}") 

88 

89 self.lines[line] = hits 

90 self.linesValid += 1 

91 

92 if hits: 

93 self.linesCovered += 1 

94 

95 def getXmlNode(self) -> etree._Element: 

96 classNode = etree.Element("class") 

97 classNode.attrib["name"] = self.sourceFile 

98 classNode.attrib["filename"] = self.sourceFile 

99 classNode.attrib["complexity"] = "0" 

100 classNode.attrib["branch-rate"] = "0" 

101 

102 try: 

103 rate = self.linesCovered / self.linesValid 

104 except ZeroDivisionError: 

105 rate = 1.0 

106 

107 classNode.attrib["line-rate"] = f"{rate:.16g}" 

108 

109 classNode.append(etree.Element("methods")) 

110 linesNode = etree.SubElement(classNode, "lines") 

111 

112 for line in self.lines: 

113 etree.SubElement( 

114 linesNode, 

115 "line", 

116 number=str(line), 

117 hits=str(self.lines[line]), 

118 ) 

119 

120 return classNode 

121 

122 

123@export 

124class Package: 

125 """Represents a grouping element in the Cobertura coverage data model (Java-focused).""" 

126 

127 name: str 

128 classes: Dict[str, Class] 

129 linesValid: int 

130 linesCovered: int 

131 

132 def __init__(self, name: str): 

133 self.name = name 

134 self.classes = {} 

135 self.linesValid = 0 

136 self.linesCovered = 0 

137 

138 def addClass(self, coberturaClass: Class): 

139 if coberturaClass.name in self.classes: 139 ↛ 140line 139 didn't jump to line 140, because the condition on line 139 was never true

140 raise DuplicatedClassName(f"Duplicated class name: '{coberturaClass.name}'.") 

141 

142 self.classes[coberturaClass.name] = coberturaClass 

143 

144 def refreshStatistics(self) -> None: 

145 self.linesValid = 0 

146 self.linesCovered = 0 

147 

148 for coberturaClass in self.classes.values(): 

149 self.linesCovered += coberturaClass.linesCovered 

150 self.linesValid += coberturaClass.linesValid 

151 

152 def getXmlNode(self) -> etree._Element: 

153 classesNode = etree.Element("classes") 

154 packageNode = etree.Element("package") 

155 packageNode.attrib["name"] = self.name 

156 packageNode.attrib["complexity"] = "0" 

157 packageNode.attrib["branch-rate"] = "0" 

158 

159 try: 

160 rate = self.linesCovered / self.linesValid 

161 except ZeroDivisionError: 

162 rate = 1.0 

163 

164 packageNode.attrib["line-rate"] = f"{rate:.16g}" 

165 

166 packageNode.append(classesNode) 

167 

168 for coberturaClass in self.classes.values(): 

169 classesNode.append(coberturaClass.getXmlNode()) 

170 

171 return packageNode 

172 

173 

174@export 

175class Coverage: 

176 """Represents the root element in the Cobertura coverage data model (Java-focused).""" 

177 

178 sources: Set 

179 packages: Dict[str, Package] 

180 linesValid: int 

181 linesCovered: int 

182 

183 def __init__(self): 

184 self.sources = set() 

185 self.packages = {} 

186 self.linesValid = 0 

187 self.linesCovered = 0 

188 

189 def addSource(self, source: str) -> None: 

190 self.sources.add(source) 

191 

192 def addPackage(self, package: Package) -> None: 

193 if package.name in self.packages: 193 ↛ 194line 193 didn't jump to line 194, because the condition on line 193 was never true

194 raise DuplicatedPackageName(f"Duplicated package name: '{package.name}'.") 

195 

196 self.packages[package.name] = package 

197 

198 def refreshStatistics(self) -> None: 

199 self.linesValid = 0 

200 self.linesCovered = 0 

201 

202 for package in self.packages.values(): 

203 package.refreshStatistics() 

204 self.linesCovered += package.linesCovered 

205 self.linesValid += package.linesValid 

206 

207 def getXml(self) -> bytes: 

208 self.refreshStatistics() 

209 

210 coverageNode = etree.Element("coverage") 

211 coverageNode.attrib["version"] = "5.5" 

212 coverageNode.attrib["timestamp"] = str(int(time())) 

213 coverageNode.attrib["branches-valid"] = "0" 

214 coverageNode.attrib["branches-covered"] = "0" 

215 coverageNode.attrib["branch-rate"] = "0" 

216 coverageNode.attrib["complexity"] = "0" 

217 

218 sourcesNode = etree.Element("sources") 

219 

220 for source in self.sources: 

221 etree.SubElement(sourcesNode, "source").text = source 

222 

223 coverageNode.append(sourcesNode) 

224 

225 packagesNode = etree.Element("packages") 

226 

227 for package in self.packages.values(): 

228 packagesNode.append(package.getXmlNode()) 

229 

230 coverageNode.append(packagesNode) 

231 

232 coverageNode.attrib["lines-valid"] = str(self.linesValid) 

233 coverageNode.attrib["lines-covered"] = str(self.linesCovered) 

234 

235 try: 

236 rate = self.linesCovered / self.linesValid 

237 except ZeroDivisionError: 

238 rate = 1.0 

239 

240 coverageNode.attrib["line-rate"] = f"{rate:.16g}" 

241 

242 return etree.tostring( 

243 coverageNode, pretty_print=True, encoding="utf-8", xml_declaration=True 

244 )