Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Bins.py: 80%

101 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-21 23:49 +0000

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

2# _____ ____ _ _ ____ _ # 

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

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | |_) / _ \ '_ \ / _ \| '__| __/ __| # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ _| _ < __/ |_) | (_) | | | |_\__ \ # 

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

7# |_| |___/ |_| # 

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²) # 

15# # 

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

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

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

19# # 

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

21# # 

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

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

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

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

26# limitations under the License. # 

27# # 

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

29# ==================================================================================================================== # 

30# 

31""" 

32The bins of the UCIS XML interchange format: a coverage item's count - how often it was covered -, the tests, which 

33covered it, and its goal. 

34""" 

35from __future__ import annotations 

36 

37from typing import Iterable, Optional as Nullable, Self 

38 

39from lxml.etree import _Element 

40from pyTooling.Common import getFullyQualifiedName 

41from pyTooling.Decorators import export, readonly 

42from pyTooling.MetaClasses import ExtendedType 

43 

44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, ObjectAttributesMixin, UserAttribute 

45 

46 

47@export 

48class BinContents(metaclass=ExtendedType, slots=True): 

49 """ 

50 A ``<contents>`` of a bin: how often its coverage item was covered, the tests, which covered it, and the components 

51 of its unique ID. 

52 

53 It is a value, as a path is: it names no parent. 

54 """ 

55 

56 _coverageCount: int #: How often the coverage item was covered. 

57 _historyNodeIDs: list[int] #: The IDs of the history nodes, which covered the coverage item. 

58 _nameComponent: Nullable[str] #: The name component of the coverage item's unique ID. 

59 _typeComponent: Nullable[str] #: The type component of the coverage item's unique ID. 

60 

61 def __init__( 

62 self, 

63 coverageCount: int, 

64 historyNodeIDs: Nullable[Iterable[int]] = None, 

65 nameComponent: Nullable[str] = None, 

66 typeComponent: Nullable[str] = None 

67 ) -> None: 

68 """ 

69 Initialize the contents of a bin. 

70 

71 :param coverageCount: How often the coverage item was covered. 

72 :param historyNodeIDs: Optional, the IDs of the history nodes, which covered the coverage item. Default: ``None``. 

73 :param nameComponent: Optional, the name component of the coverage item's unique ID. Default: ``None``. 

74 :param typeComponent: Optional, the type component of the coverage item's unique ID, e.g. ``:5:`` for a 

75 statement. Default: ``None``. 

76 :raises ValueError: If parameter ``coverageCount`` is ``None``. 

77 :raises TypeError: If parameter ``coverageCount`` isn't of type :class:`int`. 

78 :raises ValueError: If parameter ``coverageCount`` is negative. 

79 :raises TypeError: If parameter ``historyNodeIDs`` isn't iterable. 

80 :raises TypeError: If parameter ``historyNodeIDs`` contains an element not of type :class:`int`. 

81 :raises TypeError: If parameter ``nameComponent`` isn't of type :class:`str`. 

82 :raises TypeError: If parameter ``typeComponent`` isn't of type :class:`str`. 

83 """ 

84 if coverageCount is None: 84 ↛ 85line 84 didn't jump to line 85 because the condition on line 84 was never true

85 raise ValueError("Parameter 'coverageCount' is None.") 

86 elif not isinstance(coverageCount, int) or isinstance(coverageCount, bool): 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true

87 ex = TypeError("Parameter 'coverageCount' is not of type 'int'.") 

88 ex.add_note(f"Got type '{getFullyQualifiedName(coverageCount)}'.") 

89 raise ex 

90 elif coverageCount < 0: 

91 ex = ValueError("Parameter 'coverageCount' is negative.") 

92 ex.add_note(f"Got value '{coverageCount}'.") 

93 raise ex 

94 

95 if nameComponent is not None and not isinstance(nameComponent, str): 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 ex = TypeError("Parameter 'nameComponent' is not of type 'str'.") 

97 ex.add_note(f"Got type '{getFullyQualifiedName(nameComponent)}'.") 

98 raise ex 

99 

