Source code for pyEDAA.Reports.CodeCoverage.UCIS.HistoryNodes

# ==================================================================================================================== #
#              _____ ____    _        _      ____                       _                                              #
#  _ __  _   _| ____|  _ \  / \      / \    |  _ \ ___ _ __   ___  _ __| |_ ___                                        #
# | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) / _ \ '_ \ / _ \| '__| __/ __|                                       #
# | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  _ <  __/ |_) | (_) | |  | |_\__ \                                       #
# | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_| \_\___| .__/ \___/|_|   \__|___/                                       #
# |_|    |___/                                        |_|                                                              #
# ==================================================================================================================== #
# Authors:                                                                                                             #
#   Patrick Lehmann                                                                                                    #
#                                                                                                                      #
# License:                                                                                                             #
# ==================================================================================================================== #
# Copyright 2026-2026 Electronic Design Automation Abstraction (EDA²)                                                  #
#                                                                                                                      #
# Licensed under the Apache License, Version 2.0 (the "License");                                                      #
# you may not use this file except in compliance with the License.                                                     #
# You may obtain a copy of the License at                                                                              #
#                                                                                                                      #
#   http://www.apache.org/licenses/LICENSE-2.0                                                                         #
#                                                                                                                      #
# Unless required by applicable law or agreed to in writing, software                                                  #
# distributed under the License is distributed on an "AS IS" BASIS,                                                    #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.                                             #
# See the License for the specific language governing permissions and                                                  #
# limitations under the License.                                                                                       #
#                                                                                                                      #
# SPDX-License-Identifier: Apache-2.0                                                                                  #
# ==================================================================================================================== #
#
"""
The history nodes of the UCIS XML interchange format: the tests, which measured the coverage, and the merges of their
coverage, with the tool, the date and how the test ran.
"""
from __future__                                import annotations

from datetime                                  import datetime
from decimal                                   import Decimal
from pathlib                                   import Path
from typing                                    import TYPE_CHECKING, Iterable, Optional as Nullable, Self

from lxml.etree                                import _Element
from pyTooling.Common                          import getFullyQualifiedName
from pyTooling.Decorators                      import export, readonly

from pyEDAA.Reports.CodeCoverage.UCIS.Elements import Base, UserAttribute

if TYPE_CHECKING:
	from pyEDAA.Reports.CodeCoverage.UCIS        import Report


