Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Instances.py: 83%

220 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 instances of the UCIS XML interchange format, each with its coverage of each kind, and the source files the 

33report's statement identifiers name by their ID. 

34""" 

35from __future__ import annotations 

36 

37from pathlib import Path 

38from typing import TYPE_CHECKING, Iterable, Mapping, Optional as Nullable, Self 

39 

40from lxml.etree import _Element 

41from pyTooling.Common import getFullyQualifiedName 

42from pyTooling.Decorators import export, readonly 

43from pyTooling.MetaClasses import ExtendedType 

44 

45from pyEDAA.Reports.CodeCoverage.UCIS.Assertions import AssertionCoverage 

46from pyEDAA.Reports.CodeCoverage.UCIS.Blocks import BlockCoverage 

47from pyEDAA.Reports.CodeCoverage.UCIS.Branches import BranchCoverage 

48from pyEDAA.Reports.CodeCoverage.UCIS.Conditions import ConditionCoverage 

49from pyEDAA.Reports.CodeCoverage.UCIS.Covergroups import CovergroupCoverage 

50from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, StatementID, UserAttribute 

51from pyEDAA.Reports.CodeCoverage.UCIS.FSMs import FSMCoverage 

52from pyEDAA.Reports.CodeCoverage.UCIS.Toggles import ToggleCoverage 

53 

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

55 from pyEDAA.Reports.CodeCoverage.UCIS import Report 

56 

57# A class with a property named like a class - ``Path`` - can't name that class in the annotation of a field: the class 

58# body's namespace, where annotations are evaluated, binds the name to the property. 

59_Path = Path 

60 

61 

62@export 

63class SourceFile(metaclass=ExtendedType, slots=True): 

64 """ 

65 A ``<sourceFiles>`` of the report: a source file's path and the ID, by which statement identifiers name it. 

66 """ 

67 

68 _parent: Nullable[Report] #: The report the source file belongs to. 

69 _path: _Path #: Path of the source file. 

70 _sourceFileID: int #: ID of the source file. 

71 

72 def __init__(self, path: Path, sourceFileID: int, *, parent: Nullable[Report] = None) -> None: 

73 """ 

74 Initialize the source file, and add it to the source files of its report. 

75 

76 :param path: Path of the source file, as the tool states it. 

77 :param sourceFileID: ID of the source file, counted from 1. 

78 :param parent: Optional, the report the source file belongs to; the source file is added to its source 

79 files. Default: ``None``. 

80 :raises ValueError: If parameter ``path`` is ``None``. 

81 :raises TypeError: If parameter ``path`` isn't of type :class:`~pathlib.Path`. 

82 :raises ValueError: If parameter ``sourceFileID`` is ``None``. 

83 :raises TypeError: If parameter ``sourceFileID`` isn't of type :class:`int`. 

84 :raises ValueError: If parameter ``sourceFileID`` is less than 1. 

85 :raises TypeError: If parameter ``parent`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Report`. 

86 """ 

87 from pyEDAA.Reports.CodeCoverage.UCIS import Report 

88 

89 if path is None: 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true

90 raise ValueError("Parameter 'path' is None.") 

91 elif not isinstance(path, Path): 

92 ex = TypeError("Parameter 'path' is not of type 'Path'.") 

93 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.") 

94 raise ex 

95 

96 if sourceFileID is None: 96 ↛ 97line 96 didn't jump to line 97 because the condition on line 96 was never true

97 raise ValueError("Parameter 'sourceFileID' is None.") 

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

99 ex = TypeError("Parameter 'sourceFileID' is not of type 'int'.") 

100 ex.add_note(f"Got type '{getFullyQualifiedName(sourceFileID)}'.") 

101 raise ex 

102 elif sourceFileID < 1: 

103 ex = ValueError("Parameter 'sourceFileID' is less than 1.") 

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

105 raise ex 

106 

107 if parent is not None and not isinstance(parent, Report): 

108 ex = TypeError("Parameter 'parent' is not of type 'Report'.") 

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

110 raise ex 

111 

112 self._parent = parent 

113 self._path = path 

114 self._sourceFileID = sourceFileID 

115 

116 if parent is not None: 

117 parent._sourceFiles[sourceFileID] = self 

118 

119 @classmethod 

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

121 """ 