100 if typeComponent is not None and not isinstance(typeComponent, str): 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true

101 ex = TypeError("Parameter 'typeComponent' is not of type 'str'.") 

102 ex.add_note(f"Got type '{getFullyQualifiedName(typeComponent)}'.") 

103 raise ex 

104 

105 self._coverageCount = coverageCount 

106 self._historyNodeIDs = [] 

107 self._nameComponent = nameComponent 

108 self._typeComponent = typeComponent 

109 

110 if historyNodeIDs is not None: 

111 if not isinstance(historyNodeIDs, Iterable): 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true

112 ex = TypeError("Parameter 'historyNodeIDs' is not iterable.") 

113 ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeIDs)}'.") 

114 raise ex 

115 

116 for historyNodeID in historyNodeIDs: 

117 if not isinstance(historyNodeID, int) or isinstance(historyNodeID, bool): 

118 ex = TypeError("Parameter 'historyNodeIDs' contains an element not of type 'int'.") 

119 ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeID)}'.") 

120 raise ex 

121 

122 self._historyNodeIDs.append(historyNodeID) 

123 

124 @classmethod 

125 def Parse(cls, element: _Element) -> Self: 

126 """ 

127 Parse the contents of a bin from its ``<contents>`` element. 

128 

129 :param element: The ``<contents>`` element. 

130 :returns: The contents. 

131 """ 

132 return cls( 

133 int(element.attrib["coverageCount"]), 

134 [int(idElement.text) for idElement in element.iterfind("{*}historyNodeId")], 

135 element.attrib.get("nameComponent"), 

136 element.attrib.get("typeComponent") 

137 ) 

138 

139 @readonly 

140 def CoverageCount(self) -> int: 

141 """ 

142 Read-only property to access how often the coverage item was covered (:attr:`_coverageCount`). 

143 

144 :returns: The count, e.g. how often a statement ran. 

145 """ 

146 return self._coverageCount 

147 

148 @readonly 

149 def HistoryNodeIDs(self) -> list[int]: 

150 """ 

151 Read-only property to access the IDs of the history nodes, which covered the item (:attr:`_historyNodeIDs`). 

152 

153 :returns: The IDs, in the order the report lists them. 

154 """ 

155 return self._historyNodeIDs 

156 

157 @readonly 

158 def NameComponent(self) -> Nullable[str]: 

159 """ 

160 Read-only property to access the name component of the coverage item's unique ID (:attr:`_nameComponent`). 

161 

162 :returns: The name component; ``None``, if the report states none. 

163 """ 

164 return self._nameComponent 

165 

166 @readonly 

167 def TypeComponent(self) -> Nullable[str]: 

168 """ 

169 Read-only property to access the type component of the coverage item's unique ID (:attr:`_typeComponent`). 

170 

171 :returns: The type component, e.g. ``:5:``; ``None``, if the report states none. 

172 """ 

173 return self._typeComponent 

174 

175 

176@export 

177class Bin(Base, ObjectAttributesMixin): 

178 """ 

179 A bin of a coverage item: its contents, its goal and its attributes. 

180 

181 A statement has ``<bin>`` elements, a branch ``<branchBin>`` elements. The element owning the bin takes it as a 

182 parameter and becomes its parent. 

183 """ 

184 

185 _parent: Nullable[Base] #: The element owning the bin. 

186 _contents: BinContents #: How often the coverage item was covered, and by which tests. 

187 _coverageCountGoal: Nullable[int] #: How often the coverage item must be covered. 

188 

189 def __init__( 

190 self, 

191 contents: BinContents, 

192 coverageCountGoal: Nullable[int] = None, 

193 alias: Nullable[str] = None, 

194 excluded: bool = False, 

195 excludedReason: Nullable[str] = None, 

196 weight: int = 1, 

197 userAttributes: Nullable[Iterable[UserAttribute]] = None 

198 ) -> None: 

