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

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. 

34 

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 

40 

41from enum import Enum 

42from typing import TYPE_CHECKING, Dict, Optional as Nullable 

43 

44from pyTooling.Common import getFullyQualifiedName 

45from pyTooling.Decorators import export, readonly 

46from pyTooling.MetaClasses import ExtendedType 

47 

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 

50 

51 

52@export 

53class ResultStatus(Enum): 

54 """An enumeration of the statuses of a test's or container's result (``<result status>``).""" 

55 

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. 

61 

62 

63@export 

64class Infrastructure(metaclass=ExtendedType, slots=True): 

65 """ 

66 The infrastructure the tests were run on (``<infrastructure>``). 

67 

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 """ 

71 

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. 

78 

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. 

90 

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 

106 

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 

116 

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 

121 

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 

126 

127 self._parent = parent 

128 self._hostName = hostName 

129 self._userName = userName 

130 self._operatingSystem = operatingSystem 

131 self._cpuCoreCount = cpuCoreCount 

132 self._extensions = {} 

133 

134 if parent is not None: 

135 parent._infrastructure = self 

136 

137 @readonly 

138 def Parent(self) -> Nullable[Execution]: 

139 """ 

140 Read-only property to access the execution the infrastructure belongs to (:attr:`_parent`). 

141 

142 :returns: The execution; ``None`` if the infrastructure belongs to no execution. 

143 """ 

144 return self._parent 

145 

146 @readonly 

147 def HostName(self) -> Nullable[str]: 

148 """ 

149 Read-only property to access the name of the host (:attr:`_hostName`). 

150 

151 :returns: The host name, or ``None`` if it wasn't reported. 

152 """ 

153 return self._hostName 

154 

155 @readonly 

156 def UserName(self) -> Nullable[str]: 

157 """ 

158 Read-only property to access the name of the user (:attr:`_userName`). 

159 

160 :returns: The user name, or ``None`` if it wasn't reported. 

161 """ 

162 return self._userName 

163 

164 @readonly 

165 def OperatingSystem(self) -> Nullable[str]: 

166 """ 

167 Read-only property to access the name of the operating system (:attr:`_operatingSystem`). 

168 

169 :returns: The operating system, or ``None`` if it wasn't reported. 

170 """ 

171 return self._operatingSystem 

172 

173 @readonly 

174 def CPUCoreCount(self) -> Nullable[int]: 

175 """ 

176 Read-only property to access the count of CPU cores (:attr:`_cpuCoreCount`). 

177 

178 :returns: The count of CPU cores, or ``None`` if it wasn't reported. 

179 """ 

180 return self._cpuCoreCount 

181 

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`). 

186 

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``. 

189 

190 :returns: The dictionary of extension values. 

191 """ 

192 return self._extensions 

193 

194 

195@export 

196class Result(metaclass=ExtendedType, slots=True): 

197 """ 

198 The result of a test or container (``<result>``). 

199 

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 """ 

203 

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. 

210 

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. 

223 

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 

240 

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 

245 

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 

255 

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 

260 

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 

265 

266 self._parent = parent 

267 self._status = status 

268 self._reason = reason 

269 self._exceptionType = exceptionType 

270 self._isAssertionError = isAssertionError 

271 self._stackTrace = stackTrace 

272 

273 if parent is not None: 

274 parent._result = self 

275 

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`). 

280 

281 :returns: The node; ``None`` if the result belongs to no node. 

282 """ 

283 return self._parent 

284 

285 @readonly 

286 def Status(self) -> Nullable[ResultStatus]: 

287 """ 

288 Read-only property to access the status of the result (:attr:`_status`). 

289 

290 :returns: The status, or ``None`` if it wasn't reported. 

291 """ 

292 return self._status 

293 

294 @readonly 

295 def Reason(self) -> Nullable[str]: 

296 """ 

297 Read-only property to access the reason for the status (:attr:`_reason`). 

298 

299 :returns: The reason, or ``None`` if it wasn't reported. 

300 """ 

301 return self._reason 

302 

303 @readonly 

304 def ExceptionType(self) -> Nullable[str]: 

305 """ 

306 Read-only property to access the fully qualified type of the exception (:attr:`_exceptionType`). 

307 

308 A failed JUnit assertion raises ``org.opentest4j.AssertionFailedError``. 

309 

310 :returns: The exception's type, or ``None`` if no exception was reported. 

311 """ 

312 return self._exceptionType 

313 

314 @readonly 

315 def IsAssertionError(self) -> Nullable[bool]: 

316 """ 

317 Read-only property to access whether the exception is an assertion error (:attr:`_isAssertionError`). 

318 

319 :returns: True, if the exception is an assertion error, or ``None`` if no exception was reported. 

320 """ 

321 return self._isAssertionError 

322 

323 @readonly 

324 def StackTrace(self) -> Nullable[str]: 

325 """ 

326 Read-only property to access the stack trace of the exception (:attr:`_stackTrace`). 

327 

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 

332 

333 @readonly 

334 def Message(self) -> Nullable[str]: 

335 """ 

336 Read-only property to return the message explaining the status. 

337 

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. 

340 

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 

347 

348 message = self._stackTrace.split("\n\tat ", 1)[0].rstrip("\r\n") 

349 if message == self._exceptionType: 

350 return None 

351 

352 return message.removeprefix(f"{self._exceptionType}: ")