[docs] @export class HistoryNode(Base): """ A ``<historyNodes>`` of the report: a test, which measured coverage, or a merge of coverage, its tool, its date and - if the report says - how the test ran. A history node names the node it belongs to - e.g. the merge, which merged it - by the ID of that node. """ _parent: Nullable[Report] #: The report the history node belongs to. _historyNodeID: int #: ID of the history node. _logicalName: str #: Name of the history node, e.g. of the test. _testStatus: bool #: Whether the test passed. _date: datetime #: When the test ran. _toolCategory: str #: Kind of the tool, e.g. ``simulator``. _ucisVersion: str #: Version of the UCIS, in which the tool measured the coverage. _vendorID: str #: ID of the tool's vendor. _vendorTool: str #: Name of the tool. _vendorToolVersion: str #: Version of the tool. _parentID: Nullable[int] #: ID of the history node this one belongs to. _physicalName: Nullable[Path] #: Path of the coverage database the history node wrote. _kind: Nullable[str] #: Kind of the history node, e.g. a test or a merge. _simulationTime: Nullable[float] #: How long the test ran, in simulated time. _timeUnit: Nullable[str] #: Unit of the simulated time. _runDirectory: Nullable[Path] #: The directory the test ran in. _cpuTime: Nullable[float] #: How long the test ran, in CPU time. _seed: Nullable[str] #: The seed of the test's random numbers. _command: Nullable[str] #: The command, which ran the test. _arguments: Nullable[str] #: The command line arguments of the test. _compulsory: Nullable[str] #: Whether the test must run. _userName: Nullable[str] #: Name of the user, who ran the test. _cost: Nullable[Decimal] #: Cost of the test. _sameTests: Nullable[int] #: ID of a test this one is equivalent to. _comment: Nullable[str] #: A comment.
[docs] def __init__( self, historyNodeID: int, logicalName: str, testStatus: bool, date: datetime, toolCategory: str, ucisVersion: str, vendorID: str, vendorTool: str, vendorToolVersion: str, parentID: Nullable[int] = None, physicalName: Nullable[Path] = None, kind: Nullable[str] = None, simulationTime: Nullable[float] = None, timeUnit: Nullable[str] = None, runDirectory: Nullable[Path] = None, cpuTime: Nullable[float] = None, seed: Nullable[str] = None, command: Nullable[str] = None, arguments: Nullable[str] = None, compulsory: Nullable[str] = None, userName: Nullable[str] = None, cost: Nullable[Decimal] = None, sameTests: Nullable[int] = None, comment: Nullable[str] = None, userAttributes: Nullable[Iterable[UserAttribute]] = None, *, parent: Nullable[Report] = None ) -> None: """ Initialize the history node, and add it to the history nodes of its report. :param historyNodeID: ID of the history node. :param logicalName: Name of the history node, e.g. of the test. :param testStatus: Whether the test passed. :param date: When the test ran. :param toolCategory: Kind of the tool, e.g. ``simulator``. :param ucisVersion: Version of the UCIS, in which the tool measured the coverage, e.g. ``1.0``. :param vendorID: ID of the tool's vendor. :param vendorTool: Name of the tool. :param vendorToolVersion: Version of the tool. :param parentID: Optional, ID of the history node this one belongs to. Default: ``None``. :param physicalName: Optional, path of the coverage database the history node wrote. Default: ``None``. :param kind: Optional, kind of the history node, e.g. a test or a merge. Default: ``None``. :param simulationTime: Optional, how long the test ran, in simulated time. Default: ``None``. :param timeUnit: Optional, unit of the simulated time, e.g. ``ns``. Default: ``None``. :param runDirectory: Optional, the directory the test ran in. Default: ``None``. :param cpuTime: Optional, how long the test ran, in CPU time. Default: ``None``. :param seed: Optional, the seed of the test's random numbers. Default: ``None``. :param command: Optional, the command, which ran the test. Default: ``None``. :param arguments: Optional, the command line arguments of the test. Default: ``None``. :param compulsory: Optional, whether the test must run. Default: ``None``. :param userName: Optional, name of the user, who ran the test. Default: ``None``. :param cost: Optional, cost of the test. Default: ``None``. :param sameTests: Optional, ID of a test this one is equivalent to. Default: ``None``. :param comment: Optional, a comment. Default: ``None``. :param userAttributes: Optional, the user-defined attributes. Default: ``None``. :param parent: Optional, the report the history node belongs to; the history node is added to its history nodes. Default: ``None``. :raises ValueError: If parameter ``historyNodeID`` is ``None``. :raises TypeError: If parameter ``historyNodeID`` isn't of type :class:`int`. :raises ValueError: If parameter ``historyNodeID`` is negative. :raises ValueError: If parameter ``logicalName`` is ``None``. :raises TypeError: If parameter ``logicalName`` isn't of type :class:`str`. :raises ValueError: If parameter ``testStatus`` is ``None``. :raises TypeError: If parameter ``testStatus`` isn't of type :class:`bool`. :raises ValueError: If parameter ``date`` is ``None``. :raises TypeError: If parameter ``date`` isn't of type :class:`~datetime.datetime`. :raises ValueError: If parameter ``toolCategory`` is ``None``. :raises TypeError: If parameter ``toolCategory`` isn't of type :class:`str`. :raises ValueError: If parameter ``ucisVersion`` is ``None``. :raises TypeError: If parameter ``ucisVersion`` isn't of type :class:`str`. :raises ValueError: If parameter ``vendorID`` is ``None``. :raises TypeError: If parameter ``vendorID`` isn't of type :class:`str`. :raises ValueError: If parameter ``vendorTool`` is ``None``. :raises TypeError: If parameter ``vendorTool`` isn't of type :class:`str`. :raises ValueError: If parameter ``vendorToolVersion`` is ``None``. :raises TypeError: If parameter ``vendorToolVersion`` isn't of type :class:`str`. :raises TypeError: If parameter ``parentID`` isn't of type :class:`int`. :raises ValueError: If parameter ``parentID`` is negative. :raises TypeError: If parameter ``physicalName`` isn't of type :class:`~pathlib.Path`. :raises TypeError: If parameter ``kind`` isn't of type :class:`str`. :raises TypeError: If parameter ``simulationTime`` isn't of type :class:`float`. :raises TypeError: If parameter ``timeUnit`` isn't of type :class:`str`. :raises TypeError: If parameter ``runDirectory`` isn't of type :class:`~pathlib.Path`. :raises TypeError: If parameter ``cpuTime`` isn't of type :class:`float`. :raises TypeError: If parameter ``seed`` isn't of type :class:`str`. :raises TypeError: If parameter ``command`` isn't of type :class:`str`. :raises TypeError: If parameter ``arguments`` isn't of type :class:`str`. :raises TypeError: If parameter ``compulsory`` isn't of type :class:`str`. :raises TypeError: If parameter ``userName`` isn't of type :class:`str`. :raises TypeError: If parameter ``cost`` isn't of type :class:`~decimal.Decimal`. :raises TypeError: If parameter ``sameTests`` isn't of type :class:`int`. :raises ValueError: If parameter ``sameTests`` is negative. :raises TypeError: If parameter ``comment`` isn't of type :class:`str`. :raises TypeError: If parameter ``userAttributes`` isn't iterable. :raises TypeError: If parameter ``userAttributes`` contains an element not of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`. :raises TypeError: If parameter ``parent`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Report`. """ from pyEDAA.Reports.CodeCoverage.UCIS import Report super().__init__(userAttributes) if historyNodeID is None: raise ValueError("Parameter 'historyNodeID' is None.") elif not isinstance(historyNodeID, int) or isinstance(historyNodeID, bool): ex = TypeError("Parameter 'historyNodeID' is not of type 'int'.") ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeID)}'.") raise ex elif historyNodeID < 0: ex = ValueError("Parameter 'historyNodeID' is negative.") ex.add_note(f"Got value '{historyNodeID}'.") raise ex if testStatus is None: raise ValueError("Parameter 'testStatus' is None.") elif not isinstance(testStatus, bool): ex = TypeError("Parameter 'testStatus' is not of type 'bool'.") ex.add_note(f"Got type '{getFullyQualifiedName(testStatus)}'.") raise ex if date is None: raise ValueError("Parameter 'date' is None.") elif not isinstance(date, datetime): ex = TypeError("Parameter 'date' is not of type 'datetime'.") ex.add_note(f"Got type '{getFullyQualifiedName(date)}'.") raise ex for name, value in ( ("logicalName", logicalName), ("toolCategory", toolCategory), ("ucisVersion", ucisVersion), ("vendorID", vendorID), ("vendorTool", vendorTool), ("vendorToolVersion", vendorToolVersion) ): if value is None: raise ValueError(f"Parameter '{name}' is None.") elif not isinstance(value, str): ex = TypeError(f"Parameter '{name}' is not of type 'str'.") ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") raise ex if parentID is not None: if not isinstance(parentID, int) or isinstance(parentID, bool): ex = TypeError("Parameter 'parentID' is not of type 'int'.") ex.add_note(f"Got type '{getFullyQualifiedName(parentID)}'.") raise ex elif parentID < 0: ex = ValueError("Parameter 'parentID' is negative.") ex.add_note(f"Got value '{parentID}'.") raise ex if sameTests is not None: if not isinstance(sameTests, int) or isinstance(sameTests, bool): ex = TypeError("Parameter 'sameTests' is not of type 'int'.") ex.add_note(f"Got type '{getFullyQualifiedName(sameTests)}'.") raise ex elif sameTests < 0: ex = ValueError("Parameter 'sameTests' is negative.") ex.add_note(f"Got value '{sameTests}'.") raise ex if physicalName is not None and not isinstance(physicalName, Path): ex = TypeError("Parameter 'physicalName' is not of type 'Path'.") ex.add_note(f"Got type '{getFullyQualifiedName(physicalName)}'.") raise ex if runDirectory is not None and not isinstance(runDirectory, Path): ex = TypeError("Parameter 'runDirectory' is not of type 'Path'.") ex.add_note(f"Got type '{getFullyQualifiedName(runDirectory)}'.") raise ex if simulationTime is not None and not isinstance(simulationTime, float): ex = TypeError("Parameter 'simulationTime' is not of type 'float'.") ex.add_note(f"Got type '{getFullyQualifiedName(simulationTime)}'.") raise ex if cpuTime is not None and not isinstance(cpuTime, float): ex = TypeError("Parameter 'cpuTime' is not of type 'float'.") ex.add_note(f"Got type '{getFullyQualifiedName(cpuTime)}'.") raise ex for name, value in ( ("kind", kind), ("timeUnit", timeUnit), ("seed", seed), ("command", command), ("arguments", arguments), ("compulsory", compulsory), ("userName", userName), ("comment", comment) ): if value is not None and not isinstance(value, str): ex = TypeError(f"Parameter '{name}' is not of type 'str'.") ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") raise ex if cost is not None and not isinstance(cost, Decimal): ex = TypeError("Parameter 'cost' is not of type 'Decimal'.") ex.add_note(f"Got type '{getFullyQualifiedName(cost)}'.") raise ex if parent is not None and not isinstance(parent, Report): ex = TypeError("Parameter 'parent' is not of type 'Report'.") ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") raise ex self._parent = parent self._historyNodeID = historyNodeID self._logicalName = logicalName self._testStatus = testStatus self._date = date self._toolCategory = toolCategory self._ucisVersion = ucisVersion self._vendorID = vendorID self._vendorTool = vendorTool self._vendorToolVersion = vendorToolVersion self._parentID = parentID self._physicalName = physicalName self._kind = kind self._simulationTime = simulationTime self._timeUnit = timeUnit self._runDirectory = runDirectory self._cpuTime = cpuTime self._seed = seed self._command = command self._arguments = arguments self._compulsory = compulsory self._userName = userName self._cost = cost self._sameTests = sameTests self._comment = comment if parent is not None: parent._historyNodes[historyNodeID] = self
[docs] @classmethod def Parse(cls, element: _Element, *, parent: Nullable[Report] = None) -> Self: """ Parse a history node from its ``<historyNodes>`` element. :param element: The ``<historyNodes>`` element. :param parent: Optional, the report the history node belongs to. Default: ``None``. :returns: The history node. :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states. """ attributes = element.attrib parentID = attributes.get("parentId") physicalName = attributes.get("physicalName") simulationTime = attributes.get("simtime") runDirectory = attributes.get("runCwd") cpuTime = attributes.get("cpuTime") cost = attributes.get("cost") sameTests = attributes.get("sameTests") return cls( int(attributes["historyNodeId"]), attributes["logicalName"], cls._ParseBoolean(attributes["testStatus"]), datetime.fromisoformat(attributes["date"]), attributes["toolCategory"], attributes["ucisVersion"], attributes["vendorId"], attributes["vendorTool"], attributes["vendorToolVersion"], None if parentID is None else int(parentID), None if physicalName is None else Path(physicalName), attributes.get("kind"), None if simulationTime is None else float(simulationTime), attributes.get("timeunit"), None if runDirectory is None else Path(runDirectory), None if cpuTime is None else float(cpuTime), attributes.get("seed"), attributes.get("cmd"), attributes.get("args"), attributes.get("compulsory"), attributes.get("userName"), None if cost is None else Decimal(cost), None if sameTests is None else int(sameTests), attributes.get("comment"), cls._ParseUserAttributes(element), parent=parent )
@readonly def Parent(self) -> Nullable[Report]: """ Read-only property to access the report the history node belongs to (:attr:`_parent`). :returns: The report; ``None``, if the history node belongs to none. """ return self._parent @readonly def HistoryNodeID(self) -> int: """ Read-only property to access the ID of the history node (:attr:`_historyNodeID`). :returns: The ID, by which bins name the tests, which covered them. """ return self._historyNodeID @readonly def LogicalName(self) -> str: """ Read-only property to access the name of the history node (:attr:`_logicalName`). :returns: The name, e.g. of the test. """ return self._logicalName @readonly def TestStatus(self) -> bool: """ Read-only property to access whether the test passed (:attr:`_testStatus`). :returns: ``True``, if the test passed. """ return self._testStatus @readonly def Date(self) -> datetime: """ Read-only property to access when the test ran (:attr:`_date`). :returns: The date and time. """ return self._date @readonly def ToolCategory(self) -> str: """ Read-only property to access the kind of the tool (:attr:`_toolCategory`). :returns: The kind, as stated. """ return self._toolCategory @readonly def UCISVersion(self) -> str: """ Read-only property to access the UCIS version, in which the tool measured the coverage (:attr:`_ucisVersion`). :returns: The version, as stated. """ return self._ucisVersion @readonly def VendorID(self) -> str: """ Read-only property to access the ID of the tool's vendor (:attr:`_vendorID`). :returns: The ID, as stated. """ return self._vendorID @readonly def VendorTool(self) -> str: """ Read-only property to access the name of the tool (:attr:`_vendorTool`). :returns: The name, as stated. """ return self._vendorTool @readonly def VendorToolVersion(self) -> str: """ Read-only property to access the version of the tool (:attr:`_vendorToolVersion`). :returns: The version, as stated. """ return self._vendorToolVersion @readonly def ParentID(self) -> Nullable[int]: """ Read-only property to access the ID of the history node this one belongs to (:attr:`_parentID`). :returns: The ID, e.g. of the merge, which merged this one; ``None``, if the report states none. """ return self._parentID @readonly def PhysicalName(self) -> Nullable[Path]: """ Read-only property to access the path of the coverage database the history node wrote (:attr:`_physicalName`). :returns: The path, as stated; ``None``, if the report states none. """ return self._physicalName @readonly def Kind(self) -> Nullable[str]: """ Read-only property to access the kind of the history node (:attr:`_kind`). :returns: The kind, as stated; ``None``, if the report states none. """ return self._kind @readonly def SimulationTime(self) -> Nullable[float]: """ Read-only property to access how long the test ran in simulated time (:attr:`_simulationTime`). :returns: The time, in :attr:`TimeUnit`; ``None``, if the report states none. """ return self._simulationTime @readonly def TimeUnit(self) -> Nullable[str]: """ Read-only property to access the unit of the simulated time (:attr:`_timeUnit`). :returns: The unit, e.g. ``ns``; ``None``, if the report states none. """ return self._timeUnit @readonly def RunDirectory(self) -> Nullable[Path]: """ Read-only property to access the directory the test ran in (:attr:`_runDirectory`). It is a path on the machine the coverage was measured on, so it may not exist where the report is read. :returns: The directory; ``None``, if the report states none. """ return self._runDirectory @readonly def CPUTime(self) -> Nullable[float]: """ Read-only property to access how long the test ran in CPU time (:attr:`_cpuTime`). :returns: The time; ``None``, if the report states none. """ return self._cpuTime @readonly def Seed(self) -> Nullable[str]: """ Read-only property to access the seed of the test's random numbers (:attr:`_seed`). :returns: The seed; ``None``, if the report states none. """ return self._seed @readonly def Command(self) -> Nullable[str]: """ Read-only property to access the command, which ran the test (:attr:`_command`). :returns: The command; ``None``, if the report states none. """ return self._command @readonly def Arguments(self) -> Nullable[str]: """ Read-only property to access the command line arguments of the test (:attr:`_arguments`). :returns: The arguments; ``None``, if the report states none. """ return self._arguments @readonly def Compulsory(self) -> Nullable[str]: """ Read-only property to access whether the test must run (:attr:`_compulsory`). :returns: The value, as stated; ``None``, if the report states none. """ return self._compulsory @readonly def UserName(self) -> Nullable[str]: """ Read-only property to access the name of the user, who ran the test (:attr:`_userName`). :returns: The name; ``None``, if the report states none. """ return self._userName @readonly def Cost(self) -> Nullable[Decimal]: """ Read-only property to access the cost of the test (:attr:`_cost`). :returns: The cost; ``None``, if the report states none. """ return self._cost @readonly def SameTests(self) -> Nullable[int]: """ Read-only property to access the ID of a test this one is equivalent to (:attr:`_sameTests`). :returns: The ID; ``None``, if the report states none. """ return self._sameTests @readonly def Comment(self) -> Nullable[str]: """ Read-only property to access the comment (:attr:`_comment`). :returns: The comment; ``None``, if the report states none. """ return self._comment