Coverage for pyEDAA/OSVVM/Build.py: 60%
308 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-28 23:17 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-28 23:17 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ ______ ____ ____ __ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| #
7# |_| |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2021-2025 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"""Reader for OSVVM test report summary files in YAML format."""
32from datetime import timedelta, datetime
33from pathlib import Path
34from typing import Optional as Nullable, Iterator, Iterable, Mapping, Any, List
36from ruamel.yaml import YAML, CommentedMap, CommentedSeq
37from pyTooling.Decorators import export, InheritDocString, notimplemented, readonly
38from pyTooling.MetaClasses import ExtendedType
39from pyTooling.Common import getFullyQualifiedName
40from pyTooling.Stopwatch import Stopwatch
41from pyTooling.Versioning import CalendarVersion, SemanticVersion
43from pyEDAA.Reports.Unittesting import UnittestException, Document, TestcaseStatus, TestsuiteStatus, TestsuiteType, TestsuiteKind
44from pyEDAA.Reports.Unittesting import TestsuiteSummary as ut_TestsuiteSummary, Testsuite as ut_Testsuite
45from pyEDAA.Reports.Unittesting import Testcase as ut_Testcase
48@export
49class OsvvmException:
50 pass
53@export
54@InheritDocString(UnittestException)
55class UnittestException(UnittestException, OsvvmException):
56 """@InheritDocString(UnittestException)"""
59@export
60@InheritDocString(ut_Testcase)
61class Testcase(ut_Testcase):
62 """@InheritDocString(ut_Testcase)"""
64 _disabledWarningCount: int
65 _disabledErrorCount: int
66 _disabledFatalCount: int
68 _requirementsCount: Nullable[int]
69 _passedRequirementsCount: Nullable[int]
70 _failedRequirementsCount: Nullable[int]
71 _functionalCoverage: Nullable[float]
73 def __init__(
74 self,
75 name: str,
76 startTime: Nullable[datetime] = None,
77 setupDuration: Nullable[timedelta] = None,
78 testDuration: Nullable[timedelta] = None,
79 teardownDuration: Nullable[timedelta] = None,
80 totalDuration: Nullable[timedelta] = None,
81 status: TestcaseStatus = TestcaseStatus.Unknown,
82 assertionCount: Nullable[int] = None,
83 failedAssertionCount: Nullable[int] = None,
84 passedAssertionCount: Nullable[int] = None,
85 requirementsCount: Nullable[int] = None,
86 passedRequirementsCount: Nullable[int] = None,
87 failedRequirementsCount: Nullable[int] = None,
88 functionalCoverage: Nullable[float] = None,
89 warningCount: int = 0,
90 errorCount: int = 0,
91 fatalCount: int = 0,
92 disabledWarningCount: int = 0,
93 disabledErrorCount: int = 0,
94 disabledFatalCount: int = 0,
95 expectedWarningCount: int = 0,
96 expectedErrorCount: int = 0,
97 expectedFatalCount: int = 0,
98 keyValuePairs: Nullable[Mapping[str, Any]] = None,
99 parent: Nullable["Testsuite"] = None
100 ):
101 """
102 Initializes the fields of a test case.
104 :param name: Name of the test entity.
105 :param startTime: Time when the test entity was started.
106 :param setupDuration: Duration it took to set up the entity.
107 :param testDuration: Duration of the entity's test run.
108 :param teardownDuration: Duration it took to tear down the entity.
109 :param totalDuration: Total duration of the entity's execution (setup + test + teardown)
110 :param status: Status of the test case.
111 :param assertionCount: Number of assertions within the test.
112 :param failedAssertionCount: Number of failed assertions within the test.
113 :param passedAssertionCount: Number of passed assertions within the test.
114 :param requirementsCount: Number of requirements within the test.
115 :param failedRequirementsCount: Number of failed requirements within the test.
116 :param passedRequirementsCount: Number of passed requirements within the test.
117 :param warningCount: Count of encountered warnings.
118 :param errorCount: Count of encountered errors.
119 :param fatalCount: Count of encountered fatal errors.
120 :param disabledWarningCount: Count of disabled warnings.
121 :param disabledErrorCount: Count of disabled errors.
122 :param disabledFatalCount: Count of disabled fatal errors.
123 :param expectedWarningCount: Count of expected warnings.
124 :param expectedErrorCount: Count of expected errors.
125 :param expectedFatalCount: Count of expected fatal errors.
126 :param keyValuePairs: Mapping of key-value pairs to initialize the test case.
127 :param parent: Reference to the parent test suite.
128 :raises TypeError: If parameter 'parent' is not a Testsuite.
129 :raises ValueError: If parameter 'assertionCount' is not consistent.
130 """
131 super().__init__(
132 name,
133 startTime, setupDuration, testDuration, teardownDuration, totalDuration,
134 status,
135 assertionCount, failedAssertionCount, passedAssertionCount,
136 warningCount, errorCount, fatalCount,
137 expectedWarningCount, expectedErrorCount, expectedFatalCount,
138 keyValuePairs,
139 parent
140 )
142 if not isinstance(disabledWarningCount, int): 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true
143 ex = TypeError(f"Parameter 'disabledWarningCount' is not of type 'int'.")
144 ex.add_note(f"Got type '{getFullyQualifiedName(disabledWarningCount)}'.")
145 raise ex
147 if not isinstance(disabledErrorCount, int): 147 ↛ 148line 147 didn't jump to line 148 because the condition on line 147 was never true
148 ex = TypeError(f"Parameter 'disabledErrorCount' is not of type 'int'.")
149 ex.add_note(f"Got type '{getFullyQualifiedName(disabledErrorCount)}'.")
150 raise ex
152 if not isinstance(disabledFatalCount, int): 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true
153 ex = TypeError(f"Parameter 'disabledFatalCount' is not of type 'int'.")
154 ex.add_note(f"Got type '{getFullyQualifiedName(disabledFatalCount)}'.")
155 raise ex
157 self._disabledWarningCount = disabledWarningCount
158 self._disabledErrorCount = disabledErrorCount
159 self._disabledFatalCount = disabledFatalCount
161 if requirementsCount is not None and not isinstance(requirementsCount, int): 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true
162 ex = TypeError(f"Parameter 'requirementsCount' is not of type 'int'.")
163 ex.add_note(f"Got type '{getFullyQualifiedName(requirementsCount)}'.")
164 raise ex
166 if passedRequirementsCount is not None and not isinstance(passedRequirementsCount, int): 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true
167 ex = TypeError(f"Parameter 'passedRequirementsCount' is not of type 'int'.")
168 ex.add_note(f"Got type '{getFullyQualifiedName(passedRequirementsCount)}'.")
169 raise ex
171 if failedRequirementsCount is not None and not isinstance(failedRequirementsCount, int): 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true
172 ex = TypeError(f"Parameter 'failedRequirementsCount' is not of type 'int'.")
173 ex.add_note(f"Got type '{getFullyQualifiedName(failedRequirementsCount)}'.")
174 raise ex
176 if requirementsCount is not None: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true
177 if passedRequirementsCount is not None:
178 if failedRequirementsCount is not None:
179 if passedRequirementsCount + failedRequirementsCount != requirementsCount:
180 raise ValueError(f"Parameter 'requirementsCount' is not the sum of 'passedRequirementsCount' and 'failedRequirementsCount'.")
181 else:
182 failedRequirementsCount = requirementsCount - passedRequirementsCount
183 elif failedRequirementsCount is not None:
184 passedRequirementsCount = requirementsCount - failedRequirementsCount
185 else:
186 passedRequirementsCount = requirementsCount
187 failedRequirementsCount = 0
188 else:
189 if passedRequirementsCount is not None: 189 ↛ 190line 189 didn't jump to line 190 because the condition on line 189 was never true
190 if failedRequirementsCount is not None:
191 requirementsCount = passedRequirementsCount + failedRequirementsCount
192 else:
193 requirementsCount = passedRequirementsCount
194 failedRequirementsCount = 0
195 elif failedRequirementsCount is not None: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 requirementsCount = failedRequirementsCount
197 passedRequirementsCount = 0
199 self._requirementsCount = requirementsCount
200 self._passedRequirementsCount = passedRequirementsCount
201 self._failedRequirementsCount = failedRequirementsCount
203 if functionalCoverage is not None: 203 ↛ 204line 203 didn't jump to line 204 because the condition on line 203 was never true
204 if not isinstance(functionalCoverage, float):
205 ex = TypeError(f"Parameter 'functionalCoverage' is not of type 'float'.")
206 ex.add_note(f"Got type '{getFullyQualifiedName(functionalCoverage)}'.")
207 raise ex
209 if not (0.0 <= functionalCoverage <= 1.0):
210 raise ValueError(f"Parameter 'functionalCoverage' is not in range 0.0..1.0.")
212 self._functionalCoverage = functionalCoverage
214 @readonly
215 def DisabledWarningCount(self) -> int:
216 """
217 Read-only property returning the number of disabled warnings.
219 :return: Count of disabled warnings.
220 """
221 return self._disabledWarningCount
223 @readonly
224 def DisabledErrorCount(self) -> int:
225 """
226 Read-only property returning the number of disabled errors.
228 :return: Count of disabled errors.
229 """
230 return self._disabledErrorCount
232 @readonly
233 def DisabledFatalCount(self) -> int:
234 """
235 Read-only property returning the number of disabled fatal errors.
237 :return: Count of disabled fatal errors.
238 """
239 return self._disabledFatalCount
241 @readonly
242 def RequirementsCount(self) -> int:
243 """
244 Read-only property returning the number of requirements.
246 :return: Count of requirements.
247 """
248 return self._requirementsCount
250 @readonly
251 def PassedRequirementsCount(self) -> int:
252 """
253 Read-only property returning the number of passed requirements.
255 :return: Count of passed rerquirements.
256 """
257 return self._passedRequirementsCount
259 @readonly
260 def FailedRequirementsCount(self) -> int:
261 """
262 Read-only property returning the number of failed requirements.
264 :return: Count of failed requirements.
265 """
266 return self._failedRequirementsCount
268 @readonly
269 def FunctionalCoverage(self) -> float:
270 """
271 Read-only property returning the functional coverage.
273 :return: Percentage of functional coverage.
274 """
275 return self._functionalCoverage
278@export
279@InheritDocString(ut_Testsuite)
280class Testsuite(ut_Testsuite):
281 """@InheritDocString(ut_Testsuite)"""
284@export
285class BuildInformation(metaclass=ExtendedType, slots=True):
286 _startTime: datetime
287 _finishTime: datetime
288 _elapsed: timedelta
289 _simulator: str
290 _simulatorVersion: SemanticVersion
291 _osvvmVersion: CalendarVersion
292 _buildErrorCode: int
293 _analyzeErrorCount: int
294 _simulateErrorCount: int
296 def __init__(self) -> None:
297 pass
300@export
301class Settings(metaclass=ExtendedType, slots=True):
302 _baseDirectory: Path
303 _reportsSubdirectory: Path
304 _simulationLogFile: Path
305 _simulationHtmlLogFile: Path
306 _requirementsSubdirectory: Path
307 _coverageSubdirectory: Path
308 _report2CssFiles: List[Path]
309 _report2PngFile: List[Path]
311 def __init__(self) -> None:
312 pass
315@export
316@InheritDocString(ut_TestsuiteSummary)
317class TestsuiteSummary(ut_TestsuiteSummary):
318 """@InheritDocString(ut_TestsuiteSummary)"""
320 _datetime: datetime
322 def __init__(
323 self,
324 name: str,
325 startTime: Nullable[datetime] = None,
326 setupDuration: Nullable[timedelta] = None,
327 testDuration: Nullable[timedelta] = None,
328 teardownDuration: Nullable[timedelta] = None,
329 totalDuration: Nullable[timedelta] = None,
330 status: TestsuiteStatus = TestsuiteStatus.Unknown,
331 warningCount: int = 0,
332 errorCount: int = 0,
333 fatalCount: int = 0,
334 testsuites: Nullable[Iterable[TestsuiteType]] = None,
335 keyValuePairs: Nullable[Mapping[str, Any]] = None,
336 parent: Nullable[TestsuiteType] = None
337 ) -> None:
338 """
339 Initializes the fields of a test summary.
341 :param name: Name of the test summary.
342 :param startTime: Time when the test summary was started.
343 :param setupDuration: Duration it took to set up the test summary.
344 :param testDuration: Duration of all tests listed in the test summary.
345 :param teardownDuration: Duration it took to tear down the test summary.
346 :param totalDuration: Total duration of the entity's execution (setup + test + teardown)
347 :param status: Overall status of the test summary.
348 :param warningCount: Count of encountered warnings incl. warnings from sub-elements.
349 :param errorCount: Count of encountered errors incl. errors from sub-elements.
350 :param fatalCount: Count of encountered fatal errors incl. fatal errors from sub-elements.
351 :param testsuites: List of test suites to initialize the test summary with.
352 :param keyValuePairs: Mapping of key-value pairs to initialize the test summary with.
353 :param parent: Reference to the parent test summary.
354 """
355 super().__init__(
356 name,
357 startTime, setupDuration, testDuration, teardownDuration, totalDuration,
358 status,
359 warningCount, errorCount, fatalCount,
360 testsuites,
361 keyValuePairs,
362 parent
363 )
366@export
367class BuildSummaryDocument(TestsuiteSummary, Document):
368 _yamlDocument: Nullable[YAML]
370 def __init__(self, yamlReportFile: Path, analyzeAndConvert: bool = False) -> None:
371 super().__init__("Unprocessed OSVVM YAML file")
373 self._yamlDocument = None
375 Document.__init__(self, yamlReportFile, analyzeAndConvert)
377 def Analyze(self) -> None:
378 """
379 Analyze the YAML file, parse the content into an YAML data structure.
381 .. hint::
383 The time spend for analysis will be made available via property :data:`AnalysisDuration`..
384 """
385 if not self._path.exists(): 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true
386 raise UnittestException(f"OSVVM YAML file '{self._path}' does not exist.") \
387 from FileNotFoundError(f"File '{self._path}' not found.")
389 with Stopwatch() as sw:
390 try:
391 yamlReader = YAML()
392 self._yamlDocument = yamlReader.load(self._path)
393 except Exception as ex:
394 raise UnittestException(f"Couldn't open '{self._path}'.") from ex
396 self._analysisDuration = sw.Duration
398 @notimplemented
399 def Write(self, path: Nullable[Path] = None, overwrite: bool = False) -> None:
400 """
401 Write the data model as XML into a file adhering to the Any JUnit dialect.
403 :param path: Optional path to the YAML file, if internal path shouldn't be used.
404 :param overwrite: If true, overwrite an existing file.
405 :raises UnittestException: If the file cannot be overwritten.
406 :raises UnittestException: If the internal YAML data structure wasn't generated.
407 :raises UnittestException: If the file cannot be opened or written.
408 """
409 if path is None:
410 path = self._path
412 if not overwrite and path.exists():
413 raise UnittestException(f"OSVVM YAML file '{path}' can not be overwritten.") \
414 from FileExistsError(f"File '{path}' already exists.")
416 # if regenerate:
417 # self.Generate(overwrite=True)
419 if self._yamlDocument is None:
420 ex = UnittestException(f"Internal YAML document tree is empty and needs to be generated before write is possible.")
421 # ex.add_note(f"Call 'BuildSummaryDocument.Generate()' or 'BuildSummaryDocument.Write(..., regenerate=True)'.")
422 raise ex
424 # with path.open("w", encoding="utf-8") as file:
425 # self._yamlDocument.writexml(file, addindent="\t", encoding="utf-8", newl="\n")
427 @staticmethod
428 def _ParseSequenceFromYAML(node: CommentedMap, fieldName: str) -> Nullable[CommentedSeq]:
429 try:
430 value = node[fieldName]
431 except KeyError as ex:
432 newEx = UnittestException(f"Sequence field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
433 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
434 raise newEx from ex
436 if value is None: 436 ↛ 437line 436 didn't jump to line 437 because the condition on line 436 was never true
437 return ()
438 elif not isinstance(value, CommentedSeq): 438 ↛ 439line 438 didn't jump to line 439 because the condition on line 438 was never true
439 line = node._yaml_line_col.data[fieldName][0] + 1
440 ex = UnittestException(f"Field '{fieldName}' is not a sequence.") # TODO: from TypeError??
441 ex.add_note(f"Found type {value.__class__.__name__} at line {line}.")
442 raise ex
444 return value
446 @staticmethod
447 def _ParseMapFromYAML(node: CommentedMap, fieldName: str) -> Nullable[CommentedMap]:
448 try:
449 value = node[fieldName]
450 except KeyError as ex:
451 newEx = UnittestException(f"Dictionary field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
452 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
453 raise newEx from ex
455 if value is None: 455 ↛ 456line 455 didn't jump to line 456 because the condition on line 455 was never true
456 return {}
457 elif not isinstance(value, CommentedMap): 457 ↛ 458line 457 didn't jump to line 458 because the condition on line 457 was never true
458 line = node._yaml_line_col.data[fieldName][0] + 1
459 ex = UnittestException(f"Field '{fieldName}' is not a list.") # TODO: from TypeError??
460 ex.add_note(f"Type mismatch found for line {line}.")
461 raise ex
462 return value
464 @staticmethod
465 def _ParseStrFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[str]:
466 try:
467 value = node[fieldName]
468 except KeyError as ex:
469 newEx = UnittestException(f"String field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
470 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
471 raise newEx from ex
473 if not isinstance(value, str): 473 ↛ 474line 473 didn't jump to line 474 because the condition on line 473 was never true
474 raise UnittestException(f"Field '{fieldName}' is not of type str.") # TODO: from TypeError??
476 return value
478 @staticmethod
479 def _ParseIntFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[int]:
480 try:
481 value = node[fieldName]
482 except KeyError as ex:
483 newEx = UnittestException(f"Integer field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
484 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
485 raise newEx from ex
487 if not isinstance(value, int): 487 ↛ 488line 487 didn't jump to line 488 because the condition on line 487 was never true
488 raise UnittestException(f"Field '{fieldName}' is not of type int.") # TODO: from TypeError??
490 return value
492 @staticmethod
493 def _ParseDateFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[datetime]:
494 try:
495 value = node[fieldName]
496 except KeyError as ex:
497 newEx = UnittestException(f"Date field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
498 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
499 raise newEx from ex
501 if not isinstance(value, datetime): 501 ↛ 502line 501 didn't jump to line 502 because the condition on line 501 was never true
502 raise UnittestException(f"Field '{fieldName}' is not of type datetime.") # TODO: from TypeError??
504 return value
506 @staticmethod
507 def _ParseDurationFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[timedelta]:
508 try:
509 value = node[fieldName]
510 except KeyError as ex:
511 newEx = UnittestException(f"Duration field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
512 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
513 raise newEx from ex
515 if not isinstance(value, float): 515 ↛ 516line 515 didn't jump to line 516 because the condition on line 515 was never true
516 raise UnittestException(f"Field '{fieldName}' is not of type float.") # TODO: from TypeError??
518 return timedelta(seconds=value)
520 def Convert(self) -> None:
521 """
522 Convert the parsed YAML data structure into a test entity hierarchy.
524 This method converts the root element.
526 .. hint::
528 The time spend for model conversion will be made available via property :data:`ModelConversionDuration`.
530 :raises UnittestException: If XML was not read and parsed before.
531 """
532 if self._yamlDocument is None: 532 ↛ 533line 532 didn't jump to line 533 because the condition on line 532 was never true
533 ex = UnittestException(f"OSVVM YAML file '{self._path}' needs to be read and analyzed by a YAML parser.")
534 ex.add_note(f"Call 'Document.Analyze()' or create document using 'Document(path, parse=True)'.")
535 raise ex
537 with Stopwatch() as sw:
538 self._name = self._yamlDocument["Name"]
539 buildInfo = self._ParseMapFromYAML(self._yamlDocument, "BuildInfo")
540 self._startTime = self._ParseDateFieldFromYAML(buildInfo, "StartTime")
541 self._totalDuration = self._ParseDurationFieldFromYAML(buildInfo, "Elapsed")
543 if "TestSuites" in self._yamlDocument:
544 for yamlTestsuite in self._ParseSequenceFromYAML(self._yamlDocument, "TestSuites"):
545 self._ConvertTestsuite(self, yamlTestsuite)
547 self.Aggregate()
549 self._modelConversion = sw.Duration
551 def _ConvertTestsuite(self, parentTestsuite: Testsuite, yamlTestsuite: CommentedMap) -> None:
552 testsuiteName = self._ParseStrFieldFromYAML(yamlTestsuite, "Name")
553 totalDuration = self._ParseDurationFieldFromYAML(yamlTestsuite, "ElapsedTime")
555 testsuite = Testsuite(
556 testsuiteName,
557 totalDuration=totalDuration,
558 parent=parentTestsuite
559 )
561 # if yamlTestsuite['TestCases'] is not None:
562 for yamlTestcase in self._ParseSequenceFromYAML(yamlTestsuite, 'TestCases'):
563 self._ConvertTestcase(testsuite, yamlTestcase)
565 def _ConvertTestcase(self, parentTestsuite: Testsuite, yamlTestcase: CommentedMap) -> None:
566 testcaseName = self._ParseStrFieldFromYAML(yamlTestcase, "TestCaseName")
567 totalDuration = self._ParseDurationFieldFromYAML(yamlTestcase, "ElapsedTime")
568 yamlStatus = self._ParseStrFieldFromYAML(yamlTestcase, "Status").lower()
569 yamlResults = self._ParseMapFromYAML(yamlTestcase, "Results")
570 assertionCount = self._ParseIntFieldFromYAML(yamlResults, "AffirmCount")
571 passedAssertionCount = self._ParseIntFieldFromYAML(yamlResults, "PassedCount")
573 totalErrors = self._ParseIntFieldFromYAML(yamlResults, "TotalErrors")
575 yamlAlertCount = self._ParseMapFromYAML(yamlResults, "AlertCount")
576 warningCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Warning")
577 errorCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Error")
578 failureCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Failure")
580 yamlDisabledAlertCount = self._ParseMapFromYAML(yamlResults, "DisabledAlertCount")
581 disabledWarningCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Warning")
582 disabledErrorCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Error")
583 disabledFailureCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Failure")
585 yamlExpectedAlertCount = self._ParseMapFromYAML(yamlResults, "ExpectedCount")
586 expectedWarningCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Warning")
587 expectedErrorCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Error")
588 expectedFailureCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Failure")
590 # FIXME: write a Parse classmethod in enum
591 if yamlStatus == "passed": 591 ↛ 593line 591 didn't jump to line 593 because the condition on line 591 was always true
592 status = TestcaseStatus.Passed
593 elif yamlStatus == "skipped":
594 status = TestcaseStatus.Skipped
595 elif yamlStatus == "failed":
596 status = TestcaseStatus.Failed
597 else:
598 status = TestcaseStatus.Unknown
600 if (
601 abs(warningDiff := warningCount - expectedWarningCount) +
602 abs(errorDiff := errorCount - expectedErrorCount) +
603 abs(failureDiff := failureCount - expectedFailureCount) -
604 totalErrors
605 ) == 0:
606 if warningDiff > 0: 606 ↛ 607line 606 didn't jump to line 607 because the condition on line 606 was never true
607 status |= TestcaseStatus.Warned
608 if errorDiff > 0: 608 ↛ 609line 608 didn't jump to line 609 because the condition on line 608 was never true
609 status |= TestcaseStatus.Errored
610 if failureDiff > 0: 610 ↛ 611line 610 didn't jump to line 611 because the condition on line 610 was never true
611 status |= TestcaseStatus.Aborted
612 else:
613 status |= TestcaseStatus.Inconsistent
615 _ = Testcase(
616 testcaseName,
617 totalDuration=totalDuration,
618 status=status,
619 assertionCount=assertionCount,
620 passedAssertionCount=passedAssertionCount,
621 warningCount=warningCount,
622 errorCount=errorCount,
623 fatalCount=failureCount,
624 disabledWarningCount=disabledWarningCount,
625 disabledErrorCount=disabledErrorCount,
626 disabledFatalCount=disabledFailureCount,
627 expectedWarningCount=expectedWarningCount,
628 expectedErrorCount=expectedErrorCount,
629 expectedFatalCount=expectedFailureCount,
630 parent=parentTestsuite
631 )
633 def __contains__(self, key: str) -> bool:
634 return key in self._testsuites
636 def __iter__(self) -> Iterator[Testsuite]:
637 return iter(self._testsuites.values())
639 def __getitem__(self, key: str) -> Testsuite:
640 return self._testsuites[key]
642 def __len__(self) -> int:
643 return self._testsuites.__len__()