122 Parse a source file from its ``<sourceFiles>`` element. 

123 

124 :param element: The ``<sourceFiles>`` element. 

125 :param parent: Optional, the report the source file belongs to. Default: ``None``. 

126 :returns: The source file. 

127 """ 

128 return cls(Path(element.attrib["fileName"]), int(element.attrib["id"]), parent=parent) 

129 

130 @readonly 

131 def Parent(self) -> Nullable[Report]: 

132 """ 

133 Read-only property to access the report the source file belongs to (:attr:`_parent`). 

134 

135 :returns: The report; ``None``, if the source file belongs to none. 

136 """ 

137 return self._parent 

138 

139 @readonly 

140 def Path(self) -> Path: 

141 """ 

142 Read-only property to access the path of the source file (:attr:`_path`). 

143 

144 :returns: The path, as the tool states it: absolute, or relative to the directory it ran in. 

145 """ 

146 return self._path 

147 

148 @readonly 

149 def SourceFileID(self) -> int: 

150 """ 

151 Read-only property to access the ID of the source file (:attr:`_sourceFileID`). 

152 

153 :returns: The ID, by which statement identifiers name the file. 

154 """ 

155 return self._sourceFileID 

156 

157 

158@export 

159class InstanceCoverage(Base): 

160 """ 

161 An ``<instanceCoverages>`` of the report: an instance of a design unit, where it is instantiated, its parameters, and 

162 its coverage of each kind. 

163 

164 An instance names the instance it is instantiated in by that instance's ID. 

165 """ 

166 

167 _parent: Nullable[Report] #: The report the instance belongs to. 

168 _name: str #: Name of the instance. 

169 _key: str #: UCIS key of the instance. 

170 _id: StatementID #: Where the instance is instantiated. 

171 _instanceID: Nullable[int] #: ID of the instance. 

172 _alias: Nullable[str] #: An alias of the instance's name. 

173 _moduleName: Nullable[str] #: Name of the design unit the instance instantiates. 

174 _parentInstanceID: Nullable[int] #: ID of the instance this one is instantiated in. 

175 _designParameters: dict[str, str] #: The design parameters of the instance, by name. 

176 _toggleCoverages: list[ToggleCoverage] #: The toggle coverage, per metric mode. 

177 _blockCoverages: list[BlockCoverage] #: The statement and block coverage, per metric mode. 

178 _conditionCoverages: list[ConditionCoverage] #: The condition and expression coverage, per metric mode. 

179 _branchCoverages: list[BranchCoverage] #: The branch coverage, per metric mode. 

180 _fsmCoverages: list[FSMCoverage] #: The FSM coverage, per metric mode. 

181 _assertionCoverages: list[AssertionCoverage] #: The assertion coverage, per metric mode. 

182 _covergroupCoverages: list[CovergroupCoverage] #: The covergroup coverage, per metric mode. 

183 

184 def __init__( 

185 self, 

186 name: str, 

187 key: str, 

188 statementID: StatementID, 

189 instanceID: Nullable[int] = None, 

190 alias: Nullable[str] = None, 

191 moduleName: Nullable[str] = None, 

192 parentInstanceID: Nullable[int] = None, 

193 designParameters: Nullable[Mapping[str, str]] = None, 

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

195 *, 

196 parent: Nullable[Report] = None 

197 ) -> None: 

198 """ 

199 Initialize the instance, and append it to the instances of its report. 

200 

201 Its coverage of each kind is added by creating it with this instance as its parent. 

202 

