Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Conditions.py: 79%

156 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 condition and expression coverage of the UCIS XML interchange format: an instance's ``<conditionCoverage>`` and its 

33expressions, nested by their sub-expressions. 

34""" 

35from __future__ import annotations 

36 

37from typing import TYPE_CHECKING, Generic, Iterable, Optional as Nullable, Self 

38from typing import TypeVar 

39 

40from lxml.etree import _Element 

41from pyTooling.Common import getFullyQualifiedName 

42from pyTooling.Decorators import export, readonly 

43 

44from pyEDAA.Reports.CodeCoverage.UCIS.Bins import Bin 

45from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, StatementID 

46from pyEDAA.Reports.CodeCoverage.UCIS.Elements import UserAttribute 

47 

48if TYPE_CHECKING: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true

49 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage 

50 

51 

52__all__ = ["ExpressionParentType"] 

53 

54ExpressionParentType = TypeVar("ExpressionParentType", bound="ConditionCoverage | Expression") 

55"""A type variable for the parent of an :class:`Expression`: a :class:`ConditionCoverage` or an :class:`Expression`.""" 

56 

57 

58@export 

59class ConditionCoverage(MetricCoverage): 

60 """ 

61 A ``<conditionCoverage>`` of an instance: its top-level expressions, of conditions or assignments. 

62 """ 

63 

64 _expressions: list[Expression[ConditionCoverage]] #: The top-level expressions. 

65 

66 def __init__( 

67 self, 

68 metricMode: Nullable[str] = None, 

69 weight: int = 1, 

70 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

71 *, 

72 parent: Nullable[InstanceCoverage] = None 

73 ) -> None: 

74 """ 

75 Initialize the condition coverage, and append it to the condition coverages of its instance. 

76 

77 Its expressions are added by creating them with this coverage as their parent. 

78 

79 :param metricMode: Optional, the mode in which the coverage was measured, e.g. ``UCIS:BITWISE_FLAT``. 

80 Default: ``None``. 

81 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``. 

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

83 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

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

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

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

87 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`. 

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

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

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

91 :raises TypeError: If parameter ``parent`` isn't of type 

92 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`. 

93 """ 

94 super().__init__(metricMode, weight, userAttributes, parent=parent) 

95 

96 self._expressions = [] 

97 

98 if parent is not None: 

99 parent._conditionCoverages.append(self) 

100 

101 @classmethod 

102 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self: 

103 """ 

104 Parse a condition coverage and its expressions from its ``<conditionCoverage>`` element. 

105 

106 :param element: The ``<conditionCoverage>`` element. 

107 :param parent: Optional, the instance the coverage belongs to. Default: ``None``. 

108 :returns: The condition coverage. 

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

110 """ 

111 conditionCoverage = cls( 

112 element.attrib.get("metricMode"), 

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

114 cls._ParseUserAttributes(element), 

115 parent=parent 

116 ) 

117 

118 for expressionElement in element.iterfind("{*}expr"): 

119 Expression.Parse(expressionElement, parent=conditionCoverage) 

120 

121 return conditionCoverage 

122 

123 @readonly 

124 def Expressions(self) -> list[Expression[ConditionCoverage]]: 

125 """ 

126 Read-only property to access the top-level expressions (:attr:`_expressions`). 

127 

128 :returns: The expressions, in the order the report lists them. 

129 """ 

130 return self._expressions 

131 

132 

133@export 

134class Expression(Base, ObjectAttributesMixin, Generic[ExpressionParentType]): 

135 """ 

136 An ``<expr>`` of a condition coverage - or a ``<hierarchicalExpr>`` of an expression -: the expression, where it is, 

137 its sub-expressions, the bins of the combinations of their values, and the expressions nested in it. 

