Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Assertions.py: 81%
94 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 assertion coverage of the UCIS XML interchange format: an instance's ``<assertionCoverage>`` and its assertions.
34An assertion - e.g. an ``assert``, ``assume`` or ``cover`` directive - has a bin per outcome.
35"""
36from __future__ import annotations
38from typing import TYPE_CHECKING, Iterable, Mapping, Optional as Nullable, Self
40from lxml.etree import _Element
41from pyTooling.Common import getFullyQualifiedName, StringEnum
42from pyTooling.Decorators import export, readonly
44from pyEDAA.Reports.CodeCoverage.UCIS.Bins import Bin
45from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, UserAttribute
47if TYPE_CHECKING: 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true
48 from pyEDAA.Reports.CodeCoverage.UCIS.Instances import InstanceCoverage
51@export
52class AssertionBinKind(StringEnum):
53 """
54 Kind of an assertion's bin: the outcome it counts, named as the bin's element.
55 """
57 Cover = "coverBin" #: The cover directive's sequence matched.
58 Pass = "passBin" #: The assertion passed.
59 Fail = "failBin" #: The assertion failed.
60 Vacuous = "vacuousBin" #: The assertion passed vacuously.
61 Disabled = "disabledBin" #: The assertion was disabled.
62 Attempt = "attemptBin" #: The assertion was attempted.
63 Active = "activeBin" #: The assertion's threads were active.
64 PeakActive = "peakActiveBin" #: The peak number of the assertion's active threads.
67@export
68class AssertionCoverage(MetricCoverage):
69 """
70 An ``<assertionCoverage>`` of an instance: its assertions.
71 """
73 _assertions: list[Assertion] #: The assertions.
75 def __init__(
76 self,
77 metricMode: Nullable[str] = None,
78 weight: int = 1,
79 userAttributes: Nullable[Iterable[UserAttribute]] = None,
80 *,
81 parent: Nullable[InstanceCoverage] = None
82 ) -> None:
83 """
84 Initialize the assertion coverage, and append it to the assertion coverages of its instance.
86 Its assertions are added by creating them with this coverage as their parent.
88 :param metricMode: Optional, the mode in which the coverage was measured. Default: ``None``.
89 :param weight: Optional, weight of the mode in computing the coverage. Default: ``1``.
90 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
91 :param parent: Optional, the instance the coverage belongs to. Default: ``None``.
92 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
93 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
94 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
95 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`.
96 :raises ValueError: If parameter ``weight`` is ``None``.
97 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
98 :raises ValueError: If parameter ``weight`` is negative.
99 :raises TypeError: If parameter ``parent`` isn't of type
100 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Instances.InstanceCoverage`.
101 """
102 super().__init__(metricMode, weight, userAttributes, parent=parent)
104 self._assertions = []
106 if parent is not None:
107 parent._assertionCoverages.append(self)
109 @classmethod
110 def Parse(cls, element: _Element, *, parent: Nullable[InstanceCoverage] = None) -> Self:
111 """
112 Parse an assertion coverage and its assertions from its ``<assertionCoverage>`` element.
114 :param element: The ``<assertionCoverage>`` element.
115 :param parent: Optional, the instance the coverage belongs to. Default: ``None``.
116 :returns: The assertion coverage.
117 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
118 """
119 assertionCoverage = cls(
120 element.attrib.get("metricMode"),
121 int(element.attrib.get("weight", "1")),
122 cls._ParseUserAttributes(element),
123 parent=parent
124 )
126 for assertionElement in element.iterfind("{*}assertion"):
127 Assertion.Parse(assertionElement, parent=assertionCoverage)
129 return assertionCoverage
131 @readonly
132 def Assertions(self) -> list[Assertion]:
133 """
134 Read-only property to access the assertions (:attr:`_assertions`).
136 :returns: The assertions, in the order the report lists them.
137 """
138 return self._assertions
141@export
142class Assertion(Base, ObjectAttributesMixin):
143 """
144 An ``<assertion>`` of an assertion coverage: its name, its kind and a bin per outcome the report counts.
145 """
147 _parent: Nullable[AssertionCoverage] #: The assertion coverage the assertion belongs to.
148 _name: str #: Name of the assertion.
149 _assertionKind: str #: Kind of the assertion, e.g. ``assert`` or ``cover``.
150 _bins: dict[AssertionBinKind, Bin] #: The bins, by the outcome they count.
152 def __init__(
153 self,
154 name: str,
155 assertionKind: str,
156 bins: Nullable[Mapping[AssertionBinKind, Bin]] = None,
157 alias: Nullable[str] = None,
158 excluded: bool = False,
159 excludedReason: Nullable[str] = None,
160 weight: int = 1,
161 userAttributes: Nullable[Iterable[UserAttribute]] = None,
162 *,
163 parent: Nullable[AssertionCoverage] = None
164 ) -> None:
165 """
166 Initialize the assertion, become its bins' parent, and append it to the assertions of its assertion coverage.
168 :param name: Name of the assertion, e.g. ``a1``.
169 :param assertionKind: Kind of the assertion, e.g. ``assert`` or ``cover``.
170 :param bins: Optional, the bins, by the outcome they count. Default: ``None``.
171 :param alias: Optional, an alias of the assertion's name. Default: ``None``.
172 :param excluded: Optional, whether the assertion is excluded from the coverage. Default: ``False``.
173 :param excludedReason: Optional, why the assertion is excluded. Default: ``None``.
174 :param weight: Optional, weight of the assertion in computing the coverage. Default: ``1``.
175 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
176 :param parent: Optional, the assertion coverage the assertion belongs to. Default: ``None``.
177 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
178 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
179 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
180 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
181 :raises ValueError: If parameter ``excluded`` is ``None``.
182 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
183 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
184 :raises ValueError: If parameter ``weight`` is ``None``.
185 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
186 :raises ValueError: If parameter ``weight`` is negative.
187 :raises ValueError: If parameter ``name`` or ``assertionKind`` is ``None``.
188 :raises TypeError: If parameter ``name`` or ``assertionKind`` isn't of type :class:`str`.
189 :raises TypeError: If parameter ``parent`` isn't of type :class:`AssertionCoverage`.
190 :raises TypeError: If parameter ``bins`` isn't a mapping.
191 :raises TypeError: If parameter ``bins`` contains a key not of type :class:`AssertionBinKind`.
192 :raises TypeError: If parameter ``bins`` contains a value not of type
193 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`.
194 """
195 super().__init__(userAttributes)
196 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
198 for parameterName, value in (
199 ("name", name),
200 ("assertionKind", assertionKind)
201 ):
202 if value is None: 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true
203 raise ValueError(f"Parameter '{parameterName}' is None.")
204 elif not isinstance(value, str): 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true
205 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
206 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
207 raise ex
209 if parent is not None and not isinstance(parent, AssertionCoverage): 209 ↛ 210line 209 didn't jump to line 210 because the condition on line 209 was never true
210 ex = TypeError("Parameter 'parent' is not of type 'AssertionCoverage'.")
211 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
212 raise ex
214 self._parent = parent
215 self._name = name
216 self._assertionKind = assertionKind
217 self._bins = {}
219 if bins is not None: 219 ↛ 238line 219 didn't jump to line 238 because the condition on line 219 was always true
220 if not isinstance(bins, Mapping): 220 ↛ 221line 220 didn't jump to line 221 because the condition on line 220 was never true
221 ex = TypeError("Parameter 'bins' is not a mapping.")
222 ex.add_note(f"Got type '{getFullyQualifiedName(bins)}'.")
223 raise ex
225 for binKind, coverBin in bins.items():
226 if not isinstance(binKind, AssertionBinKind):
227 ex = TypeError("Parameter 'bins' contains a key not of type 'AssertionBinKind'.")
228 ex.add_note(f"Got type '{getFullyQualifiedName(binKind)}'.")
229 raise ex
230 elif not isinstance(coverBin, Bin): 230 ↛ 231line 230 didn't jump to line 231 because the condition on line 230 was never true
231 ex = TypeError("Parameter 'bins' contains a value not of type 'Bin'.")
232 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}' for key '{binKind}'.")
233 raise ex
235 coverBin._parent = self
236 self._bins[binKind] = coverBin
238 if parent is not None: 238 ↛ exitline 238 didn't return from function '__init__' because the condition on line 238 was always true
239 parent._assertions.append(self)
241 @classmethod
242 def Parse(cls, element: _Element, *, parent: Nullable[AssertionCoverage] = None) -> Self:
243 """
244 Parse an assertion and its bins from its ``<assertion>`` element.
246 :param element: The ``<assertion>`` element.
247 :param parent: Optional, the assertion coverage the assertion belongs to. Default: ``None``.
248 :returns: The assertion.
249 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
250 """
251 bins = {
252 binKind: Bin.Parse(binElement)
253 for binKind in AssertionBinKind if (binElement := element.find(f"{{*}}{binKind}")) is not None
254 }
256 return cls(
257 element.attrib["name"],
258 element.attrib["assertionKind"],
259 bins,
260 element.attrib.get("alias"),
261 cls._ParseBoolean(element.attrib.get("excluded", "false")),
262 element.attrib.get("excludedReason"),
263 int(element.attrib.get("weight", "1")),
264 cls._ParseUserAttributes(element),
265 parent=parent
266 )
268 @readonly
269 def Parent(self) -> Nullable[AssertionCoverage]:
270 """
271 Read-only property to access the assertion coverage the assertion belongs to (:attr:`_parent`).
273 :returns: The assertion coverage; ``None``, if the assertion belongs to none.
274 """
275 return self._parent
277 @readonly
278 def Name(self) -> str:
279 """
280 Read-only property to access the name of the assertion (:attr:`_name`).
282 :returns: The name, as declared.
283 """
284 return self._name
286 @readonly
287 def AssertionKind(self) -> str:
288 """
289 Read-only property to access the kind of the assertion (:attr:`_assertionKind`).
291 :returns: The kind, as stated, e.g. ``assert`` or ``cover``.
292 """
293 return self._assertionKind
295 @readonly
296 def Bins(self) -> dict[AssertionBinKind, Bin]:
297 """
298 Read-only property to access the bins (:attr:`_bins`).
300 :returns: The bins, by the outcome they count, in the order of :class:`AssertionBinKind`.
301 """
302 return self._bins