203 :param name: Name of the instance, e.g. ``top.dut``. 

204 :param key: UCIS key of the instance. 

205 :param statementID: Where the instance is instantiated. 

206 :param instanceID: Optional, ID of the instance. Default: ``None``. 

207 :param alias: Optional, an alias of the instance's name. Default: ``None``. 

208 :param moduleName: Optional, name of the design unit the instance instantiates. Default: ``None``. 

209 :param parentInstanceID: Optional, ID of the instance this one is instantiated in. Default: ``None``. 

210 :param designParameters: Optional, the design parameters of the instance, by name, e.g. ``{"WIDTH": "8"}``. 

211 Default: ``None``. 

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

213 :param parent: Optional, the report the instance belongs to; the instance is appended to its instances. 

214 Default: ``None``. 

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

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

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

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

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

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

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

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

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

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

225 :raises TypeError: If parameter ``instanceID`` isn't of type :class:`int`. 

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

227 :raises TypeError: If parameter ``moduleName`` isn't of type :class:`str`. 

228 :raises TypeError: If parameter ``parentInstanceID`` isn't of type :class:`int`. 

229 :raises TypeError: If parameter ``designParameters`` isn't a mapping. 

230 :raises TypeError: If parameter ``designParameters`` contains a name or value not of type :class:`str`. 

231 :raises TypeError: If parameter ``parent`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Report`. 

232 """ 

233 from pyEDAA.Reports.CodeCoverage.UCIS import Report 

234 

235 super().__init__(userAttributes) 

236 

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

238 raise ValueError("Parameter 'name' is None.") 

239 elif not isinstance(name, str): 239 ↛ 240line 239 didn't jump to line 240 because the condition on line 239 was never true

240 ex = TypeError("Parameter 'name' is not of type 'str'.") 

241 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

242 raise ex 

243 

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

245 raise ValueError("Parameter 'key' is None.") 

246 elif not isinstance(key, str): 246 ↛ 247line 246 didn't jump to line 247 because the condition on line 246 was never true

247 ex = TypeError("Parameter 'key' is not of type 'str'.") 

248 ex.add_note(f"Got type '{getFullyQualifiedName(key)}'.") 

249 raise ex 

250 

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

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

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

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

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

256 raise ex 

257 

258 if instanceID is not None and (not isinstance(instanceID, int) or isinstance(instanceID, bool)): 

259 ex = TypeError("Parameter 'instanceID' is not of type 'int'.") 

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

261 raise ex 

262 

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

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

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

266 raise ex 

267 

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

269 ex = TypeError("Parameter 'moduleName' is not of type 'str'.") 

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

271 raise ex 

272 

273 if parentInstanceID is not None and (not isinstance(parentInstanceID, int) or isinstance(parentInstanceID, bool)): 273 ↛ 274line 273 didn't jump to line 274 because the condition on line 273 was never true

274 ex = TypeError("Parameter 'parentInstanceID' is not of type 'int'.") 

275 ex.add_note(f"Got type '{getFullyQualifiedName(parentInstanceID)}'.") 

276 raise ex 

277 

278 if parent is not None and not isinstance(parent, Report): 278 ↛ 279line 278 didn't jump to line 279 because the condition on line 278 was never true

279 ex = TypeError("Parameter 'parent' is not of type 'Report'.") 

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

281 raise ex 

282 

283 self._parent = parent 

284 self._name = name 

285 self._key = key 

286 self._id = statementID 

287 self._instanceID = instanceID 

288 self._alias = alias 

289 self._moduleName = moduleName 

290 self._parentInstanceID = parentInstanceID 

291 self._designParameters = {} 

292 self._toggleCoverages = [] 

293 self._blockCoverages = [] 

294 self._conditionCoverages = [] 

295 self._branchCoverages = [] 

296 self._fsmCoverages = [] 

297 self._assertionCoverages = [] 

298 self._covergroupCoverages = [] 

299 

300 if designParameters is not None: 

301 if not isinstance(designParameters, Mapping): 301 ↛ 302line 301 didn't jump to line 302 because the condition on line 301 was never true

302 ex = TypeError("Parameter 'designParameters' is not a mapping.") 

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

304 raise ex 

305 

306 for parameterName, value in designParameters.items(): 

307 if not isinstance(parameterName, str) or not isinstance(value, str): 

308 ex = TypeError("Parameter 'designParameters' contains a name or value not of type 'str'.") 

309 ex.add_note(f"Got types '{getFullyQualifiedName(parameterName)}' and '{getFullyQualifiedName(value)}'.") 

310 raise ex 

311 

312 self._designParameters[parameterName] = value 

313 

314 if parent is not None: 

315 parent._instances.append(self) 

316 

317 @classmethod 

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

319 """ 