138 """ 

139 

140 _parent: Nullable[ExpressionParentType] #: The condition coverage or expression the expression belongs to. 

141 _name: str #: UCIS name of the expression. 

142 _key: str #: UCIS key of the expression. 

143 _text: str #: The expression's text, e.g. ``(a&&b) || (c&&d)``. 

144 _index: int #: Index of the expression in its parent expression. 

145 _width: int #: Bit width of the expression. 

146 _id: StatementID #: Where the expression is. 

147 _subExpressions: list[str] #: The sub-expressions, whose values the bins combine. 

148 _bins: list[Bin] #: The bins of the combinations of the sub-expressions' values. 

149 _statementType: Nullable[str] #: Kind of the statement containing the expression, e.g. ``if``. 

150 _expressions: list[Expression[Expression]] #: The expressions nested in this one. 

151 

152 def __init__( 

153 self, 

154 name: str, 

155 key: str, 

156 text: str, 

157 index: int, 

158 width: int, 

159 statementID: StatementID, 

160 subExpressions: Iterable[str], 

161 bins: Iterable[Bin], 

162 statementType: Nullable[str] = None, 

163 alias: Nullable[str] = None, 

164 excluded: bool = False, 

165 excludedReason: Nullable[str] = None, 

166 weight: int = 1, 

167 userAttributes: Nullable[Iterable[UserAttribute]] = None, 

168 *, 

169 parent: Nullable[ExpressionParentType] = None 

170 ) -> None: 

171 """ 

172 Initialize the expression, become its bins' parent, and append it to the expressions of its parent. 

173 

174 The expressions nested in it are added by creating them with this expression as their parent. 

175 

176 :param name: UCIS name of the expression. 

177 :param key: UCIS key of the expression. 

178 :param text: The expression's text, e.g. ``(a&&b) || (c&&d)``. 

179 :param index: Index of the expression in its parent expression; ``0`` for a top-level expression. 

180 :param width: Bit width of the expression. 

181 :param statementID: Where the expression is. 

182 :param subExpressions: The sub-expressions, whose values the bins combine, e.g. ``a`` and ``b``. 

183 :param bins: The bins of the combinations of the sub-expressions' values. 

184 :param statementType: Optional, kind of the statement containing the expression, e.g. ``if``. Default: ``None``. 

185 :param alias: Optional, an alias of the expression's name. Default: ``None``. 

186 :param excluded: Optional, whether the expression is excluded from the coverage. Default: ``False``. 

187 :param excludedReason: Optional, why the expression is excluded. Default: ``None``. 

188 :param weight: Optional, weight of the expression in computing the coverage. Default: ``1``. 

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

190 :param parent: Optional, the condition coverage or expression the expression belongs to. Default: ``None``. 

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

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

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

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

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

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

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

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

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

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

201 :raises ValueError: If parameter ``name``, ``key`` or ``text`` is ``None``. 

202 :raises TypeError: If parameter ``name``, ``key`` or ``text`` isn't of type :class:`str`. 

203 :raises ValueError: If parameter ``index`` or ``width`` is ``None``. 

204 :raises TypeError: If parameter ``index`` or ``width`` isn't of type :class:`int`. 

205 :raises ValueError: If parameter ``index`` or ``width`` is negative. 

206 :raises ValueError: If parameter ``statementID`` is ``None``. 

207 :raises TypeError: If parameter ``statementID`` isn't of type 

208 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`. 

209 :raises TypeError: If parameter ``statementType`` isn't of type :class:`str`. 

210 :raises TypeError: If parameter ``parent`` isn't of type :class:`ConditionCoverage` or :class:`Expression`. 

211 :raises ValueError: If parameter ``subExpressions`` is ``None``. 

212 :raises TypeError: If parameter ``subExpressions`` isn't iterable. 

213 :raises TypeError: If parameter ``subExpressions`` contains an element not of type :class:`str`. 

214 :raises ValueError: If parameter ``bins`` is ``None``. 

215 :raises TypeError: If parameter ``bins`` isn't iterable. 

216 :raises TypeError: If parameter ``bins`` contains an element not of type 

217 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`. 

