Coverage for pyEDAA/Reports/CodeCoverage/UCIS/HistoryNodes.py: 76%
230 statements
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-14 02:26 +0000
« prev ^ index » next coverage.py v7.17.3, created at 2026-11-14 02:26 +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 ValueError: If parameter ``historyNodeID`` is ``None``.
148 :raises TypeError: If parameter ``historyNodeID`` isn't of type :class:`int`.
149 :raises ValueError: If parameter ``historyNodeID`` is negative.
150 :raises ValueError: If parameter ``logicalName`` is ``None``.
151 :raises TypeError: If parameter ``logicalName`` isn't of type :class:`str`.
152 :raises ValueError: If parameter ``testStatus`` is ``None``.
153 :raises TypeError: If parameter ``testStatus`` isn't of type :class:`bool`.
154 :raises ValueError: If parameter ``date`` is ``None``.
155 :raises TypeError: If parameter ``date`` isn't of type :class:`~datetime.datetime`.
156 :raises ValueError: If parameter ``toolCategory`` is ``None``.
157 :raises TypeError: If parameter ``toolCategory`` isn't of type :class:`str`.
158 :raises ValueError: If parameter ``ucisVersion`` is ``None``.
159 :raises TypeError: If parameter ``ucisVersion`` isn't of type :class:`str`.
160 :raises ValueError: If parameter ``vendorID`` is ``None``.
161 :raises TypeError: If parameter ``vendorID`` isn't of type :class:`str`.
162 :raises ValueError: If parameter ``vendorTool`` is ``None``.
163 :raises TypeError: If parameter ``vendorTool`` isn't of type :class:`str`.
164 :raises ValueError: If parameter ``vendorToolVersion`` is ``None``.
165 :raises TypeError: If parameter ``vendorToolVersion`` isn't of type :class:`str`.
166 :raises TypeError: If parameter ``parentID`` isn't of type :class:`int`.
167 :raises ValueError: If parameter ``parentID`` is negative.
168 :raises TypeError: If parameter ``physicalName`` isn't of type :class:`~pathlib.Path`.
169 :raises TypeError: If parameter ``kind`` isn't of type :class:`str`.
170 :raises TypeError: If parameter ``simulationTime`` isn't of type :class:`float`.
171 :raises TypeError: If parameter ``timeUnit`` isn't of type :class:`str`.
172 :raises TypeError: If parameter ``runDirectory`` isn't of type :class:`~pathlib.Path`.
173 :raises TypeError: If parameter ``cpuTime`` isn't of type :class:`float`.
174 :raises TypeError: If parameter ``seed`` isn't of type :class:`str`.
175 :raises TypeError: If parameter ``command`` isn't of type :class:`str`.
176 :raises TypeError: If parameter ``arguments`` isn't of type :class:`str`.
177 :raises TypeError: If parameter ``compulsory`` isn't of type :class:`str`.
178 :raises TypeError: If parameter ``userName`` isn't of type :class:`str`.
179 :raises TypeError: If parameter ``cost`` isn't of type :class:`~decimal.Decimal`.
180 :raises TypeError: If parameter ``sameTests`` isn't of type :class:`int`.
181 :raises ValueError: If parameter ``sameTests`` is negative.
182 :raises TypeError: If parameter ``comment`` isn't of type :class:`str`.
183 :raises TypeError: If parameter ``userAttributes`` isn't iterable.
184 :raises TypeError: If parameter ``userAttributes`` contains an element not of type
185 :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Elements.UserAttribute`.
186 :raises TypeError: If parameter ``parent`` isn't of type :class:`~pyEDAA.Reports.CodeCoverage.UCIS.Report`.
187 """
188 from pyEDAA.Reports.CodeCoverage.UCIS import Report
190 super().__init__(userAttributes)
192 if historyNodeID is None: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true
193 raise ValueError("Parameter 'historyNodeID' is None.")
194 elif not isinstance(historyNodeID, int) or isinstance(historyNodeID, bool): 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 ex = TypeError("Parameter 'historyNodeID' is not of type 'int'.")
196 ex.add_note(f"Got type '{getFullyQualifiedName(historyNodeID)}'.")
197 raise ex
198 elif historyNodeID < 0: 198 ↛ 199line 198 didn't jump to line 199 because the condition on line 198 was never true
199 ex = ValueError("Parameter 'historyNodeID' is negative.")
200 ex.add_note(f"Got value '{historyNodeID}'.")
201 raise ex
203 if testStatus is None: 203 ↛ 204line 203 didn't jump to line 204 because the condition on line 203 was never true
204 raise ValueError("Parameter 'testStatus' is None.")
205 elif not isinstance(testStatus, bool): 205 ↛ 206line 205 didn't jump to line 206 because the condition on line 205 was never true
206 ex = TypeError("Parameter 'testStatus' is not of type 'bool'.")
207 ex.add_note(f"Got type '{getFullyQualifiedName(testStatus)}'.")
208 raise ex
210 if date is None: 210 ↛ 211line 210 didn't jump to line 211 because the condition on line 210 was never true
211 raise ValueError("Parameter 'date' is None.")
212 elif not isinstance(date, datetime):
213 ex = TypeError("Parameter 'date' is not of type 'datetime'.")
214 ex.add_note(f"Got type '{getFullyQualifiedName(date)}'.")
215 raise ex
217 for name, value in (
218 ("logicalName", logicalName),
219 ("toolCategory", toolCategory),
220 ("ucisVersion", ucisVersion),
221 ("vendorID", vendorID),
222 ("vendorTool", vendorTool),
223 ("vendorToolVersion", vendorToolVersion)
224 ):
225 if value is None:
226 raise ValueError(f"Parameter '{name}' is None.")
227 elif not isinstance(value, str): 227 ↛ 228line 227 didn't jump to line 228 because the condition on line 227 was never true
228 ex = TypeError(f"Parameter '{name}' is not of type 'str'.")
229 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
230 raise ex
232 if parentID is not None:
233 if not isinstance(parentID, int) or isinstance(parentID, bool): 233 ↛ 234line 233 didn't jump to line 234 because the condition on line 233 was never true
234 ex = TypeError("Parameter 'parentID' is not of type 'int'.")
235 ex.add_note(f"Got type '{getFullyQualifiedName(parentID)}'.")
236 raise ex
237 elif parentID < 0: 237 ↛ 242line 237 didn't jump to line 242 because the condition on line 237 was always true
238 ex = ValueError("Parameter 'parentID' is negative.")
239 ex.add_note(f"Got value '{parentID}'.")
240 raise ex
242 if sameTests is not None: 242 ↛ 243line 242 didn't jump to line 243 because the condition on line 242 was never true
243 if not isinstance(sameTests, int) or isinstance(sameTests, bool):
244 ex = TypeError("Parameter 'sameTests' is not of type 'int'.")
245 ex.add_note(f"Got type '{getFullyQualifiedName(sameTests)}'.")
246 raise ex
247 elif sameTests < 0:
248 ex = ValueError("Parameter 'sameTests' is negative.")
249 ex.add_note(f"Got value '{sameTests}'.")
250 raise ex
252 if physicalName is not None and not isinstance(physicalName, Path): 252 ↛ 253line 252 didn't jump to line 253 because the condition on line 252 was never true
253 ex = TypeError("Parameter 'physicalName' is not of type 'Path'.")
254 ex.add_note(f"Got type '{getFullyQualifiedName(physicalName)}'.")
255 raise ex
257 if runDirectory is not None and not isinstance(runDirectory, Path): 257 ↛ 258line 257 didn't jump to line 258 because the condition on line 257 was never true
258 ex = TypeError("Parameter 'runDirectory' is not of type 'Path'.")
259 ex.add_note(f"Got type '{getFullyQualifiedName(runDirectory)}'.")
260 raise ex
262 if simulationTime is not None and not isinstance(simulationTime, float):
263 ex = TypeError("Parameter 'simulationTime' is not of type 'float'.")
264 ex.add_note(f"Got type '{getFullyQualifiedName(simulationTime)}'.")
265 raise ex
267 if cpuTime is not None and not isinstance(cpuTime, float): 267 ↛ 268line 267 didn't jump to line 268 because the condition on line 267 was never true
268 ex = TypeError("Parameter 'cpuTime' is not of type 'float'.")
269 ex.add_note(f"Got type '{getFullyQualifiedName(cpuTime)}'.")
270 raise ex
272 for name, value in (
273 ("kind", kind),
274 ("timeUnit", timeUnit),
275 ("seed", seed),
276 ("command", command),
277 ("arguments", arguments),
278 ("compulsory", compulsory),
279 ("userName", userName),
280 ("comment", comment)
281 ):
282 if value is not None and not isinstance(value, str): 282 ↛ 283line 282 didn't jump to line 283 because the condition on line 282 was never true
283 ex = TypeError(f"Parameter '{name}' is not of type 'str'.")
284 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
285 raise ex
287 if cost is not None and not isinstance(cost, Decimal): 287 ↛ 288line 287 didn't jump to line 288 because the condition on line 287 was never true
288 ex = TypeError("Parameter 'cost' is not of type 'Decimal'.")
289 ex.add_note(f"Got type '{getFullyQualifiedName(cost)}'.")
290 raise ex
292 if parent is not None and not isinstance(parent, Report): 292 ↛ 293line 292 didn't jump to line 293 because the condition on line 292 was never true
293 ex = TypeError("Parameter 'parent' is not of type 'Report'.")
294 ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.")
295 raise ex
297 self._parent = parent
298 self._historyNodeID = historyNodeID
299 self._logicalName = logicalName
300 self._testStatus = testStatus
301 self._date = date
302 self._toolCategory = toolCategory
303 self._ucisVersion = ucisVersion
304 self._vendorID = vendorID
305 self._vendorTool = vendorTool
306 self._vendorToolVersion = vendorToolVersion
307 self._parentID = parentID
308 self._physicalName = physicalName
309 self._kind = kind
310 self._simulationTime = simulationTime
311 self._timeUnit = timeUnit
312 self._runDirectory = runDirectory
313 self._cpuTime = cpuTime
314 self._seed = seed
315 self._command = command
316 self._arguments = arguments
317 self._compulsory = compulsory
318 self._userName = userName
319 self._cost = cost
320 self._sameTests = sameTests
321 self._comment = comment
323 if parent is not None:
324 parent._historyNodes[historyNodeID] = self
326 @classmethod
327 def Parse(cls, element: _Element, *, parent: Nullable[Report] = None) -> Self:
328 """
329 Parse a history node from its ``<historyNodes>`` element.
331 :param element: The ``<historyNodes>`` element.
332 :param parent: Optional, the report the history node belongs to. Default: ``None``.
333 :returns: The history node.
334 :raises CodeCoverageError: If a user-defined attribute's value isn't of the type the attribute states.
335 """
336 attributes = element.attrib
337 parentID = attributes.get("parentId")
338 physicalName = attributes.get("physicalName")
339 simulationTime = attributes.get("simtime")
340 runDirectory = attributes.get("runCwd")
341 cpuTime = attributes.get("cpuTime")
342 cost = attributes.get("cost")
343 sameTests = attributes.get("sameTests")
345 return cls(
346 int(attributes["historyNodeId"]),
347 attributes["logicalName"],
348 cls._ParseBoolean(attributes["testStatus"]),
349 datetime.fromisoformat(attributes["date"]),
350 attributes["toolCategory"],
351 attributes["ucisVersion"],
352 attributes["vendorId"],
353 attributes["vendorTool"],
354 attributes["vendorToolVersion"],
355 None if parentID is None else int(parentID),
356 None if physicalName is None else Path(physicalName),
357 attributes.get("kind"),
358 None if simulationTime is None else float(simulationTime),
359 attributes.get("timeunit"),
360 None if runDirectory is None else Path(runDirectory),
361 None if cpuTime is None else float(cpuTime),
362 attributes.get("seed"),
363 attributes.get("cmd"),
364 attributes.get("args"),
365 attributes.get("compulsory"),
366 attributes.get("userName"),
367 None if cost is None else Decimal(cost),
368 None if sameTests is None else int(sameTests),
369 attributes.get("comment"),
370 cls._ParseUserAttributes(element),
371 parent=parent
372 )
374 @readonly
375 def Parent(self) -> Nullable[Report]:
376 """
377 Read-only property to access the report the history node belongs to (:attr:`_parent`).
379 :returns: The report; ``None``, if the history node belongs to none.
380 """
381 return self._parent
383 @readonly
384 def HistoryNodeID(self) -> int:
385 """
386 Read-only property to access the ID of the history node (:attr:`_historyNodeID`).
388 :returns: The ID, by which bins name the tests, which covered them.
389 """
390 return self._historyNodeID
392 @readonly
393 def LogicalName(self) -> str:
394 """
395 Read-only property to access the name of the history node (:attr:`_logicalName`).
397 :returns: The name, e.g. of the test.
398 """
399 return self._logicalName
401 @readonly
402 def TestStatus(self) -> bool:
403 """
404 Read-only property to access whether the test passed (:attr:`_testStatus`).
406 :returns: ``True``, if the test passed.
407 """
408 return self._testStatus
410 @readonly
411 def Date(self) -> datetime:
412 """
413 Read-only property to access when the test ran (:attr:`_date`).
415 :returns: The date and time.
416 """
417 return self._date
419 @readonly
420 def ToolCategory(self) -> str:
421 """
422 Read-only property to access the kind of the tool (:attr:`_toolCategory`).
424 :returns: The kind, as stated.
425 """
426 return self._toolCategory
428 @readonly
429 def UCISVersion(self) -> str:
430 """
431 Read-only property to access the UCIS version, in which the tool measured the coverage (:attr:`_ucisVersion`).
433 :returns: The version, as stated.
434 """
435 return self._ucisVersion
437 @readonly
438 def VendorID(self) -> str:
439 """
440 Read-only property to access the ID of the tool's vendor (:attr:`_vendorID`).
442 :returns: The ID, as stated.
443 """
444 return self._vendorID
446 @readonly
447 def VendorTool(self) -> str:
448 """
449 Read-only property to access the name of the tool (:attr:`_vendorTool`).
451 :returns: The name, as stated.
452 """
453 return self._vendorTool
455 @readonly
456 def VendorToolVersion(self) -> str:
457 """
458 Read-only property to access the version of the tool (:attr:`_vendorToolVersion`).
460 :returns: The version, as stated.
461 """
462 return self._vendorToolVersion
464 @readonly
465 def ParentID(self) -> Nullable[int]:
466 """
467 Read-only property to access the ID of the history node this one belongs to (:attr:`_parentID`).
469 :returns: The ID, e.g. of the merge, which merged this one; ``None``, if the report states none.
470 """
471 return self._parentID
473 @readonly
474 def PhysicalName(self) -> Nullable[Path]:
475 """
476 Read-only property to access the path of the coverage database the history node wrote (:attr:`_physicalName`).
478 :returns: The path, as stated; ``None``, if the report states none.
479 """
480 return self._physicalName
482 @readonly
483 def Kind(self) -> Nullable[str]:
484 """
485 Read-only property to access the kind of the history node (:attr:`_kind`).
487 :returns: The kind, as stated; ``None``, if the report states none.
488 """
489 return self._kind
491 @readonly
492 def SimulationTime(self) -> Nullable[float]:
493 """
494 Read-only property to access how long the test ran in simulated time (:attr:`_simulationTime`).
496 :returns: The time, in :attr:`TimeUnit`; ``None``, if the report states none.
497 """
498 return self._simulationTime
500 @readonly
501 def TimeUnit(self) -> Nullable[str]:
502 """
503 Read-only property to access the unit of the simulated time (:attr:`_timeUnit`).
505 :returns: The unit, e.g. ``ns``; ``None``, if the report states none.
506 """
507 return self._timeUnit
509 @readonly
510 def RunDirectory(self) -> Nullable[Path]:
511 """
512 Read-only property to access the directory the test ran in (:attr:`_runDirectory`).
514 It is a path on the machine the coverage was measured on, so it may not exist where the report is read.
516 :returns: The directory; ``None``, if the report states none.
517 """
518 return self._runDirectory
520 @readonly
521 def CPUTime(self) -> Nullable[float]:
522 """
523 Read-only property to access how long the test ran in CPU time (:attr:`_cpuTime`).
525 :returns: The time; ``None``, if the report states none.
526 """
527 return self._cpuTime
529 @readonly
530 def Seed(self) -> Nullable[str]:
531 """
532 Read-only property to access the seed of the test's random numbers (:attr:`_seed`).
534 :returns: The seed; ``None``, if the report states none.
535 """
536 return self._seed
538 @readonly
539 def Command(self) -> Nullable[str]:
540 """
541 Read-only property to access the command, which ran the test (:attr:`_command`).
543 :returns: The command; ``None``, if the report states none.
544 """
545 return self._command
547 @readonly
548 def Arguments(self) -> Nullable[str]:
549 """
550 Read-only property to access the command line arguments of the test (:attr:`_arguments`).
552 :returns: The arguments; ``None``, if the report states none.
553 """
554 return self._arguments
556 @readonly
557 def Compulsory(self) -> Nullable[str]:
558 """
559 Read-only property to access whether the test must run (:attr:`_compulsory`).
561 :returns: The value, as stated; ``None``, if the report states none.
562 """
563 return self._compulsory
565 @readonly
566 def UserName(self) -> Nullable[str]:
567 """
568 Read-only property to access the name of the user, who ran the test (:attr:`_userName`).
570 :returns: The name; ``None``, if the report states none.
571 """
572 return self._userName
574 @readonly
575 def Cost(self) -> Nullable[Decimal]:
576 """
577 Read-only property to access the cost of the test (:attr:`_cost`).
579 :returns: The cost; ``None``, if the report states none.
580 """
581 return self._cost
583 @readonly
584 def SameTests(self) -> Nullable[int]:
585 """
586 Read-only property to access the ID of a test this one is equivalent to (:attr:`_sameTests`).
588 :returns: The ID; ``None``, if the report states none.
589 """
590 return self._sameTests
592 @readonly
593 def Comment(self) -> Nullable[str]:
594 """
595 Read-only property to access the comment (:attr:`_comment`).
597 :returns: The comment; ``None``, if the report states none.
598 """
599 return self._comment