320 Parse an instance and its coverage of each kind from its ``<instanceCoverages>`` element. 

321 

322 :param element: The ``<instanceCoverages>`` element. 

323 :param parent: Optional, the report the instance belongs to. Default: ``None``. 

324 :returns: The instance. 

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

326 """ 

327 attributes = element.attrib 

328 instanceID = attributes.get("instanceId") 

329 parentInstanceID = attributes.get("parentInstanceId") 

330 designParameters = { 

331 parameterElement.findtext("{*}name"): parameterElement.findtext("{*}value") 

332 for parameterElement in element.iterfind("{*}designParameter") 

333 } 

334 

335 instance = cls( 

336 attributes["name"], 

337 attributes["key"], 

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

339 None if instanceID is None else int(instanceID), 

340 attributes.get("alias"), 

341 attributes.get("moduleName"), 

342 None if parentInstanceID is None else int(parentInstanceID), 

343 designParameters, 

344 cls._ParseUserAttributes(element), 

345 parent=parent 

346 ) 

347 

348 for coverageElement in element.iterfind("{*}toggleCoverage"): 

349 ToggleCoverage.Parse(coverageElement, parent=instance) 

350 

351 for coverageElement in element.iterfind("{*}blockCoverage"): 

352 BlockCoverage.Parse(coverageElement, parent=instance) 

353 

354 for coverageElement in element.iterfind("{*}conditionCoverage"): 

355 ConditionCoverage.Parse(coverageElement, parent=instance) 

356 

357 for coverageElement in element.iterfind("{*}branchCoverage"): 

358 BranchCoverage.Parse(coverageElement, parent=instance) 

359 

360 for coverageElement in element.iterfind("{*}fsmCoverage"): 

361 FSMCoverage.Parse(coverageElement, parent=instance) 

362 

363 for coverageElement in element.iterfind("{*}assertionCoverage"): 

364 AssertionCoverage.Parse(coverageElement, parent=instance) 

365 

366 for coverageElement in element.iterfind("{*}covergroupCoverage"): 

367 CovergroupCoverage.Parse(coverageElement, parent=instance) 

368 

369 return instance 

370 

371 @readonly 

372 def Parent(self) -> Nullable[Report]: 

373 """ 

374 Read-only property to access the report the instance belongs to (:attr:`_parent`). 

375 

376 :returns: The report; ``None``, if the instance belongs to none. 

377 """ 

378 return self._parent 

379 

380 @readonly 

381 def Name(self) -> str: 

382 """ 

383 Read-only property to access the name of the instance (:attr:`_name`). 

384 

385 :returns: The name, as stated. 

386 """ 

387 return self._name 

388 

389 @readonly 

390 def Key(self) -> str: 

391 """ 

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

393 

394 :returns: The key, as stated. 

395 """ 

396 return self._key 

397 

398 @readonly 

399 def ID(self) -> StatementID: 

400 """ 

401 Read-only property to access where the instance is instantiated (:attr:`_id`). 

402 

403 :returns: The source statement identifier. 

