Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Conditions.py: 79%
154 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 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, \
38 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, \
46 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 ``metricMode`` isn't of type :class:`str`.
85 :raises ValueError: If parameter ``weight`` is ``None``.
86 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
87 :raises ValueError: If parameter ``weight`` is negative.
88 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
89 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
90 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
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 ValueError: If parameter ``name`` is ``None``.
192 :raises TypeError: If parameter ``name`` isn't of type :class:`str`.
193 :raises ValueError: If parameter ``key`` is ``None``.
194 :raises TypeError: If parameter ``key`` isn't of type :class:`str`.
195 :raises ValueError: If parameter ``text`` is ``None``.
196 :raises TypeError: If parameter ``text`` isn't of type :class:`str`.
197 :raises ValueError: If parameter ``index`` is ``None``.
198 :raises TypeError: If parameter ``index`` isn't of type :class:`int`.
199 :raises ValueError: If parameter ``index`` is negative.
200 :raises ValueError: If parameter ``width`` is ``None``.
201 :raises TypeError: If parameter ``width`` isn't of type :class:`int`.
202 :raises ValueError: If parameter ``width`` is negative.
203 :raises ValueError: If parameter ``statementID`` is ``None``.
204 :raises TypeError: If parameter ``statementID`` isn't of type
205 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.StatementID`.
206 :raises ValueError: If parameter ``subExpressions`` is ``None``.
207 :raises TypeError: If parameter ``subExpressions`` isn't iterable.
208 :raises TypeError: If parameter ``subExpressions`` contains an element not of type :class:`str`.
209 :raises ValueError: If parameter ``bins`` is ``None``.
210 :raises TypeError: If parameter ``bins`` isn't iterable.
211 :raises TypeError: If parameter ``bins`` contains an element not of type
212 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Bins.Bin`.
213 :raises TypeError: If parameter ``statementType`` isn't of type :class:`str`.
214 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
215 :raises ValueError: If parameter ``excluded`` is ``None``.
216 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
217 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
218 :raises ValueError: If parameter ``weight`` is ``None``.
219 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
220 :raises ValueError: If parameter ``weight`` is negative.
221 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
222 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
223 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
224 :raises TypeError: If parameter ``parent`` isn't of type :class:`ConditionCoverage` or :class:`Expression`.
225 """
226 super().__init__(userAttributes)
227 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
229 for parameterName, value in (
230 ("name", name),
231 ("key", key),
232 ("text", text)
233 ):
234 if value is None: 234 ↛ 235line 234 didn't jump to line 235 because the condition on line 234 was never true
235 raise ValueError(f"Parameter '{parameterName}' is None.")
236 elif not isinstance(value, str): 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true
237 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
238 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
239 raise ex
241 for parameterName, value in (
242 ("index", index),
243 ("width", width)
244 ):
245 if value is None: 245 ↛ 246line 245 didn't jump to line 246 because the condition on line 245 was never true
246 raise ValueError(f"Parameter '{parameterName}' is None.")
247 elif not isinstance(value, int) or isinstance(value, bool): 247 ↛ 248line 247 didn't jump to line 248 because the condition on line 247 was never true
248 ex = TypeError(f"Parameter '{parameterName}' is not of type 'int'.")
249 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
250 raise ex
251 elif value < 0:
252 ex = ValueError(f"Parameter '{parameterName}' is negative.")
253 ex.add_note(f"Got value '{value}'.")
254 raise ex
256 if statementID is None: 256 ↛ 257line 256 didn't jump to line 257 because the condition on line 256 was never true
257 raise ValueError("Parameter 'statementID' is None.")
258 elif not isinstance(statementID, StatementID): 258 ↛ 259line 258 didn't jump to line 259 because the condition on line 258 was never true
259 ex = TypeError("Parameter 'statementID' is not of type 'StatementID'.")
260 ex.add_note(f"Got type '{getFullyQualifiedName(statementID)}'.")
261 raise ex
263 if statementType is not None and not isinstance(statementType, str): 263 ↛ 264line 263 didn't jump to line 264 because the condition on line 263 was never true
264 ex = TypeError("Parameter 'statementType' is not of type 'str'.")
265 ex.add_note(f"Got type '{getFullyQualifiedName(statementType)}'.")
266 raise ex
268 if parent is not None and not isinstance(parent, (ConditionCoverage, Expression)):
269 ex = TypeError("Parameter 'parent' is not of type 'ConditionCoverage' or 'Expression'.")
270 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
271 raise ex
273 self._parent = parent
274 self._name = name
275 self._key = key
276 self._text = text
277 self._index = index
278 self._width = width
279 self._id = statementID
280 self._subExpressions = subExpressionList = []
281 self._bins = binList = []
282 self._statementType = statementType
283 self._expressions = []
285 if subExpressions is None: 285 ↛ 286line 285 didn't jump to line 286 because the condition on line 285 was never true
286 raise ValueError("Parameter 'subExpressions' is None.")
287 elif not isinstance(subExpressions, Iterable): 287 ↛ 288line 287 didn't jump to line 288 because the condition on line 287 was never true
288 ex = TypeError("Parameter 'subExpressions' is not iterable.")
289 ex.add_note(f"Got type '{getFullyQualifiedName(subExpressions)}'.")
290 raise ex
292 for subExpression in subExpressions:
293 if not isinstance(subExpression, str): 293 ↛ 294line 293 didn't jump to line 294 because the condition on line 293 was never true
294 ex = TypeError("Parameter 'subExpressions' contains an element not of type 'str'.")
295 ex.add_note(f"Got type '{getFullyQualifiedName(subExpression)}'.")
296 raise ex
298 subExpressionList.append(subExpression)
300 if bins is None: 300 ↛ 301line 300 didn't jump to line 301 because the condition on line 300 was never true
301 raise ValueError("Parameter 'bins' is None.")
302 elif not isinstance(bins, Iterable): 302 ↛ 303line 302 didn't jump to line 303 because the condition on line 302 was never true
303 ex = TypeError("Parameter 'bins' is not iterable.")
304 ex.add_note(f"Got type '{getFullyQualifiedName(bins)}'.")
305 raise ex
307 for coverBin in bins:
308 if not isinstance(coverBin, Bin):
309 ex = TypeError("Parameter 'bins' contains an element not of type 'Bin'.")
310 ex.add_note(f"Got type '{getFullyQualifiedName(coverBin)}'.")
311 raise ex
313 coverBin._parent = self
314 binList.append(coverBin)
316 if parent is not None: 316 ↛ exitline 316 didn't return from function '__init__' because the condition on line 316 was always true
317 parent._expressions.append(self)
319 @classmethod
320 def Parse(cls, element: _Element, *, parent: Nullable[ExpressionParentType] = None) -> Self:
321 """
322 Parse an expression, its bins and the expressions nested in it from its ``<expr>`` or ``<hierarchicalExpr>``
323 element.
325 :param element: The ``<expr>`` or ``<hierarchicalExpr>`` element.
326 :param parent: Optional, the condition coverage or expression the expression belongs to. Default:
327 ``None``.
328 :returns: The expression.
329 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
330 """
331 expression = cls(
332 element.attrib["name"],
333 element.attrib["key"],
334 element.attrib["exprString"],
335 int(element.attrib["index"]),
336 int(element.attrib["width"]),
337 StatementID.Parse(element.find("{*}id")),
338 [subExpressionElement.text or "" for subExpressionElement in element.iterfind("{*}subExpr")],
339 [Bin.Parse(binElement) for binElement in element.iterfind("{*}bin")],
340 element.attrib.get("statementType"),
341 element.attrib.get("alias"),
342 cls._ParseBoolean(element.attrib.get("excluded", "false")),
343 element.attrib.get("excludedReason"),
344 int(element.attrib.get("weight", "1")),
345 cls._ParseUserAttributes(element),
346 parent=parent
347 )
349 for expressionElement in element.iterfind("{*}hierarchicalExpr"):
350 Expression.Parse(expressionElement, parent=expression)
352 return expression
354 @readonly
355 def Parent(self) -> Nullable[ExpressionParentType]:
356 """
357 Read-only property to access the condition coverage or expression the expression belongs to (:attr:`_parent`).
359 :returns: The :class:`ConditionCoverage` or :class:`Expression`; ``None``, if the expression belongs to none.
360 """
361 return self._parent
363 @readonly
364 def Name(self) -> str:
365 """
366 Read-only property to access the UCIS name of the expression (:attr:`_name`).
368 :returns: The name, as stated.
369 """
370 return self._name
372 @readonly
373 def Key(self) -> str:
374 """
375 Read-only property to access the UCIS key of the expression (:attr:`_key`).
377 :returns: The key, as stated.
378 """
379 return self._key
381 @readonly
382 def Text(self) -> str:
383 """
384 Read-only property to access the expression's text (:attr:`_text`).
386 :returns: The text, e.g. ``(a&&b) || (c&&d)``.
387 """
388 return self._text
390 @readonly
391 def Index(self) -> int:
392 """
393 Read-only property to access the index of the expression in its parent expression (:attr:`_index`).
395 :returns: The index; ``0`` for a top-level expression.
396 """
397 return self._index
399 @readonly
400 def Width(self) -> int:
401 """
402 Read-only property to access the bit width of the expression (:attr:`_width`).
404 :returns: The width.
405 """
406 return self._width
408 @readonly
409 def ID(self) -> StatementID:
410 """
411 Read-only property to access where the expression is (:attr:`_id`).
413 :returns: The source statement identifier.
414 """
415 return self._id
417 @readonly
418 def SubExpressions(self) -> list[str]:
419 """
420 Read-only property to access the sub-expressions, whose values the bins combine (:attr:`_subExpressions`).
422 :returns: The sub-expressions, in the order the report lists them.
423 """
424 return self._subExpressions
426 @readonly
427 def Bins(self) -> list[Bin]:
428 """
429 Read-only property to access the bins of the combinations of the sub-expressions' values (:attr:`_bins`).
431 :returns: The bins, in the order the report lists them.
432 """
433 return self._bins
435 @readonly
436 def StatementType(self) -> Nullable[str]:
437 """
438 Read-only property to access the kind of the statement containing the expression (:attr:`_statementType`).
440 :returns: The kind, e.g. ``if``; ``None``, if the report states none.
441 """
442 return self._statementType
444 @readonly
445 def Expressions(self) -> list[Expression[Expression]]:
446 """
447 Read-only property to access the expressions nested in this one (:attr:`_expressions`).
449 :returns: The expressions, in the order the report lists them.
450 """
451 return self._expressions