199 """ 

200 Initialize the bin. 

201 

202 :param contents: How often the coverage item was covered, and by which tests. 

203 :param coverageCountGoal: Optional, how often the coverage item must be covered. Default: ``None``. 

204 :param alias: Optional, an alias of the bin's name. Default: ``None``. 

205 :param excluded: Optional, whether the bin is excluded from the coverage. Default: ``False``. 

206 :param excludedReason: Optional, why the bin is excluded. Default: ``None``. 

207 :param weight: Optional, weight of the bin in computing the coverage. Default: ``1``. 

208 :param userAttributes: Optional, the user-defined attributes. Default: ``None``. 

209 :raises TypeError: If parameter ``userAttributes`` isn't iterable. 

210 :raises TypeError: If parameter ``userAttributes`` contains an element not of type 

211 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. 

212 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`. 

213 :raises ValueError: If parameter ``excluded`` is ``None``. 

214 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`. 

215 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`. 

216 :raises ValueError: If parameter ``weight`` is ``None``. 

217 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`. 

218 :raises ValueError: If parameter ``weight`` is negative. 

219 :raises ValueError: If parameter ``contents`` is ``None``. 

220 :raises TypeError: If parameter ``contents`` isn't of type :class:`BinContents`. 

221 :raises TypeError: If parameter ``coverageCountGoal`` isn't of type :class:`int`. 

222 :raises ValueError: If parameter ``coverageCountGoal`` is negative. 

223 """ 

224 super().__init__(userAttributes) 

225 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight) 

226 

227 if contents is None: 

228 raise ValueError("Parameter 'contents' is None.") 

229 elif not isinstance(contents, BinContents): 229 ↛ 230line 229 didn't jump to line 230 because the condition on line 229 was never true

230 ex = TypeError("Parameter 'contents' is not of type 'BinContents'.") 

231 ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.") 

232 raise ex 

233 

234 if coverageCountGoal is not None: 

235 if not isinstance(coverageCountGoal, int) or isinstance(coverageCountGoal, bool): 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true

236 ex = TypeError("Parameter 'coverageCountGoal' is not of type 'int'.") 

237 ex.add_note(f"Got type '{getFullyQualifiedName(coverageCountGoal)}'.") 

238 raise ex 

239 elif coverageCountGoal < 0: 

240 ex = ValueError("Parameter 'coverageCountGoal' is negative.") 

241 ex.add_note(f"Got value '{coverageCountGoal}'.") 

242 raise ex 

243 

244 self._parent = None 

245 self._contents = contents 

246 self._coverageCountGoal = coverageCountGoal 

247 

248 @classmethod 

249 def Parse(cls, element: _Element) -> Self: 

250 """ 

251 Parse a bin, its contents and its user-defined attributes from its element. 

252 

253 The element is e.g. a ``<bin>`` or a ``<branchBin>``. 

254 

255 :param element: The bin's element. 

256 :returns: The bin. 

257 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. 

258 """ 

259 goal = element.attrib.get("coverageCountGoal") 

260 return cls( 

261 BinContents.Parse(element.find("{*}contents")), 

262 None if goal is None else int(goal), 

263 element.attrib.get("alias"), 

264 cls._ParseBoolean(element.attrib.get("excluded", "false")), 

265 element.attrib.get("excludedReason"), 

266 int(element.attrib.get("weight", "1")), 

267 cls._ParseUserAttributes(element) 

268 ) 

269 

270 @readonly 

271 def Parent(self) -> Nullable[Base]: 

272 """ 

273 Read-only property to access the element owning the bin (:attr:`_parent`). 

274 

275 :returns: The element, e.g. a :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Blocks.Statement`; ``None``, if the bin 

276 belongs to none. 

277 """ 

278 return self._parent 

279 

280 @readonly 

281 def Contents(self) -> BinContents: 

282 """ 

283 Read-only property to access the contents of the bin (:attr:`_contents`). 

284 

285 :returns: How often the coverage item was covered, and by which tests. 

286 """ 

287 return self._contents 

288 

289 @readonly 

290 def CoverageCountGoal(self) -> Nullable[int]: 

291 """ 

292 Read-only property to access how often the coverage item must be covered (:attr:`_coverageCountGoal`). 

293 

294 :returns: The goal; ``None``, if the report states none. 

295 """ 

296 return self._coverageCountGoal