Coverage for pyEDAA/Reports/CodeCoverage/UCIS/HistoryNodes.py: 76%
230 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 history nodes of the UCIS XML interchange format: the tests, which measured the coverage, and the merges of their
33coverage, with the tool, the date and how the test ran.
34"""
35from __future__ import annotations
37from datetime import datetime
38from decimal import Decimal
39from pathlib import Path
40from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Self
42from lxml.etree import _Element
43from pyTooling.Common import getFullyQualifiedName
44from pyTooling.Decorators import export, readonly
46from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, 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 import Report
52@export
53class HistoryNode(Base):
54 """
55 A ``<historyNodes>`` of the report: a test, which measured coverage, or a merge of coverage, its tool, its date and -
56 if the report says - how the test ran.
58 A history node names the node it belongs to - e.g. the merge, which merged it - by the ID of that node.
59 """
61 _parent: Nullable[Report] #: The report the history node belongs to.
62 _historyNodeID: int #: ID of the history node.
63 _logicalName: str #: Name of the history node, e.g. of the test.
64 _testStatus: bool #: Whether the test passed.
65 _date: datetime #: When the test ran.
66 _toolCategory: str #: Kind of the tool, e.g. ``simulator``.
67 _ucisVersion: str #: Version of the UCIS, in which the tool measured the coverage.
68 _vendorID: str #: ID of the tool's vendor.
69 _vendorTool: str #: Name of the tool.
70 _vendorToolVersion: str #: Version of the tool.
71 _parentID: Nullable[int] #: ID of the history node this one belongs to.
72 _physicalName: Nullable[Path] #: Path of the coverage database the history node wrote.
73 _kind: Nullable[str] #: Kind of the history node, e.g. a test or a merge.
74 _simulationTime: Nullable[float] #: How long the test ran, in simulated time.
75 _timeUnit: Nullable[str] #: Unit of the simulated time.
76 _runDirectory: Nullable[Path] #: The directory the test ran in.
77 _cpuTime: Nullable[float] #: How long the test ran, in CPU time.
78 _seed: Nullable[str] #: The seed of the test's random numbers.
79 _command: Nullable[str] #: The command, which ran the test.
80 _arguments: Nullable[str] #: The command line arguments of the test.
81 _compulsory: Nullable[str] #: Whether the test must run.
82 _userName: Nullable[str] #: Name of the user, who ran the test.
83 _cost: Nullable[Decimal] #: Cost of the test.
84 _sameTests: Nullable[int] #: ID of a test this one is equivalent to.
85 _comment: Nullable[str] #: A comment.
87 def __init__(
88 self,
89 historyNodeID: int,
90 logicalName: str,
91 testStatus: bool,
92 date: datetime,
93 toolCategory: str,
94 ucisVersion: str,
95 vendorID: str,
96 vendorTool: str,
97 vendorToolVersion: str,
98 parentID: Nullable[int] = None,
99 physicalName: Nullable[Path] = None,
100 kind: Nullable[str] = None,
101 simulationTime: Nullable[float] = None,
102 timeUnit: Nullable[str] = None,
103 runDirectory: Nullable[Path] = None,
104 cpuTime: Nullable[float] = None,
105 seed: Nullable[str] = None,
106 command: Nullable[str] = None,
107 arguments: Nullable[str] = None,
108 compulsory: Nullable[str] = None,
109 userName: Nullable[str] = None,
110 cost: Nullable[Decimal] = None,
111 sameTests: Nullable[int] = None,
112 comment: Nullable[str] = None,
113 userAttributes: Nullable[Iterable[UserAttribute]] = None,
114 *,
115 parent: Nullable[Report] = None
116 ) -> None:
117 """
118 Initialize the history node, and add it to the history nodes of its report.
120 :param historyNodeID: ID of the history node.
121 :param logicalName: Name of the history node, e.g. of the test.
122 :param testStatus: Whether the test passed.
123 :param date: When the test ran.
124 :param toolCategory: Kind of the tool, e.g. ``simulator``.
125 :param ucisVersion: Version of the UCIS, in which the tool measured the coverage, e.g. ``1.0``.
126 :param vendorID: ID of the tool's vendor.
127 :param vendorTool: Name of the tool.
128 :param vendorToolVersion: Version of the tool.
129 :param parentID: Optional, ID of the history node this one belongs to. Default: ``None``.
130 :param physicalName: Optional, path of the coverage database the history node wrote. Default: ``None``.
131 :param kind: Optional, kind of the history node, e.g. a test or a merge. Default: ``None``.
132 :param simulationTime: Optional, how long the test ran, in simulated time. Default: ``None``.
133 :param timeUnit: Optional, unit of the simulated time, e.g. ``ns``. Default: ``None``.
134 :param runDirectory: Optional, the directory the test ran in. Default: ``None``.
135 :param cpuTime: Optional, how long the test ran, in CPU time. Default: ``None``.
136 :param seed: Optional, the seed of the test's random numbers. Default: ``None``.
137 :param command: Optional, the command, which ran the test. Default: ``None``.
138 :param arguments: Optional, the command line arguments of the test. Default: ``None``.
139 :param compulsory: Optional, whether the test must run. Default: ``None``.
140 :param userName: Optional, name of the user, who ran the test. Default: ``None``.
141 :param cost: Optional, cost of the test. Default: ``None``.
142 :param sameTests: Optional, ID of a test this one is equivalent to. Default: ``None``.
143 :param comment: Optional, a comment. Default: ``None``.
144 :param userAttributes: Optional, the user-defined attributes. Default: ``None``.
145 :param parent: Optional, the report the history node belongs to; the history node is added to its
146 history nodes. Default: ``None``.
147 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
148 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
149 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
150 :raises ValueError: If parameter ``historyNodeID`` is ``None``.
151 :raises TypeError: If parameter ``historyNodeID`` isn't of type :class:`int`.
152 :raises ValueError: If parameter ``historyNodeID`` is negative.
153 :raises ValueError: If parameter ``testStatus`` is ``None``.
154 :raises TypeError: If parameter ``testStatus`` isn't of type :class:`bool`.
155 :raises ValueError: If parameter ``date`` is ``None``.
156 :raises TypeError: If parameter ``date`` isn't of type :class:`~datetime.datetime`.
157 :raises ValueError: If parameter ``logicalName``, ``toolCategory``, ``ucisVersion``, ``vendorID``,
158 ``vendorTool`` or ``vendorToolVersion`` is ``None``.
159 :raises TypeError: If parameter ``logicalName``, ``toolCategory``, ``ucisVersion``, ``vendorID``,
160 ``vendorTool`` or ``vendorToolVersion`` isn't of type :class:`str`.
161 :raises TypeError: If parameter ``parentID`` isn't of type :class:`int`.
162 :raises ValueError: If parameter ``parentID`` is negative.
163 :raises TypeError: If parameter ``sameTests`` isn't of type :class:`int`.
164 :raises ValueError: If parameter ``sameTests`` is negative.
165 :raises TypeError: If parameter ``physicalName`` isn't of type :class:`~pathlib.Path`.
166 :raises TypeError: If parameter ``runDirectory`` isn't of type :class:`~pathlib.Path`.
167 :raises TypeError: If parameter ``simulationTime`` isn't of type :class:`float`.
168 :raises TypeError: If parameter ``cpuTime`` isn't of type :class:`float`.
169 :raises TypeError: If parameter ``kind``, ``timeUnit``, ``seed``, ``command``, ``arguments``,
170 ``compulsory``, ``userName`` or ``comment`` isn't of type :class:`str`.
171 :raises TypeError: If parameter ``cost`` isn't of type :class:`~decimal.Decimal`.
172 :raises TypeError: If parameter ``parent`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Report`.
173 """
174 from pyEDAA.Reports.CodeCoverage.UCIS import Report
176 super().__init__(userAttributes)
178 if historyNodeID is None: 178 ↛ 179line 178 didn't jump to line 179 because the condition on line 178 was never true
179 raise ValueError("Parameter 'historyNodeID' is None.")
180 elif not isinstance(historyNodeID, int) or isinstance(historyNodeID, bool): 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true
181 ex = TypeError("Parameter 'historyNodeID' is not of type 'int'.")
182 ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeID)}'.")
183 raise ex
184 elif historyNodeID < 0: 184 ↛ 185line 184 didn't jump to line 185 because the condition on line 184 was never true
185 ex = ValueError("Parameter 'historyNodeID' is negative.")
186 ex.add_note(f"Got value '{historyNodeID}'.")
187 raise ex
189 if testStatus is None: 189 ↛ 190line 189 didn't jump to line 190 because the condition on line 189 was never true
190 raise ValueError("Parameter 'testStatus' is None.")
191 elif not isinstance(testStatus, bool): 191 ↛ 192line 191 didn't jump to line 192 because the condition on line 191 was never true
192 ex = TypeError("Parameter 'testStatus' is not of type 'bool'.")
193 ex.add_note(f"Got type '{getFullyQualifiedName(testStatus)}'.")
194 raise ex
196 if date is None: 196 ↛ 197line 196 didn't jump to line 197 because the condition on line 196 was never true
197 raise ValueError("Parameter 'date' is None.")
198 elif not isinstance(date, datetime):
199 ex = TypeError("Parameter 'date' is not of type 'datetime'.")
200 ex.add_note(f"Got type '{getFullyQualifiedName(date)}'.")
201 raise ex
203 for name, value in (
204 ("logicalName", logicalName),
205 ("toolCategory", toolCategory),
206 ("ucisVersion", ucisVersion),
207 ("vendorID", vendorID),
208 ("vendorTool", vendorTool),
209 ("vendorToolVersion", vendorToolVersion)
210 ):
211 if value is None:
212 raise ValueError(f"Parameter '{name}' is None.")
213 elif not isinstance(value, str): 213 ↛ 214line 213 didn't jump to line 214 because the condition on line 213 was never true
214 ex = TypeError(f"Parameter '{name}' is not of type 'str'.")
215 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
216 raise ex
218 if parentID is not None:
219 if not isinstance(parentID, int) or isinstance(parentID, bool): 219 ↛ 220line 219 didn't jump to line 220 because the condition on line 219 was never true
220 ex = TypeError("Parameter 'parentID' is not of type 'int'.")
221 ex.add_note(f"Got type '{getFullyQualifiedName(parentID)}'.")
222 raise ex
223 elif parentID < 0: 223 ↛ 228line 223 didn't jump to line 228 because the condition on line 223 was always true
224 ex = ValueError("Parameter 'parentID' is negative.")
225 ex.add_note(f"Got value '{parentID}'.")
226 raise ex
228 if sameTests is not None: 228 ↛ 229line 228 didn't jump to line 229 because the condition on line 228 was never true
229 if not isinstance(sameTests, int) or isinstance(sameTests, bool):
230 ex = TypeError("Parameter 'sameTests' is not of type 'int'.")
231 ex.add_note(f"Got type '{getFullyQualifiedName(sameTests)}'.")
232 raise ex
233 elif sameTests < 0:
234 ex = ValueError("Parameter 'sameTests' is negative.")
235 ex.add_note(f"Got value '{sameTests}'.")
236 raise ex
238 if physicalName is not None and not isinstance(physicalName, Path): 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true
239 ex = TypeError("Parameter 'physicalName' is not of type 'Path'.")
240 ex.add_note(f"Got type '{getFullyQualifiedName(physicalName)}'.")
241 raise ex
243 if runDirectory is not None and not isinstance(runDirectory, Path): 243 ↛ 244line 243 didn't jump to line 244 because the condition on line 243 was never true
244 ex = TypeError("Parameter 'runDirectory' is not of type 'Path'.")
245 ex.add_note(f"Got type '{getFullyQualifiedName(runDirectory)}'.")
246 raise ex
248 if simulationTime is not None and not isinstance(simulationTime, float):
249 ex = TypeError("Parameter 'simulationTime' is not of type 'float'.")
250 ex.add_note(f"Got type '{getFullyQualifiedName(simulationTime)}'.")
251 raise ex
253 if cpuTime is not None and not isinstance(cpuTime, float): 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true
254 ex = TypeError("Parameter 'cpuTime' is not of type 'float'.")
255 ex.add_note(f"Got type '{getFullyQualifiedName(cpuTime)}'.")
256 raise ex
258 for name, value in (
259 ("kind", kind),
260 ("timeUnit", timeUnit),
261 ("seed", seed),
262 ("command", command),
263 ("arguments", arguments),
264 ("compulsory", compulsory),
265 ("userName", userName),
266 ("comment", comment)
267 ):
268 if value is not None and not isinstance(value, str): 268 ↛ 269line 268 didn't jump to line 269 because the condition on line 268 was never true
269 ex = TypeError(f"Parameter '{name}' is not of type 'str'.")
270 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
271 raise ex
273 if cost is not None and not isinstance(cost, Decimal): 273 ↛ 274line 273 didn't jump to line 274 because the condition on line 273 was never true
274 ex = TypeError("Parameter 'cost' is not of type 'Decimal'.")
275 ex.add_note(f"Got type '{getFullyQualifiedName(cost)}'.")
276 raise ex
278 if parent is not None and not isinstance(parent, Report): 278 ↛ 279line 278 didn't jump to line 279 because the condition on line 278 was never true
279 ex = TypeError("Parameter 'parent' is not of type 'Report'.")
280 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
281 raise ex
283 self._parent = parent
284 self._historyNodeID = historyNodeID
285 self._logicalName = logicalName
286 self._testStatus = testStatus
287 self._date = date
288 self._toolCategory = toolCategory
289 self._ucisVersion = ucisVersion
290 self._vendorID = vendorID
291 self._vendorTool = vendorTool
292 self._vendorToolVersion = vendorToolVersion
293 self._parentID = parentID
294 self._physicalName = physicalName
295 self._kind = kind
296 self._simulationTime = simulationTime
297 self._timeUnit = timeUnit
298 self._runDirectory = runDirectory
299 self._cpuTime = cpuTime
300 self._seed = seed
301 self._command = command
302 self._arguments = arguments
303 self._compulsory = compulsory
304 self._userName = userName
305 self._cost = cost
306 self._sameTests = sameTests
307 self._comment = comment
309 if parent is not None:
310 parent._historyNodes[historyNodeID] = self
312 @classmethod
313 def Parse(cls, element: _Element, *, parent: Nullable[Report] = None) -> Self:
314 """
315 Parse a history node from its ``<historyNodes>`` element.
317 :param element: The ``<historyNodes>`` element.
318 :param parent: Optional, the report the history node belongs to. Default: ``None``.
319 :returns: The history node.
320 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
321 """
322 attributes = element.attrib
323 parentID = attributes.get("parentId")
324 physicalName = attributes.get("physicalName")
325 simulationTime = attributes.get("simtime")
326 runDirectory = attributes.get("runCwd")
327 cpuTime = attributes.get("cpuTime")
328 cost = attributes.get("cost")
329 sameTests = attributes.get("sameTests")
331 return cls(
332 int(attributes["historyNodeId"]),
333 attributes["logicalName"],
334 cls._ParseBoolean(attributes["testStatus"]),
335 datetime.fromisoformat(attributes["date"]),
336 attributes["toolCategory"],
337 attributes["ucisVersion"],
338 attributes["vendorId"],
339 attributes["vendorTool"],
340 attributes["vendorToolVersion"],
341 None if parentID is None else int(parentID),
342 None if physicalName is None else Path(physicalName),
343 attributes.get("kind"),
344 None if simulationTime is None else float(simulationTime),
345 attributes.get("timeunit"),
346 None if runDirectory is None else Path(runDirectory),
347 None if cpuTime is None else float(cpuTime),
348 attributes.get("seed"),
349 attributes.get("cmd"),
350 attributes.get("args"),
351 attributes.get("compulsory"),
352 attributes.get("userName"),
353 None if cost is None else Decimal(cost),
354 None if sameTests is None else int(sameTests),
355 attributes.get("comment"),
356 cls._ParseUserAttributes(element),
357 parent=parent
358 )
360 @readonly
361 def Parent(self) -> Nullable[Report]:
362 """
363 Read-only property to access the report the history node belongs to (:attr:`_parent`).
365 :returns: The report; ``None``, if the history node belongs to none.
366 """
367 return self._parent
369 @readonly
370 def HistoryNodeID(self) -> int:
371 """
372 Read-only property to access the ID of the history node (:attr:`_historyNodeID`).
374 :returns: The ID, by which bins name the tests, which covered them.
375 """
376 return self._historyNodeID
378 @readonly
379 def LogicalName(self) -> str:
380 """
381 Read-only property to access the name of the history node (:attr:`_logicalName`).
383 :returns: The name, e.g. of the test.
384 """
385 return self._logicalName
387 @readonly
388 def TestStatus(self) -> bool:
389 """
390 Read-only property to access whether the test passed (:attr:`_testStatus`).
392 :returns: ``True``, if the test passed.
393 """
394 return self._testStatus
396 @readonly
397 def Date(self) -> datetime:
398 """
399 Read-only property to access when the test ran (:attr:`_date`).
401 :returns: The date and time.
402 """
403 return self._date
405 @readonly
406 def ToolCategory(self) -> str:
407 """
408 Read-only property to access the kind of the tool (:attr:`_toolCategory`).
410 :returns: The kind, as stated.
411 """
412 return self._toolCategory
414 @readonly
415 def UCISVersion(self) -> str:
416 """
417 Read-only property to access the UCIS version, in which the tool measured the coverage (:attr:`_ucisVersion`).
419 :returns: The version, as stated.
420 """
421 return self._ucisVersion
423 @readonly
424 def VendorID(self) -> str:
425 """
426 Read-only property to access the ID of the tool's vendor (:attr:`_vendorID`).
428 :returns: The ID, as stated.
429 """
430 return self._vendorID
432 @readonly
433 def VendorTool(self) -> str:
434 """
435 Read-only property to access the name of the tool (:attr:`_vendorTool`).
437 :returns: The name, as stated.
438 """
439 return self._vendorTool
441 @readonly
442 def VendorToolVersion(self) -> str:
443 """
444 Read-only property to access the version of the tool (:attr:`_vendorToolVersion`).
446 :returns: The version, as stated.
447 """
448 return self._vendorToolVersion
450 @readonly
451 def ParentID(self) -> Nullable[int]:
452 """
453 Read-only property to access the ID of the history node this one belongs to (:attr:`_parentID`).
455 :returns: The ID, e.g. of the merge, which merged this one; ``None``, if the report states none.
456 """
457 return self._parentID
459 @readonly
460 def PhysicalName(self) -> Nullable[Path]:
461 """
462 Read-only property to access the path of the coverage database the history node wrote (:attr:`_physicalName`).
464 :returns: The path, as stated; ``None``, if the report states none.
465 """
466 return self._physicalName
468 @readonly
469 def Kind(self) -> Nullable[str]:
470 """
471 Read-only property to access the kind of the history node (:attr:`_kind`).
473 :returns: The kind, as stated; ``None``, if the report states none.
474 """
475 return self._kind
477 @readonly
478 def SimulationTime(self) -> Nullable[float]:
479 """
480 Read-only property to access how long the test ran in simulated time (:attr:`_simulationTime`).
482 :returns: The time, in :attr:`TimeUnit`; ``None``, if the report states none.
483 """
484 return self._simulationTime
486 @readonly
487 def TimeUnit(self) -> Nullable[str]:
488 """
489 Read-only property to access the unit of the simulated time (:attr:`_timeUnit`).
491 :returns: The unit, e.g. ``ns``; ``None``, if the report states none.
492 """
493 return self._timeUnit
495 @readonly
496 def RunDirectory(self) -> Nullable[Path]:
497 """
498 Read-only property to access the directory the test ran in (:attr:`_runDirectory`).
500 It is a path on the machine the coverage was measured on, so it may not exist where the report is read.
502 :returns: The directory; ``None``, if the report states none.
503 """
504 return self._runDirectory
506 @readonly
507 def CPUTime(self) -> Nullable[float]:
508 """
509 Read-only property to access how long the test ran in CPU time (:attr:`_cpuTime`).
511 :returns: The time; ``None``, if the report states none.
512 """
513 return self._cpuTime
515 @readonly
516 def Seed(self) -> Nullable[str]:
517 """
518 Read-only property to access the seed of the test's random numbers (:attr:`_seed`).
520 :returns: The seed; ``None``, if the report states none.
521 """
522 return self._seed
524 @readonly
525 def Command(self) -> Nullable[str]:
526 """
527 Read-only property to access the command, which ran the test (:attr:`_command`).
529 :returns: The command; ``None``, if the report states none.
530 """
531 return self._command
533 @readonly
534 def Arguments(self) -> Nullable[str]:
535 """
536 Read-only property to access the command line arguments of the test (:attr:`_arguments`).
538 :returns: The arguments; ``None``, if the report states none.
539 """
540 return self._arguments
542 @readonly
543 def Compulsory(self) -> Nullable[str]:
544 """
545 Read-only property to access whether the test must run (:attr:`_compulsory`).
547 :returns: The value, as stated; ``None``, if the report states none.
548 """
549 return self._compulsory
551 @readonly
552 def UserName(self) -> Nullable[str]:
553 """
554 Read-only property to access the name of the user, who ran the test (:attr:`_userName`).
556 :returns: The name; ``None``, if the report states none.
557 """
558 return self._userName
560 @readonly
561 def Cost(self) -> Nullable[Decimal]:
562 """
563 Read-only property to access the cost of the test (:attr:`_cost`).
565 :returns: The cost; ``None``, if the report states none.
566 """
567 return self._cost
569 @readonly
570 def SameTests(self) -> Nullable[int]:
571 """
572 Read-only property to access the ID of a test this one is equivalent to (:attr:`_sameTests`).
574 :returns: The ID; ``None``, if the report states none.
575 """
576 return self._sameTests
578 @readonly
579 def Comment(self) -> Nullable[str]:
580 """
581 Read-only property to access the comment (:attr:`_comment`).
583 :returns: The comment; ``None``, if the report states none.
584 """
585 return self._comment