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
« 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
37from pathlib import Path
38from typing import TYPE_CHECKING, Iterable, Mapping, Optional as Nullable, Self
40from lxml.etree import _Element
41from pyTooling.Common import getFullyQualifiedName
42from pyTooling.Decorators import export, readonly
43from pyTooling.MetaClasses import ExtendedType
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
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
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
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 """
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.
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.
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
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
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
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
112 self._parent = parent
113 self._path = path
114 self._sourceFileID = sourceFileID
116 if parent is not None:
117 parent._sourceFiles[sourceFileID] = self
119 @classmethod
120 def Parse(cls, element: _Element, *, parent: Nullable[Report] = None) -> Self:
121 """
122 Parse a source file from its ``<sourceFiles>`` element.
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)
130 @readonly
131 def Parent(self) -> Nullable[Report]:
132 """
133 Read-only property to access the report the source file belongs to (:attr:`_parent`).
135 :returns: The report; ``None``, if the source file belongs to none.
136 """
137 return self._parent
139 @readonly
140 def Path(self) -> Path:
141 """
142 Read-only property to access the path of the source file (:attr:`_path`).
144 :returns: The path, as the tool states it: absolute, or relative to the directory it ran in.
145 """
146 return self._path
148 @readonly
149 def SourceFileID(self) -> int:
150 """
151 Read-only property to access the ID of the source file (:attr:`_sourceFileID`).
153 :returns: The ID, by which statement identifiers name the file.
154 """
155 return self._sourceFileID
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.
164 An instance names the instance it is instantiated in by that instance's ID.
165 """
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.
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.
201 Its coverage of each kind is added by creating it with this instance as its parent.
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
235 super().__init__(userAttributes)
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
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
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
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
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
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
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
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
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 = []
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
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
312 self._designParameters[parameterName] = value
314 if parent is not None:
315 parent._instances.append(self)
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.
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 }
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 )
348 for coverageElement in element.iterfind("{*}toggleCoverage"):
349 ToggleCoverage.Parse(coverageElement, parent=instance)
351 for coverageElement in element.iterfind("{*}blockCoverage"):
352 BlockCoverage.Parse(coverageElement, parent=instance)
354 for coverageElement in element.iterfind("{*}conditionCoverage"):
355 ConditionCoverage.Parse(coverageElement, parent=instance)
357 for coverageElement in element.iterfind("{*}branchCoverage"):
358 BranchCoverage.Parse(coverageElement, parent=instance)
360 for coverageElement in element.iterfind("{*}fsmCoverage"):
361 FSMCoverage.Parse(coverageElement, parent=instance)
363 for coverageElement in element.iterfind("{*}assertionCoverage"):
364 AssertionCoverage.Parse(coverageElement, parent=instance)
366 for coverageElement in element.iterfind("{*}covergroupCoverage"):
367 CovergroupCoverage.Parse(coverageElement, parent=instance)
369 return instance
371 @readonly
372 def Parent(self) -> Nullable[Report]:
373 """
374 Read-only property to access the report the instance belongs to (:attr:`_parent`).
376 :returns: The report; ``None``, if the instance belongs to none.
377 """
378 return self._parent
380 @readonly
381 def Name(self) -> str:
382 """
383 Read-only property to access the name of the instance (:attr:`_name`).
385 :returns: The name, as stated.
386 """
387 return self._name
389 @readonly
390 def Key(self) -> str:
391 """
392 Read-only property to access the UCIS key of the instance (:attr:`_key`).
394 :returns: The key, as stated.
395 """
396 return self._key
398 @readonly
399 def ID(self) -> StatementID:
400 """
401 Read-only property to access where the instance is instantiated (:attr:`_id`).
403 :returns: The source statement identifier.
404 """
405 return self._id
407 @readonly
408 def InstanceID(self) -> Nullable[int]:
409 """
410 Read-only property to access the ID of the instance (:attr:`_instanceID`).
412 :returns: The ID; ``None``, if the report states none.
413 """
414 return self._instanceID
416 @readonly
417 def Alias(self) -> Nullable[str]:
418 """
419 Read-only property to access the alias of the instance's name (:attr:`_alias`).
421 :returns: The alias; ``None``, if the report states none.
422 """
423 return self._alias
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`).
430 :returns: The design unit's name; ``None``, if the report states none.
431 """
432 return self._moduleName
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`).
439 :returns: The ID; ``None`` for a top-level instance, or if the report states none.
440 """
441 return self._parentInstanceID
443 @readonly
444 def DesignParameters(self) -> dict[str, str]:
445 """
446 Read-only property to access the design parameters of the instance (:attr:`_designParameters`).
448 :returns: The parameters' values, by name.
449 """
450 return self._designParameters
452 @readonly
453 def BlockCoverages(self) -> list[BlockCoverage]:
454 """
455 Read-only property to access the statement and block coverage (:attr:`_blockCoverages`).
457 :returns: The block coverages, one per metric mode, in the order the report lists them.
458 """
459 return self._blockCoverages
461 @readonly
462 def BranchCoverages(self) -> list[BranchCoverage]:
463 """
464 Read-only property to access the branch coverage (:attr:`_branchCoverages`).
466 :returns: The branch coverages, one per metric mode, in the order the report lists them.
467 """
468 return self._branchCoverages
470 @readonly
471 def ToggleCoverages(self) -> list[ToggleCoverage]:
472 """
473 Read-only property to access the toggle coverage (:attr:`_toggleCoverages`).
475 :returns: The toggle coverages, one per metric mode, in the order the report lists them.
476 """
477 return self._toggleCoverages
479 @readonly
480 def ConditionCoverages(self) -> list[ConditionCoverage]:
481 """
482 Read-only property to access the condition and expression coverage (:attr:`_conditionCoverages`).
484 :returns: The condition coverages, one per metric mode, in the order the report lists them.
485 """
486 return self._conditionCoverages
488 @readonly
489 def FSMCoverages(self) -> list[FSMCoverage]:
490 """
491 Read-only property to access the FSM coverage (:attr:`_fsmCoverages`).
493 :returns: The FSM coverages, one per metric mode, in the order the report lists them.
494 """
495 return self._fsmCoverages
497 @readonly
498 def AssertionCoverages(self) -> list[AssertionCoverage]:
499 """
500 Read-only property to access the assertion coverage (:attr:`_assertionCoverages`).
502 :returns: The assertion coverages, one per metric mode, in the order the report lists them.
503 """
504 return self._assertionCoverages
506 @readonly
507 def CovergroupCoverages(self) -> list[CovergroupCoverage]:
508 """
509 Read-only property to access the covergroup coverage (:attr:`_covergroupCoverages`).
511 :returns: The covergroup coverages, one per metric mode, in the order the report lists them.
512 """
513 return self._covergroupCoverages