Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Conditions.py: 79%
156 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 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
37from typing import TYPE_CHECKING, Generic, Iterable, Optional as Nullable, Self
38from typing import TypeVar
40from lxml.etree import _Element
41from pyTooling.Common import getFullyQualifiedName
42from pyTooling.Decorators import export, readonly
44from pyEDAA.Reports.CodeCoverage.UCIS.Bins import Bin
45from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, MetricCoverage, ObjectAttributesMixin, StatementID
46from pyEDAA.Reports.CodeCoverage.UCIS.Elements import UserAttribute
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
52__all__ = ["ExpressionParentType"]
54ExpressionParentType = TypeVar("ExpressionParentType", bound="ConditionCoverage | Expression")
55"""A type variable for the parent of an :class:`Expression`: a :class:`ConditionCoverage` or an :class:`Expression`."""
58@export
59class ConditionCoverage(MetricCoverage):
60 """
61 A ``<conditionCoverage>`` of an instance: its top-level expressions, of conditions or assignments.
62 """
64 _expressions: list[Expression[ConditionCoverage]] #: The top-level expressions.
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.
77 Its expressions are added by creating them with this coverage as their parent.
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 ``userAttributes`` isn't iterable.
85 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
86 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
87 :raises TypeError: If parameter ``metricMode`` isn't of type :class:`str`.
88 :raises ValueError: If parameter ``weight`` is ``None``.
89 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
90 :raises ValueError: If parameter ``weight`` is negative.
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)
96 self._expressions = []
98 if parent is not None:
99 parent._conditionCoverages.append(self)
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.
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 )
118 for expressionElement in element.iterfind("{*}expr"):
119 Expression.Parse(expressionElement, parent=conditionCoverage)
121 return conditionCoverage
123 @readonly
124 def Expressions(self) -> list[Expression[ConditionCoverage]]:
125 """
126 Read-only property to access the top-level expressions (:attr:`_expressions`).
128 :returns: The expressions, in the order the report lists them.
129 """
130 return self._expressions
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 """
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.
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.
174 The expressions nested in it are added by creating them with this expression as their parent.
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 TypeError: If parameter ``userAttributes`` isn't iterable.
192 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
193 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
194 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
195 :raises ValueError: If parameter ``excluded`` is ``None``.
196 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
197 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
198 :raises ValueError: If parameter ``weight`` is ``None``.
199 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
200 :raises ValueError: If parameter ``weight`` is negative.
201 :raises ValueError: If parameter ``name``, ``key`` or ``text`` is ``None``.
202 :raises TypeError: If parameter ``name``, ``key`` or ``text`` isn't of type :class:`str`.
203 :raises ValueError: If parameter ``index`` or ``width`` is ``None``.
204 :raises TypeError: If parameter ``index`` or ``width`` isn't of type :class:`int`.
205 :raises ValueError: If parameter ``index`` or ``width`` is negative.
206 :raises ValueError: If parameter ``statementID`` is ``None``.
207 :raises TypeError: If parameter ``statementID`` isn't of type
208 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
209 :raises TypeError: If parameter ``statementType`` isn't of type :class:`str`.
210 :raises TypeError: If parameter ``parent`` isn't of type :class:`ConditionCoverage` or :class:`Expression`.
211 :raises ValueError: If parameter ``subExpressions`` is ``None``.
212 :raises TypeError: If parameter ``subExpressions`` isn't iterable.
213 :raises TypeError: If parameter ``subExpressions`` contains an element not of type :class:`str`.
214 :raises ValueError: If parameter ``bins`` is ``None``.
215 :raises TypeError: If parameter ``bins`` isn't iterable.
216 :raises TypeError: If parameter ``bins`` contains an element not of type
217 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`.
218 """
219 super().__init__(userAttributes)
220 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
222 for parameterName, value in (
223 ("name", name),
224 ("key", key),
225 ("text", text)
226 ):
227 if value is None: 227 ↛ 228line 227 didn't jump to line 228 because the condition on line 227 was never true
228 raise ValueError(f"Parameter '{parameterName}' is None.")
229 elif not isinstance(value, str): 229 ↛ 230line 229 didn't jump to line 230 because the condition on line 229 was never true
230 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
231 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
232 raise ex
234 for parameterName, value in (
235 ("index", index),
236 ("width", width)
237 ):
238 if value is None: 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true
239 raise ValueError(f"Parameter '{parameterName}' is None.")
240 elif not isinstance(value, int) or isinstance(value, bool): 240 ↛ 241line 240 didn't jump to line 241 because the condition on line 240 was never true
241 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
242 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
243 raise ex
244 elif value < 0:
245 ex = ValueError(f"Parameter '{parameterName}' is negative.")
246 ex.add_note(f"Got value '{value}'.")
247 raise ex
249 if statementID is None: 249 ↛ 250line 249 didn't jump to line 250 because the condition on line 249 was never true
250 raise ValueError("Parameter 'statementID' is None.")
251 elif not isinstance(statementID, StatementID): 251 ↛ 252line 251 didn't jump to line 252 because the condition on line 251 was never true
252 ex = TypeError("Parameter 'statementID' is not of type 'StatementID'.")
253 ex.add_note(f"Got type '{getFullyQualifiedName(statementID)}'.")
254 raise ex
256 if statementType is not None and not isinstance(statementType, str): 256 ↛ 257line 256 didn't jump to line 257 because the condition on line 256 was never true
257 ex = TypeError("Parameter 'statementType' is not of type 'str'.")
258 ex.add_note(f"Got type '{getFullyQualifiedName(statementType)}'.")
259 raise ex
261 if parent is not None and not isinstance(parent, (ConditionCoverage, Expression)):
262 ex = TypeError("Parameter 'parent' is not of type 'ConditionCoverage' or 'Expression'.")
263 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
264 raise ex
266 self._parent = parent
267 self._name = name
268 self._key = key
269 self._text = text
270 self._index = index
271 self._width = width
272 self._id = statementID
273 self._subExpressions = []
274 self._bins = []
275 self._statementType = statementType
276 self._expressions = []
278 if subExpressions is None: 278 ↛ 279line 278 didn't jump to line 279 because the condition on line 278 was never true
279 raise ValueError("Parameter 'subExpressions' is None.")
280 elif not isinstance(subExpressions, Iterable): 280 ↛ 281line 280 didn't jump to line 281 because the condition on line 280 was never true
281 ex = TypeError("Parameter 'subExpressions' is not iterable.")
282 ex.add_note(f"Got type '{getFullyQualifiedName(subExpressions)}'.")
283 raise ex
285 for subExpression in subExpressions:
286 if not isinstance(subExpression, str): 286 ↛ 287line 286 didn't jump to line 287 because the condition on line 286 was never true
287 ex = TypeError("Parameter 'subExpressions' contains an element not of type 'str'.")
288 ex.add_note(f"Got type '{getFullyQualifiedName(subExpression)}'.")
289 raise ex
291 self._subExpressions.append(subExpression)
293 if bins is None: 293 ↛ 294line 293 didn't jump to line 294 because the condition on line 293 was never true
294 raise ValueError("Parameter 'bins' is None.")
295 elif not isinstance(bins, Iterable): 295 ↛ 296line 295 didn't jump to line 296 because the condition on line 295 was never true
296 ex = TypeError("Parameter 'bins' is not iterable.")
297 ex.add_note(f"Got type '{getFullyQualifiedName(bins)}'.")
298 raise ex
300 for coverBin in bins:
301 if not isinstance(coverBin, Bin):
302 ex = TypeError("Parameter 'bins' contains an element not of type 'Bin'.")
303 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.")
304 raise ex
306 coverBin._parent = self
307 self._bins.append(coverBin)
309 if parent is not None: 309 ↛ exitline 309 didn't return from function '__init__' because the condition on line 309 was always true
310 parent._expressions.append(self)
312 @classmethod
313 def Parse(cls, element: _Element, *, parent: Nullable[ExpressionParentType] = None) -> Self:
314 """
315 Parse an expression, its bins and the expressions nested in it from its ``<expr>`` or ``<hierarchicalExpr>``
316 element.
318 :param element: The ``<expr>`` or ``<hierarchicalExpr>`` element.
319 :param parent: Optional, the condition coverage or expression the expression belongs to. Default:
320 ``None``.
321 :returns: The expression.
322 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
323 """
324 expression = cls(
325 element.attrib["name"],
326 element.attrib["key"],
327 element.attrib["exprString"],
328 int(element.attrib["index"]),
329 int(element.attrib["width"]),
330 StatementID.Parse(element.find("{*}id")),
331 [subExpressionElement.text or "" for subExpressionElement in element.iterfind("{*}subExpr")],
332 [Bin.Parse(binElement) for binElement in element.iterfind("{*}bin")],
333 element.attrib.get("statementType"),
334 element.attrib.get("alias"),
335 cls._ParseBoolean(element.attrib.get("excluded", "false")),
336 element.attrib.get("excludedReason"),
337 int(element.attrib.get("weight", "1")),
338 cls._ParseUserAttributes(element),
339 parent=parent
340 )
342 for expressionElement in element.iterfind("{*}hierarchicalExpr"):
343 Expression.Parse(expressionElement, parent=expression)
345 return expression
347 @readonly
348 def Parent(self) -> Nullable[ExpressionParentType]:
349 """
350 Read-only property to access the condition coverage or expression the expression belongs to (:attr:`_parent`).
352 :returns: The :class:`ConditionCoverage` or :class:`Expression`; ``None``, if the expression belongs to none.
353 """
354 return self._parent
356 @readonly
357 def Name(self) -> str:
358 """
359 Read-only property to access the UCIS name of the expression (:attr:`_name`).
361 :returns: The name, as stated.
362 """
363 return self._name
365 @readonly
366 def Key(self) -> str:
367 """
368 Read-only property to access the UCIS key of the expression (:attr:`_key`).
370 :returns: The key, as stated.
371 """
372 return self._key
374 @readonly
375 def Text(self) -> str:
376 """
377 Read-only property to access the expression's text (:attr:`_text`).
379 :returns: The text, e.g. ``(a&&b) || (c&&d)``.
380 """
381 return self._text
383 @readonly
384 def Index(self) -> int:
385 """
386 Read-only property to access the index of the expression in its parent expression (:attr:`_index`).
388 :returns: The index; ``0`` for a top-level expression.
389 """
390 return self._index
392 @readonly
393 def Width(self) -> int:
394 """
395 Read-only property to access the bit width of the expression (:attr:`_width`).
397 :returns: The width.
398 """
399 return self._width
401 @readonly
402 def ID(self) -> StatementID:
403 """
404 Read-only property to access where the expression is (:attr:`_id`).
406 :returns: The source statement identifier.
407 """
408 return self._id
410 @readonly
411 def SubExpressions(self) -> list[str]:
412 """
413 Read-only property to access the sub-expressions, whose values the bins combine (:attr:`_subExpressions`).
415 :returns: The sub-expressions, in the order the report lists them.
416 """
417 return self._subExpressions
419 @readonly
420 def Bins(self) -> list[Bin]:
421 """
422 Read-only property to access the bins of the combinations of the sub-expressions' values (:attr:`_bins`).
424 :returns: The bins, in the order the report lists them.
425 """
426 return self._bins
428 @readonly
429 def StatementType(self) -> Nullable[str]:
430 """
431 Read-only property to access the kind of the statement containing the expression (:attr:`_statementType`).
433 :returns: The kind, e.g. ``if``; ``None``, if the report states none.
434 """
435 return self._statementType
437 @readonly
438 def Expressions(self) -> list[Expression[Expression]]:
439 """
440 Read-only property to access the expressions nested in this one (:attr:`_expressions`).
442 :returns: The expressions, in the order the report lists them.
443 """
444 return self._expressions