Coverage for pyEDAA/Reports/CodeCoverage/UCIS/Bins.py: 80%
101 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 bins of the UCIS XML interchange format: a coverage item's count - how often it was covered -, the tests, which
33covered it, and its goal.
34"""
35from __future__ import annotations
37from typing import Iterable, Optional as Nullable, Self
39from lxml.etree import _Element
40from pyTooling.Common import getFullyQualifiedName
41from pyTooling.Decorators import export, readonly
42from pyTooling.MetaClasses import ExtendedType
44from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, ObjectAttributesMixin, UserAttribute
47@export
48class BinContents(metaclass=ExtendedType, slots=True):
49 """
50 A ``<contents>`` of a bin: how often its coverage item was covered, the tests, which covered it, and the components
51 of its unique ID.
53 It is a value, as a path is: it names no parent.
54 """
56 _coverageCount: int #: How often the coverage item was covered.
57 _historyNodeIDs: list[int] #: The IDs of the history nodes, which covered the coverage item.
58 _nameComponent: Nullable[str] #: The name component of the coverage item's unique ID.
59 _typeComponent: Nullable[str] #: The type component of the coverage item's unique ID.
61 def __init__(
62 self,
63 coverageCount: int,
64 historyNodeIDs: Nullable[Iterable[int]] = None,
65 nameComponent: Nullable[str] = None,
66 typeComponent: Nullable[str] = None
67 ) -> None:
68 """
69 Initialize the contents of a bin.
71 :param coverageCount: How often the coverage item was covered.
72 :param historyNodeIDs: Optional, the IDs of the history nodes, which covered the coverage item. Default: ``None``.
73 :param nameComponent: Optional, the name component of the coverage item's unique ID. Default: ``None``.
74 :param typeComponent: Optional, the type component of the coverage item's unique ID, e.g. ``:5:`` for a
75 statement. Default: ``None``.
76 :raises ValueError: If parameter ``coverageCount`` is ``None``.
77 :raises TypeError: If parameter ``coverageCount`` isn't of type :class:`int`.
78 :raises ValueError: If parameter ``coverageCount`` is negative.
79 :raises TypeError: If parameter ``historyNodeIDs`` isn't iterable.
80 :raises TypeError: If parameter ``historyNodeIDs`` contains an element not of type :class:`int`.
81 :raises TypeError: If parameter ``nameComponent`` isn't of type :class:`str`.
82 :raises TypeError: If parameter ``typeComponent`` isn't of type :class:`str`.
83 """
84 if coverageCount is None: 84 ↛ 85line 84 didn't jump to line 85 because the condition on line 84 was never true
85 raise ValueError("Parameter 'coverageCount' is None.")
86 elif not isinstance(coverageCount, int) or isinstance(coverageCount, bool): 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 ex = TypeError("Parameter 'coverageCount' is not of type 'int'.")
88 ex.add_note(f"Got type '{getFullyQualifiedName(coverageCount)}'.")
89 raise ex
90 elif coverageCount < 0:
91 ex = ValueError("Parameter 'coverageCount' is negative.")
92 ex.add_note(f"Got value '{coverageCount}'.")
93 raise ex
95 if nameComponent is not None and not isinstance(nameComponent, str): 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true
96 ex = TypeError("Parameter 'nameComponent' is not of type 'str'.")
97 ex.add_note(f"Got type '{getFullyQualifiedName(nameComponent)}'.")
98 raise ex
100 if typeComponent is not None and not isinstance(typeComponent, str): 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true
101 ex = TypeError("Parameter 'typeComponent' is not of type 'str'.")
102 ex.add_note(f"Got type '{getFullyQualifiedName(typeComponent)}'.")
103 raise ex
105 self._coverageCount = coverageCount
106 self._historyNodeIDs = []
107 self._nameComponent = nameComponent
108 self._typeComponent = typeComponent
110 if historyNodeIDs is not None:
111 if not isinstance(historyNodeIDs, Iterable): 111 ↛ 112line 111 didn't jump to line 112 because the condition on line 111 was never true
112 ex = TypeError("Parameter 'historyNodeIDs' is not iterable.")
113 ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeIDs)}'.")
114 raise ex
116 for historyNodeID in historyNodeIDs:
117 if not isinstance(historyNodeID, int) or isinstance(historyNodeID, bool):
118 ex = TypeError("Parameter 'historyNodeIDs' contains an element not of type 'int'.")
119 ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeID)}'.")
120 raise ex
122 self._historyNodeIDs.append(historyNodeID)
124 @classmethod
125 def Parse(cls, element: _Element) -> Self:
126 """
127 Parse the contents of a bin from its ``<contents>`` element.
129 :param element: The ``<contents>`` element.
130 :returns: The contents.
131 """
132 return cls(
133 int(element.attrib["coverageCount"]),
134 [int(idElement.text) for idElement in element.iterfind("{*}historyNodeId")],
135 element.attrib.get("nameComponent"),
136 element.attrib.get("typeComponent")
137 )
139 @readonly
140 def CoverageCount(self) -> int:
141 """
142 Read-only property to access how often the coverage item was covered (:attr:`_coverageCount`).
144 :returns: The count, e.g. how often a statement ran.
145 """
146 return self._coverageCount
148 @readonly
149 def HistoryNodeIDs(self) -> list[int]:
150 """
151 Read-only property to access the IDs of the history nodes, which covered the item (:attr:`_historyNodeIDs`).
153 :returns: The IDs, in the order the report lists them.
154 """
155 return self._historyNodeIDs
157 @readonly
158 def NameComponent(self) -> Nullable[str]:
159 """
160 Read-only property to access the name component of the coverage item's unique ID (:attr:`_nameComponent`).
162 :returns: The name component; ``None``, if the report states none.
163 """
164 return self._nameComponent
166 @readonly
167 def TypeComponent(self) -> Nullable[str]:
168 """
169 Read-only property to access the type component of the coverage item's unique ID (:attr:`_typeComponent`).
171 :returns: The type component, e.g. ``:5:``; ``None``, if the report states none.
172 """
173 return self._typeComponent
176@export
177class Bin(Base, ObjectAttributesMixin):
178 """
179 A bin of a coverage item: its contents, its goal and its attributes.
181 A statement has ``<bin>`` elements, a branch ``<branchBin>`` elements. The element owning the bin takes it as a
182 parameter and becomes its parent.
183 """
185 _parent: Nullable[Base] #: The element owning the bin.
186 _contents: BinContents #: How often the coverage item was covered, and by which tests.
187 _coverageCountGoal: Nullable[int] #: How often the coverage item must be covered.
189 def __init__(
190 self,
191 contents: BinContents,
192 coverageCountGoal: Nullable[int] = None,
193 alias: Nullable[str] = None,
194 excluded: bool = False,
195 excludedReason: Nullable[str] = None,
196 weight: int = 1,
197 userAttributes: Nullable[Iterable[UserAttribute]] = None
198 ) -> None:
199 """
200 Initialize the bin.
202 :param contents: How often the coverage item was covered, and by which tests.
203 :param coverageCountGoal: Optional, how often the coverage item must be covered. Default: ``None``.
204 :param alias: Optional, an alias of the bin's name. Default: ``None``.
205 :param excluded: Optional, whether the bin is excluded from the coverage. Default: ``False``.
206 :param excludedReason: Optional, why the bin is excluded. Default: ``None``.
207 :param weight: Optional, weight of the bin in computing the coverage. Default: ``1``.
208 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
209 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
210 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
211 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
212 :raises TypeError: If parameter ``alias`` isn't of type :class:`str`.
213 :raises ValueError: If parameter ``excluded`` is ``None``.
214 :raises TypeError: If parameter ``excluded`` isn't of type :class:`bool`.
215 :raises TypeError: If parameter ``excludedReason`` isn't of type :class:`str`.
216 :raises ValueError: If parameter ``weight`` is ``None``.
217 :raises TypeError: If parameter ``weight`` isn't of type :class:`int`.
218 :raises ValueError: If parameter ``weight`` is negative.
219 :raises ValueError: If parameter ``contents`` is ``None``.
220 :raises TypeError: If parameter ``contents`` isn't of type :class:`BinContents`.
221 :raises TypeError: If parameter ``coverageCountGoal`` isn't of type :class:`int`.
222 :raises ValueError: If parameter ``coverageCountGoal`` is negative.
223 """
224 super().__init__(userAttributes)
225 ObjectAttributesMixin.__init__(self, alias, excluded, excludedReason, weight)
227 if contents is None:
228 raise ValueError("Parameter 'contents' is None.")
229 elif not isinstance(contents, BinContents): 229 ↛ 230line 229 didn't jump to line 230 because the condition on line 229 was never true
230 ex = TypeError("Parameter 'contents' is not of type 'BinContents'.")
231 ex.add_note(f"Got type '{getFullyQualifiedName(contents)}'.")
232 raise ex
234 if coverageCountGoal is not None:
235 if not isinstance(coverageCountGoal, int) or isinstance(coverageCountGoal, bool): 235 ↛ 236line 235 didn't jump to line 236 because the condition on line 235 was never true
236 ex = TypeError("Parameter 'coverageCountGoal' is not of type 'int'.")
237 ex.add_note(f"Got type '{getFullyQualifiedName(coverageCountGoal)}'.")
238 raise ex
239 elif coverageCountGoal < 0:
240 ex = ValueError("Parameter 'coverageCountGoal' is negative.")
241 ex.add_note(f"Got value '{coverageCountGoal}'.")
242 raise ex
244 self._parent = None
245 self._contents = contents
246 self._coverageCountGoal = coverageCountGoal
248 @classmethod
249 def Parse(cls, element: _Element) -> Self:
250 """
251 Parse a bin, its contents and its user-defined attributes from its element.
253 The element is e.g. a ``<bin>`` or a ``<branchBin>``.
255 :param element: The bin's element.
256 :returns: The bin.
257 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
258 """
259 goal = element.attrib.get("coverageCountGoal")
260 return cls(
261 BinContents.Parse(element.find("{*}contents")),
262 None if goal is None else int(goal),
263 element.attrib.get("alias"),
264 cls._ParseBoolean(element.attrib.get("excluded", "false")),
265 element.attrib.get("excludedReason"),
266 int(element.attrib.get("weight", "1")),
267 cls._ParseUserAttributes(element)
268 )
270 @readonly
271 def Parent(self) -> Nullable[Base]:
272 """
273 Read-only property to access the element owning the bin (:attr:`_parent`).
275 :returns: The element, e.g. a :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Blocks.Statement`; ``None``, if the bin
276 belongs to none.
277 """
278 return self._parent
280 @readonly
281 def Contents(self) -> BinContents:
282 """
283 Read-only property to access the contents of the bin (:attr:`_contents`).
285 :returns: How often the coverage item was covered, and by which tests.
286 """
287 return self._contents
289 @readonly
290 def CoverageCountGoal(self) -> Nullable[int]:
291 """
292 Read-only property to access how often the coverage item must be covered (:attr:`_coverageCountGoal`).
294 :returns: The goal; ``None``, if the report states none.
295 """
296 return self._coverageCountGoal