404 """ 

405 return self._id 

406 

407 @readonly 

408 def InstanceID(self) -> Nullable[int]: 

409 """ 

410 Read-only property to access the ID of the instance (:attr:`_instanceID`). 

411 

412 :returns: The ID; ``None``, if the report states none. 

413 """ 

414 return self._instanceID 

415 

416 @readonly 

417 def Alias(self) -> Nullable[str]: 

418 """ 

419 Read-only property to access the alias of the instance's name (:attr:`_alias`). 

420 

421 :returns: The alias; ``None``, if the report states none. 

422 """ 

423 return self._alias 

424 

425 @readonly 

426 def ModuleName(self) -> Nullable[str]: 

427 """ 

428 Read-only property to access the name of the design unit the instance instantiates (:attr:`_moduleName`). 

429 

430 :returns: The design unit's name; ``None``, if the report states none. 

431 """ 

432 return self._moduleName 

433 

434 @readonly 

435 def ParentInstanceID(self) -> Nullable[int]: 

436 """ 

437 Read-only property to access the ID of the instance this one is instantiated in (:attr:`_parentInstanceID`). 

438 

439 :returns: The ID; ``None`` for a top-level instance, or if the report states none. 

440 """ 

441 return self._parentInstanceID 

442 

443 @readonly 

444 def DesignParameters(self) -> dict[str, str]: 

445 """ 

446 Read-only property to access the design parameters of the instance (:attr:`_designParameters`). 

447 

448 :returns: The parameters' values, by name. 

449 """ 

450 return self._designParameters 

451 

452 @readonly 

453 def BlockCoverages(self) -> list[BlockCoverage]: 

454 """ 

455 Read-only property to access the statement and block coverage (:attr:`_blockCoverages`). 

456 

457 :returns: The block coverages, one per metric mode, in the order the report lists them. 

458 """ 

459 return self._blockCoverages 

460 

461 @readonly 

462 def BranchCoverages(self) -> list[BranchCoverage]: 

463 """ 

464 Read-only property to access the branch coverage (:attr:`_branchCoverages`). 

465 

466 :returns: The branch coverages, one per metric mode, in the order the report lists them. 

467 """ 

468 return self._branchCoverages 

469 

470 @readonly 

471 def ToggleCoverages(self) -> list[ToggleCoverage]: 

472 """ 

473 Read-only property to access the toggle coverage (:attr:`_toggleCoverages`). 

474 

475 :returns: The toggle coverages, one per metric mode, in the order the report lists them. 

476 """ 

477 return self._toggleCoverages 

478 

479 @readonly 

480 def ConditionCoverages(self) -> list[ConditionCoverage]: 

481 """ 

482 Read-only property to access the condition and expression coverage (:attr:`_conditionCoverages`). 

483 

484 :returns: The condition coverages, one per metric mode, in the order the report lists them. 

485 """ 

486 return self._conditionCoverages 

487 

488 @readonly 

489 def FSMCoverages(self) -> list[FSMCoverage]: 

490 """ 

491 Read-only property to access the FSM coverage (:attr:`_fsmCoverages`). 

492 

493 :returns: The FSM coverages, one per metric mode, in the order the report lists them. 

494 """ 

495 return self._fsmCoverages 

496 

497 @readonly 

498 def AssertionCoverages(self) -> list[AssertionCoverage]: 

499 """ 

500 Read-only property to access the assertion coverage (:attr:`_assertionCoverages`). 

501 

502 :returns: The assertion coverages, one per metric mode, in the order the report lists them. 

503 """ 

504 return self._assertionCoverages 

505 

506 @readonly 

507 def CovergroupCoverages(self) -> list[CovergroupCoverage]: 

508 """ 

509 Read-only property to access the covergroup coverage (:attr:`_covergroupCoverages`). 

510 

511 :returns: The covergroup coverages, one per metric mode, in the order the report lists them. 

512 """ 

513 return self._covergroupCoverages