218 """ 

219 super().__init__(userAttributes) 

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

221 

222 for parameterName, value in ( 

223 ("name", name), 

224 ("key", key), 

225 ("text", text) 

226 ): 

227 if value is None: 227 ↛ 228line 227 didn't jump to line 228 because the condition on line 227 was never true

228 raise ValueError(f"Parameter '{parameterName}' is None.") 

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

230 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.") 

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

232 raise ex 

233 

234 for parameterName, value in ( 

235 ("index", index), 

236 ("width", width) 

237 ): 

238 if value is None: 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true

239 raise ValueError(f"Parameter '{parameterName}' is None.") 

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

241 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.") 

242 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

243 raise ex 

244 elif value < 0: 

245 ex = ValueError(f"Parameter '{parameterName}' is negative.") 

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

247 raise ex 

248 

249 if statementID is None: 249 ↛ 250line 249 didn't jump to line 250 because the condition on line 249 was never true

250 raise ValueError("Parameter 'statementID' is None.") 

251 elif not isinstance(statementID, StatementID): 251 ↛ 252line 251 didn't jump to line 252 because the condition on line 251 was never true

252 ex = TypeError("Parameter 'statementID' is not of type 'StatementID'.") 

253 ex.add_note(f"Got type '{getFullyQualifiedName(statementID)}'.") 

254 raise ex 

255 

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

257 ex = TypeError("Parameter 'statementType' is not of type 'str'.") 

258 ex.add_note(f"Got type '{getFullyQualifiedName(statementType)}'.") 

259 raise ex 

260 

261 if parent is not None and not isinstance(parent, (ConditionCoverage, Expression)): 

262 ex = TypeError("Parameter 'parent' is not of type 'ConditionCoverage' or 'Expression'.") 

263 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") 

264 raise ex 

265 

266 self._parent = parent 

267 self._name = name 

268 self._key = key 

269 self._text = text 

270 self._index = index 

271 self._width = width 

272 self._id = statementID 

273 self._subExpressions = [] 

274 self._bins = [] 

275 self._statementType = statementType 

276 self._expressions = [] 

277 

278 if subExpressions is None: 278 ↛ 279line 278 didn't jump to line 279 because the condition on line 278 was never true

279 raise ValueError("Parameter 'subExpressions' is None.") 

280 elif not isinstance(subExpressions, Iterable): 280 ↛ 281line 280 didn't jump to line 281 because the condition on line 280 was never true

281 ex = TypeError("Parameter 'subExpressions' is not iterable.") 

282 ex.add_note(f"Got type '{getFullyQualifiedName(subExpressions)}'.") 

283 raise ex 

284 

285 for subExpression in subExpressions: 

286 if not isinstance(subExpression, str): 286 ↛ 287line 286 didn't jump to line 287 because the condition on line 286 was never true

287 ex = TypeError("Parameter 'subExpressions' contains an element not of type 'str'.") 

288 ex.add_note(f"Got type '{getFullyQualifiedName(subExpression)}'.") 

289 raise ex 

290 

291 self._subExpressions.append(subExpression) 

292 

293 if bins is None: 293 ↛ 294line 293 didn't jump to line 294 because the condition on line 293 was never true

294 raise ValueError("Parameter 'bins' is None.") 

295 elif not isinstance(bins, Iterable): 295 ↛ 296line 295 didn't jump to line 296 because the condition on line 295 was never true

296 ex = TypeError("Parameter 'bins' is not iterable.") 

297 ex.add_note(f"Got type '{getFullyQualifiedName(bins)}'.") 

298 raise ex 

299 

300 for coverBin in bins: 

301 if not isinstance(coverBin, Bin): 

302 ex = TypeError("Parameter 'bins' contains an element not of type 'Bin'.") 

303 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.") 

304 raise ex 

305 

306 coverBin._parent = self 

307 self._bins.append(coverBin) 

308 

309 if parent is not None: 309 ↛ exitline 309 didn't return from function '__init__' because the condition on line 309 was always true

310 parent._expressions.append(self) 

311 

312 @classmethod 

313 def Parse(cls, element: _Element, *, parent: Nullable[ExpressionParentType] = None) -> Self: 

314 """ 

315 Parse an expression, its bins and the expressions nested in it from its ``<expr>`` or ``<hierarchicalExpr>`` 

316 element. 

317 

