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

154 statements  

« prev     ^ index     » next       coverage.py v7.17.3, created at 2026-11-14 02:26 +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, \ 

38 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, \ 

46 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 ``metricMode`` isn't of type :class:`str`. 

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

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

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

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

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

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

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 ValueError: If parameter ``name`` is ``None``. 

192 :raises TypeError: If parameter ``name`` isn't of type :class:`str`. 

193 :raises ValueError: If parameter ``key`` is ``None``. 

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

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

196 :raises TypeError: If parameter ``text`` isn't of type :class:`str`. 

197 :raises ValueError: If parameter ``index`` is ``None``. 

198 :raises TypeError: If parameter ``index`` isn't of type :class:`int`. 

199 :raises ValueError: If parameter ``index`` is negative. 

200 :raises ValueError: If parameter ``width`` is ``None``. 

201 :raises TypeError: If parameter ``width`` isn't of type :class:`int`. 

202 :raises ValueError: If parameter ``width`` is negative. 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

225 """ 

226 super().__init__(userAttributes) 

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

228 

229 for parameterName, value in ( 

230 ("name", name), 

231 ("key", key), 

232 ("text", text) 

233 ): 

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

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

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

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

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

239 raise ex 

240 

241 for parameterName, value in ( 

242 ("index", index), 

243 ("width", width) 

244 ): 

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

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

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

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

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

250 raise ex 

251 elif value < 0: 

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

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

254 raise ex 

255 

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

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

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

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

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

261 raise ex 

262 

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

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

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

266 raise ex 

267 

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

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

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

271 raise ex 

272 

273 self._parent = parent 

274 self._name = name 

275 self._key = key 

276 self._text = text 

277 self._index = index 

278 self._width = width 

279 self._id = statementID 

280 self._subExpressions = subExpressionList = [] 

281 self._bins = binList = [] 

282 self._statementType = statementType 

283 self._expressions = [] 

284 

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

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

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

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

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

290 raise ex 

291 

292 for subExpression in subExpressions: 

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

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

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

296 raise ex 

297 

298 subExpressionList.append(subExpression) 

299 

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

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

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

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

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

305 raise ex 

306 

307 for coverBin in bins: 

308 if not isinstance(coverBin, Bin): 

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

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

311 raise ex 

312 

313 coverBin._parent = self 

314 binList.append(coverBin) 

315 

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

317 parent._expressions.append(self) 

318 

319 @classmethod 

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

321 """ 

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

323 element. 

324 

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

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

327 ``None``. 

328 :returns: The expression. 

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

330 """ 

331 expression = cls( 

332 element.attrib["name"], 

333 element.attrib["key"], 

334 element.attrib["exprString"], 

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

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

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

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

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

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

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

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

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

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

345 cls._ParseUserAttributes(element), 

346 parent=parent 

347 ) 

348 

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

350 Expression.Parse(expressionElement, parent=expression) 

351 

352 return expression 

353 

354 @readonly 

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

356 """ 

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

358 

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

360 """ 

361 return self._parent 

362 

363 @readonly 

364 def Name(self) -> str: 

365 """ 

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

367 

368 :returns: The name, as stated. 

369 """ 

370 return self._name 

371 

372 @readonly 

373 def Key(self) -> str: 

374 """ 

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

376 

377 :returns: The key, as stated. 

378 """ 

379 return self._key 

380 

381 @readonly 

382 def Text(self) -> str: 

383 """ 

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

385 

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

387 """ 

388 return self._text 

389 

390 @readonly 

391 def Index(self) -> int: 

392 """ 

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

394 

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

396 """ 

397 return self._index 

398 

399 @readonly 

400 def Width(self) -> int: 

401 """ 

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

403 

404 :returns: The width. 

405 """ 

406 return self._width 

407 

408 @readonly 

409 def ID(self) -> StatementID: 

410 """ 

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

412 

413 :returns: The source statement identifier. 

414 """ 

415 return self._id 

416 

417 @readonly 

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

419 """ 

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

421 

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

423 """ 

424 return self._subExpressions 

425 

426 @readonly 

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

428 """ 

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

430 

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

432 """ 

433 return self._bins 

434 

435 @readonly 

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

437 """ 

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

439 

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

441 """ 

442 return self._statementType 

443 

444 @readonly 

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

446 """ 

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

448 

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

450 """ 

451 return self._expressions