Coverage for pyEDAA/Reports/Unittesting/OpenTestReporting/Records.py: 91%
127 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 records of Open Test Reporting's XML formats: the infrastructure of an execution and the result of a test or
33container.
35The infrastructure belongs to an :class:`~pyEDAA.Reports.Unittesting.OpenTestReporting.Execution`, a result to a
36:class:`~pyEDAA.Reports.Unittesting.OpenTestReporting.Node` of the tree in
37:mod:`pyEDAA.Reports.Unittesting.OpenTestReporting`.
38"""
39from __future__ import annotations
41from enum import Enum
42from typing import TYPE_CHECKING, Dict, Optional as Nullable
44from pyTooling.Common import getFullyQualifiedName
45from pyTooling.Decorators import export, readonly
46from pyTooling.MetaClasses import ExtendedType
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.Unittesting.OpenTestReporting import Execution, Node
52@export
53class ResultStatus(Enum):
54 """An enumeration of the statuses of a test's or container's result (``<result status>``)."""
56 Successful = "SUCCESSFUL" #: Executed and passed.
57 Skipped = "SKIPPED" #: Not executed, e.g. a disabled test.
58 Aborted = "ABORTED" #: Started, but aborted, e.g. by a failed assumption.
59 Failed = "FAILED" #: Executed and failed, e.g. by a failed assertion or an exception.
60 Errored = "ERRORED" #: Executed, but an error outside the test occurred.
63@export
64class Infrastructure(metaclass=ExtendedType, slots=True):
65 """
66 The infrastructure the tests were run on (``<infrastructure>``).
68 The core namespace states the host, the user, the operating system and the CPU cores. Elements of extensions, like
69 the Java version, are collected in :data:`Extensions`.
70 """
72 _parent: Nullable[Execution] #: The execution the infrastructure belongs to.
73 _hostName: Nullable[str] #: Name of the host.
74 _userName: Nullable[str] #: Name of the user.
75 _operatingSystem: Nullable[str] #: Name of the operating system.
76 _cpuCoreCount: Nullable[int] #: Count of CPU cores.
77 _extensions: Dict[str, str] #: Texts and attribute values of extension elements.
79 def __init__(
80 self,
81 hostName: Nullable[str] = None,
82 userName: Nullable[str] = None,
83 operatingSystem: Nullable[str] = None,
84 cpuCoreCount: Nullable[int] = None,
85 *,
86 parent: Nullable[Execution] = None
87 ) -> None:
88 """
89 Initializes an infrastructure description, and sets it as the infrastructure of its execution.
91 :param hostName: Optional, name of the host.
92 :param userName: Optional, name of the user.
93 :param operatingSystem: Optional, name of the operating system.
94 :param cpuCoreCount: Optional, count of CPU cores.
95 :param parent: Optional, the execution the infrastructure belongs to; it becomes the execution's
96 :attr:`~pyEDAA.Reports.Unittesting.OpenTestReporting.Execution.Infrastructure`.
97 Default: ``None``.
98 :raises TypeError: If parameter 'hostName' is not of type :class:`str`.
99 :raises TypeError: If parameter 'userName' is not of type :class:`str`.
100 :raises TypeError: If parameter 'operatingSystem' is not of type :class:`str`.
101 :raises TypeError: If parameter 'cpuCoreCount' is not of type :class:`int`.
102 :raises TypeError: If parameter 'parent' is not of type
103 :class:`~pyEDAA.Reports.Unittesting.OpenTestReporting.Execution`.
104 """
105 from pyEDAA.Reports.Unittesting.OpenTestReporting import Execution
107 for parameterName, value in (
108 ("hostName", hostName),
109 ("userName", userName),
110 ("operatingSystem", operatingSystem)
111 ):
112 if value is not None and not isinstance(value, str): 112 ↛ 113line 112 didn't jump to line 113 because the condition on line 112 was never true
113 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
114 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
115 raise ex
117 if cpuCoreCount is not None and not isinstance(cpuCoreCount, int):
118 ex = TypeError("Parameter 'cpuCoreCount' is not of type 'int'.")
119 ex.add_note(f"Got type '{getFullyQualifiedName(cpuCoreCount)}'.")
120 raise ex
122 if parent is not None and not isinstance(parent, Execution):
123 ex = TypeError("Parameter 'parent' is not of type 'Execution'.")
124 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
125 raise ex
127 self._parent = parent
128 self._hostName = hostName
129 self._userName = userName
130 self._operatingSystem = operatingSystem
131 self._cpuCoreCount = cpuCoreCount
132 self._extensions = {}
134 if parent is not None:
135 parent._infrastructure = self
137 @readonly
138 def Parent(self) -> Nullable[Execution]:
139 """
140 Read-only property to access the execution the infrastructure belongs to (:attr:`_parent`).
142 :returns: The execution; ``None`` if the infrastructure belongs to no execution.
143 """
144 return self._parent
146 @readonly
147 def HostName(self) -> Nullable[str]:
148 """
149 Read-only property to access the name of the host (:attr:`_hostName`).
151 :returns: The host name, or ``None`` if it wasn't reported.
152 """
153 return self._hostName
155 @readonly
156 def UserName(self) -> Nullable[str]:
157 """
158 Read-only property to access the name of the user (:attr:`_userName`).
160 :returns: The user name, or ``None`` if it wasn't reported.
161 """
162 return self._userName
164 @readonly
165 def OperatingSystem(self) -> Nullable[str]:
166 """
167 Read-only property to access the name of the operating system (:attr:`_operatingSystem`).
169 :returns: The operating system, or ``None`` if it wasn't reported.
170 """
171 return self._operatingSystem
173 @readonly
174 def CPUCoreCount(self) -> Nullable[int]:
175 """
176 Read-only property to access the count of CPU cores (:attr:`_cpuCoreCount`).
178 :returns: The count of CPU cores, or ``None`` if it wasn't reported.
179 """
180 return self._cpuCoreCount
182 @readonly
183 def Extensions(self) -> Dict[str, str]:
184 """
185 Read-only property to access the texts and attribute values of extension elements (:attr:`_extensions`).
187 An element's text is stored by the element's local name, e.g. ``javaVersion``, an attribute's value by the local
188 name and the attribute's name, e.g. ``heapSize.max``.
190 :returns: The dictionary of extension values.
191 """
192 return self._extensions
195@export
196class Result(metaclass=ExtendedType, slots=True):
197 """
198 The result of a test or container (``<result>``).
200 Besides a status, a result can state the reason for it, e.g. why a test was skipped, and the exception ending a test
201 (Java extension, ``<java:throwable>``).
202 """
204 _parent: Nullable[Node] #: The test or container the result belongs to.
205 _status: Nullable[ResultStatus] #: Status of the result.
206 _reason: Nullable[str] #: Reason for the status.
207 _exceptionType: Nullable[str] #: Fully qualified type of the exception.
208 _isAssertionError: Nullable[bool] #: True, if the exception is an assertion error.
209 _stackTrace: Nullable[str] #: Stack trace of the exception, with its type and message.
211 def __init__(
212 self,
213 status: Nullable[ResultStatus] = None,
214 reason: Nullable[str] = None,
215 exceptionType: Nullable[str] = None,
216 isAssertionError: Nullable[bool] = None,
217 stackTrace: Nullable[str] = None,
218 *,
219 parent: Nullable[Node] = None
220 ) -> None:
221 """
222 Initializes a result, and sets it as the result of its test or container.
224 :param status: Optional, status of the result.
225 :param reason: Optional, reason for the status.
226 :param exceptionType: Optional, fully qualified type of the exception.
227 :param isAssertionError: Optional, true, if the exception is an assertion error.
228 :param stackTrace: Optional, stack trace of the exception.
229 :param parent: Optional, the test or container the result belongs to; it becomes the node's
230 :attr:`~pyEDAA.Reports.Unittesting.OpenTestReporting.Node.Result`. Default: ``None``.
231 :raises TypeError: If parameter 'status' is not of type :class:`ResultStatus`.
232 :raises TypeError: If parameter 'reason' is not of type :class:`str`.
233 :raises TypeError: If parameter 'exceptionType' is not of type :class:`str`.
234 :raises TypeError: If parameter 'isAssertionError' is not of type :class:`bool`.
235 :raises TypeError: If parameter 'stackTrace' is not of type :class:`str`.
236 :raises TypeError: If parameter 'parent' is not of type
237 :class:`~pyEDAA.Reports.Unittesting.OpenTestReporting.Node`.
238 """
239 from pyEDAA.Reports.Unittesting.OpenTestReporting import Node
241 if status is not None and not isinstance(status, ResultStatus):
242 ex = TypeError("Parameter 'status' is not of type 'ResultStatus'.")
243 ex.add_note(f"Got type '{getFullyQualifiedName(status)}'.")
244 raise ex
246 for parameterName, value in (
247 ("reason", reason),
248 ("exceptionType", exceptionType),
249 ("stackTrace", stackTrace)
250 ):
251 if value is not None and not isinstance(value, str): 251 ↛ 252line 251 didn't jump to line 252 because the condition on line 251 was never true
252 ex = TypeError(f"Parameter '{parameterName}' is not of type 'str'.")
253 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
254 raise ex
256 if isAssertionError is not None and not isinstance(isAssertionError, bool): 256 ↛ 257line 256 didn't jump to line 257 because the condition on line 256 was never true
257 ex = TypeError("Parameter 'isAssertionError' is not of type 'bool'.")
258 ex.add_note(f"Got type '{getFullyQualifiedName(isAssertionError)}'.")
259 raise ex
261 if parent is not None and not isinstance(parent, Node):
262 ex = TypeError("Parameter 'parent' is not of type 'Node'.")
263 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
264 raise ex
266 self._parent = parent
267 self._status = status
268 self._reason = reason
269 self._exceptionType = exceptionType
270 self._isAssertionError = isAssertionError
271 self._stackTrace = stackTrace
273 if parent is not None:
274 parent._result = self
276 @readonly
277 def Parent(self) -> Nullable[Node]:
278 """
279 Read-only property to access the test or container the result belongs to (:attr:`_parent`).
281 :returns: The node; ``None`` if the result belongs to no node.
282 """
283 return self._parent
285 @readonly
286 def Status(self) -> Nullable[ResultStatus]:
287 """
288 Read-only property to access the status of the result (:attr:`_status`).
290 :returns: The status, or ``None`` if it wasn't reported.
291 """
292 return self._status
294 @readonly
295 def Reason(self) -> Nullable[str]:
296 """
297 Read-only property to access the reason for the status (:attr:`_reason`).
299 :returns: The reason, or ``None`` if it wasn't reported.
300 """
301 return self._reason
303 @readonly
304 def ExceptionType(self) -> Nullable[str]:
305 """
306 Read-only property to access the fully qualified type of the exception (:attr:`_exceptionType`).
308 A failed JUnit assertion raises ``org.opentest4j.AssertionFailedError``.
310 :returns: The exception's type, or ``None`` if no exception was reported.
311 """
312 return self._exceptionType
314 @readonly
315 def IsAssertionError(self) -> Nullable[bool]:
316 """
317 Read-only property to access whether the exception is an assertion error (:attr:`_isAssertionError`).
319 :returns: True, if the exception is an assertion error, or ``None`` if no exception was reported.
320 """
321 return self._isAssertionError
323 @readonly
324 def StackTrace(self) -> Nullable[str]:
325 """
326 Read-only property to access the stack trace of the exception (:attr:`_stackTrace`).
328 :returns: The stack trace, starting with the exception's type and message, or ``None`` if no exception was
329 reported.
330 """
331 return self._stackTrace
333 @readonly
334 def Message(self) -> Nullable[str]:
335 """
336 Read-only property to return the message explaining the status.
338 It's the reason, if one was reported, else the exception's message: the stack trace's text before its first frame,
339 without the exception's type.
341 :returns: The message, or ``None`` if neither a reason nor an exception with a message was reported.
342 """
343 if self._reason is not None:
344 return self._reason
345 elif self._stackTrace is None:
346 return None
348 message = self._stackTrace.split("\n\tat ", 1)[0].rstrip("\r\n")
349 if message == self._exceptionType:
350 return None
352 return message.removeprefix(f"{self._exceptionType}: ")