Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Assertions.py: 81%
94 statements
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-14 02:26 +0000
« 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 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 ``metricMode`` isn't of type :class:`str`.
93 :raises ValueError: If parameter ``weight`` is ``None``.
94 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
95 :raises ValueError: If parameter ``weight`` is negative.
96 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
97 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
98 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
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 ValueError: If parameter ``name`` is ``None``.
178 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
179 :raises ValueError: If parameter ``assertionKind`` is ``None``.
180 :raises TypeError: If parameter ``assertionKind`` isn't of type :class:`str`.
181 :raises TypeError: If parameter ``bins`` isn't a mapping.
182 :raises TypeError: If parameter ``bins`` contains a key not of type :class:`AssertionBinKind`.
183 :raises TypeError: If parameter ``bins`` contains a value not of type
184 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`.
185 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
186 :raises ValueError: If parameter ``excluded`` is ``None``.
187 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
188 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
189 :raises ValueError: If parameter ``weight`` is ``None``.
190 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
191 :raises ValueError: If parameter ``weight`` is negative.
192 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
193 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
194 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
195 :raises TypeError: If parameter ``parent`` isn't of type :class:`AssertionCoverage`.
196 """
197 super().__init__(userAttributes)
198 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
200 for parameterName, value in (
201 ("name", name),
202 ("assertionKind", assertionKind)
203 ):
204 if value is None: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true
205 raise ValueError(f"Parameter '{parameterName}' is None.")
206 elif not isinstance(value, str): 206 ↛ 207line 206 didn't jump to line 207 because the condition on line 206 was never true
207 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
208 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
209 raise ex
211 if parent is not None and not isinstance(parent, AssertionCoverage): 211 ↛ 212line 211 didn't jump to line 212 because the condition on line 211 was never true
212 ex = TypeError("Parameter 'parent' is not of type 'AssertionCoverage'.")
213 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
214 raise ex
216 self._parent = parent
217 self._name = name
218 self._assertionKind = assertionKind
219 self._bins = {}
221 if bins is not None: 221 ↛ 240line 221 didn't jump to line 240 because the condition on line 221 was always true
222 if not isinstance(bins, Mapping): 222 ↛ 223line 222 didn't jump to line 223 because the condition on line 222 was never true
223 ex = TypeError("Parameter 'bins' is not a mapping.")
224 ex.add_note(f"Got type '{getFullyQualifiedName(bins)}'.")
225 raise ex
227 for binKind, coverBin in bins.items():
228 if not isinstance(binKind, AssertionBinKind):
229 ex = TypeError("Parameter 'bins' contains a key not of type 'AssertionBinKind'.")
230 ex.add_note(f"Got type '{getFullyQualifiedName(binKind)}'.")
231 raise ex
232 elif not isinstance(coverBin, Bin): 232 ↛ 233line 232 didn't jump to line 233 because the condition on line 232 was never true
233 ex = TypeError("Parameter 'bins' contains a value not of type 'Bin'.")
234 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}' for key '{binKind}'.")
235 raise ex
237 coverBin._parent = self
238 self._bins[binKind] = coverBin
240 if parent is not None: 240 ↛ exitline 240 didn't return from function '__init__' because the condition on line 240 was always true
241 parent._assertions.append(self)
243 @classmethod
244 def Parse(cls, element: _Element, *, parent: Nullable[AssertionCoverage] = None) -> Self:
245 """
246 Parse an assertion and its bins from its ``<assertion>`` element.
248 :param element: The ``<assertion>`` element.
249 :param parent: Optional, the assertion coverage the assertion belongs to. Default: ``None``.
250 :returns: The assertion.
251 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
252 """
253 bins = {
254 binKind: Bin.Parse(binElement)
255 for binKind in AssertionBinKind if (binElement := element.find(f"{{*}}{binKind}")) is not None
256 }
258 return cls(
259 element.attrib["name"],
260 element.attrib["assertionKind"],
261 bins,
262 element.attrib.get("alias"),
263 cls._ParseBoolean(element.attrib.get("excluded", "false")),
264 element.attrib.get("excludedReason"),
265 int(element.attrib.get("weight", "1")),
266 cls._ParseUserAttributes(element),
267 parent=parent
268 )
270 @readonly
271 def Parent(self) -> Nullable[AssertionCoverage]:
272 """
273 Read-only property to access the assertion coverage the assertion belongs to (:attr:`_parent`).
275 :returns: The assertion coverage; ``None``, if the assertion belongs to none.
276 """
277 return self._parent
279 @readonly
280 def Name(self) -> str:
281 """
282 Read-only property to access the name of the assertion (:attr:`_name`).
284 :returns: The name, as declared.
285 """
286 return self._name
288 @readonly
289 def AssertionKind(self) -> str:
290 """
291 Read-only property to access the kind of the assertion (:attr:`_assertionKind`).
293 :returns: The kind, as stated, e.g. ``assert`` or ``cover``.
294 """
295 return self._assertionKind
297 @readonly
298 def Bins(self) -> dict[AssertionBinKind, Bin]:
299 """
300 Read-only property to access the bins (:attr:`_bins`).
302 :returns: The bins, by the outcome they count, in the order of :class:`AssertionBinKind`.
303 """
304 return self._bins