318 :param element: The ``<expr>`` or ``<hierarchicalExpr>`` element. 

319 :param parent: Optional, the condition coverage or expression the expression belongs to. Default: 

320 ``None``. 

321 :returns: The expression. 

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

323 """ 

324 expression = cls( 

325 element.attrib["name"], 

326 element.attrib["key"], 

327 element.attrib["exprString"], 

328 int(element.attrib["index"]), 

329 int(element.attrib["width"]), 

330 StatementID.Parse(element.find("{*}id")), 

331 [subExpressionElement.text or "" for subExpressionElement in element.iterfind("{*}subExpr")], 

332 [Bin.Parse(binElement) for binElement in element.iterfind("{*}bin")], 

333 element.attrib.get("statementType"), 

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

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

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

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

338 cls._ParseUserAttributes(element), 

339 parent=parent 

340 ) 

341 

342 for expressionElement in element.iterfind("{*}hierarchicalExpr"): 

343 Expression.Parse(expressionElement, parent=expression) 

344 

345 return expression 

346 

347 @readonly 

348 def Parent(self) -> Nullable[ExpressionParentType]: 

349 """ 

350 Read-only property to access the condition coverage or expression the expression belongs to (:attr:`_parent`). 

351 

352 :returns: The :class:`ConditionCoverage` or :class:`Expression`; ``None``, if the expression belongs to none. 

353 """ 

354 return self._parent 

355 

356 @readonly 

357 def Name(self) -> str: 

358 """ 

359 Read-only property to access the UCIS name of the expression (:attr:`_name`). 

360 

361 :returns: The name, as stated. 

362 """ 

363 return self._name 

364 

365 @readonly 

366 def Key(self) -> str: 

367 """ 

368 Read-only property to access the UCIS key of the expression (:attr:`_key`). 

369 

370 :returns: The key, as stated. 

371 """ 

372 return self._key 

373 

374 @readonly 

375 def Text(self) -> str: 

376 """ 

377 Read-only property to access the expression's text (:attr:`_text`). 

378 

379 :returns: The text, e.g. ``(a&&b) || (c&&d)``. 

380 """ 

381 return self._text 

382 

383 @readonly 

384 def Index(self) -> int: 

385 """ 

386 Read-only property to access the index of the expression in its parent expression (:attr:`_index`). 

387 

388 :returns: The index; ``0`` for a top-level expression. 

389 """ 

390 return self._index 

391 

392 @readonly 

393 def Width(self) -> int: 

394 """ 

395 Read-only property to access the bit width of the expression (:attr:`_width`). 

396 

397 :returns: The width. 

398 """ 

399 return self._width 

400 

401 @readonly 

402 def ID(self) -> StatementID: 

403 """ 

404 Read-only property to access where the expression is (:attr:`_id`). 

405 

406 :returns: The source statement identifier. 

407 """ 

408 return self._id 

409 

410 @readonly 

411 def SubExpressions(self) -> list[str]: 

412 """ 

413 Read-only property to access the sub-expressions, whose values the bins combine (:attr:`_subExpressions`). 

414 

415 :returns: The sub-expressions, in the order the report lists them. 

416 """ 

417 return self._subExpressions 

418 

419 @readonly 

420 def Bins(self) -> list[Bin]: 

421 """ 

422 Read-only property to access the bins of the combinations of the sub-expressions' values (:attr:`_bins`). 

423 

424 :returns: The bins, in the order the report lists them. 

425 """ 

426 return self._bins 

427 

428 @readonly 

429 def StatementType(self) -> Nullable[str]: 

430 """ 

431 Read-only property to access the kind of the statement containing the expression (:attr:`_statementType`). 

432 

433 :returns: The kind, e.g. ``if``; ``None``, if the report states none. 

434 """ 

435 return self._statementType 

436 

437 @readonly 

438 def Expressions(self) -> list[Expression[Expression]]: 

439 """ 

440 Read-only property to access the expressions nested in this one (:attr:`_expressions`). 

441 

442 :returns: The expressions, in the order the report lists them. 

443 """ 

444 return self._expressions