Coverage for pyEDAA/OSVVM/Build.py: 60%
318 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-12 23:17 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-12 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-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"""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 ) -> None:
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 :returns: 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 :returns: 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 :returns: 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 :returns: 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 :returns: 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 :returns: 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 :returns: 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] #: Internal YAML document instance.
369 _version: Nullable[SemanticVersion] #: YAML data structure version.
371 def __init__(self, yamlReportFile: Path, analyzeAndConvert: bool = False) -> None:
372 super().__init__("Unprocessed OSVVM YAML file")
374 self._yamlDocument = None
375 self._version = None
377 Document.__init__(self, yamlReportFile, analyzeAndConvert)
379 @readonly
380 def Version(self) -> Nullable[SemanticVersion]:
381 """
382 Read-only property to access the YAML file's data structure version (:attr:`_version`).
384 :returns: The YAML data structures version.
385 """
386 return self._version
388 def Analyze(self) -> None:
389 """
390 Analyze the YAML file, parse the content into an YAML data structure.
392 .. hint::
394 The time spend for analysis will be made available via property :data:`AnalysisDuration`..
395 """
396 if not self._path.exists(): 396 ↛ 397line 396 didn't jump to line 397 because the condition on line 396 was never true
397 raise UnittestException(f"OSVVM YAML file '{self._path}' does not exist.") \
398 from FileNotFoundError(f"File '{self._path}' not found.")
400 with Stopwatch() as sw:
401 try:
402 yamlReader = YAML()
403 self._yamlDocument = yamlReader.load(self._path)
404 except Exception as ex:
405 raise UnittestException(f"Couldn't open '{self._path}'.") from ex
407 self._analysisDuration = sw.Duration
409 @notimplemented
410 def Write(self, path: Nullable[Path] = None, overwrite: bool = False) -> None:
411 """
412 Write the data model as XML into a file adhering to the Any JUnit dialect.
414 :param path: Optional path to the YAML file, if internal path shouldn't be used.
415 :param overwrite: If true, overwrite an existing file.
416 :raises UnittestException: If the file cannot be overwritten.
417 :raises UnittestException: If the internal YAML data structure wasn't generated.
418 :raises UnittestException: If the file cannot be opened or written.
419 """
420 if path is None:
421 path = self._path
423 if not overwrite and path.exists():
424 raise UnittestException(f"OSVVM YAML file '{path}' can not be overwritten.") \
425 from FileExistsError(f"File '{path}' already exists.")
427 # if regenerate:
428 # self.Generate(overwrite=True)
430 if self._yamlDocument is None:
431 ex = UnittestException(f"Internal YAML document tree is empty and needs to be generated before write is possible.")
432 # ex.add_note(f"Call 'BuildSummaryDocument.Generate()' or 'BuildSummaryDocument.Write(..., regenerate=True)'.")
433 raise ex
435 # with path.open("w", encoding="utf-8") as file:
436 # self._yamlDocument.writexml(file, addindent="\t", encoding="utf-8", newl="\n")
438 @staticmethod
439 def _ParseSequenceFromYAML(node: CommentedMap, fieldName: str) -> Nullable[CommentedSeq]:
440 try:
441 value = node[fieldName]
442 except KeyError as ex:
443 newEx = UnittestException(f"Sequence field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
444 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
445 raise newEx from ex
447 if value is None: 447 ↛ 448line 447 didn't jump to line 448 because the condition on line 447 was never true
448 return ()
449 elif not isinstance(value, CommentedSeq): 449 ↛ 450line 449 didn't jump to line 450 because the condition on line 449 was never true
450 line = node._yaml_line_col.data[fieldName][0] + 1
451 ex = UnittestException(f"Field '{fieldName}' is not a sequence.") # TODO: from TypeError??
452 ex.add_note(f"Found type {value.__class__.__name__} at line {line}.")
453 raise ex
455 return value
457 @staticmethod
458 def _ParseMapFromYAML(node: CommentedMap, fieldName: str) -> Nullable[CommentedMap]:
459 try:
460 value = node[fieldName]
461 except KeyError as ex:
462 newEx = UnittestException(f"Dictionary field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
463 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
464 raise newEx from ex
466 if value is None: 466 ↛ 467line 466 didn't jump to line 467 because the condition on line 466 was never true
467 return {}
468 elif not isinstance(value, CommentedMap): 468 ↛ 469line 468 didn't jump to line 469 because the condition on line 468 was never true
469 line = node._yaml_line_col.data[fieldName][0] + 1
470 ex = UnittestException(f"Field '{fieldName}' is not a list.") # TODO: from TypeError??
471 ex.add_note(f"Type mismatch found for line {line}.")
472 raise ex
473 return value
475 @staticmethod
476 def _ParseStrFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[str]:
477 try:
478 value = node[fieldName]
479 except KeyError as ex:
480 newEx = UnittestException(f"String field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
481 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
482 raise newEx from ex
484 if not isinstance(value, str): 484 ↛ 485line 484 didn't jump to line 485 because the condition on line 484 was never true
485 raise UnittestException(f"Field '{fieldName}' is not of type str.") # TODO: from TypeError??
487 return value
489 @staticmethod
490 def _ParseIntFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[int]:
491 try:
492 value = node[fieldName]
493 except KeyError as ex:
494 newEx = UnittestException(f"Integer field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
495 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
496 raise newEx from ex
498 if not isinstance(value, int): 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true
499 raise UnittestException(f"Field '{fieldName}' is not of type int.") # TODO: from TypeError??
501 return value
503 @staticmethod
504 def _ParseDateFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[datetime]:
505 try:
506 value = node[fieldName]
507 except KeyError as ex:
508 newEx = UnittestException(f"Date field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
509 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
510 raise newEx from ex
512 if not isinstance(value, datetime): 512 ↛ 513line 512 didn't jump to line 513 because the condition on line 512 was never true
513 raise UnittestException(f"Field '{fieldName}' is not of type datetime.") # TODO: from TypeError??
515 return value
517 @staticmethod
518 def _ParseDurationFieldFromYAML(node: CommentedMap, fieldName: str) -> Nullable[timedelta]:
519 try:
520 value = node[fieldName]
521 except KeyError as ex:
522 newEx = UnittestException(f"Duration field '{fieldName}' not found in node starting at line {node.lc.line + 1}.")
523 newEx.add_note(f"Available fields: {', '.join(key for key in node)}")
524 raise newEx from ex
526 if not isinstance(value, float): 526 ↛ 527line 526 didn't jump to line 527 because the condition on line 526 was never true
527 raise UnittestException(f"Field '{fieldName}' is not of type float.") # TODO: from TypeError??
529 return timedelta(seconds=value)
531 def Convert(self) -> None:
532 """
533 Convert the parsed YAML data structure into a test entity hierarchy.
535 This method converts the root element.
537 .. hint::
539 The time spend for model conversion will be made available via property :data:`ModelConversionDuration`.
541 :raises UnittestException: If XML was not read and parsed before.
542 """
543 if self._yamlDocument is None: 543 ↛ 544line 543 didn't jump to line 544 because the condition on line 543 was never true
544 ex = UnittestException(f"OSVVM YAML file '{self._path}' needs to be read and analyzed by a YAML parser.")
545 ex.add_note(f"Call 'Document.Analyze()' or create document using 'Document(path, parse=True)'.")
546 raise ex
548 with Stopwatch() as sw:
549 self._version = SemanticVersion.Parse(self._yamlDocument["Version"])
550 if not (self._version >> "0.1"): 550 ↛ 551line 550 didn't jump to line 551 because the condition on line 550 was never true
551 ex = UnittestException(f"Unsupported YAML data structure version {self._version} for file '{self._path}'.")
552 ex.add_note("Supported versions are: 1.0")
553 raise ex
555 self._name = self._yamlDocument["Name"]
556 buildInfo = self._ParseMapFromYAML(self._yamlDocument, "BuildInfo")
557 self._startTime = self._ParseDateFieldFromYAML(buildInfo, "StartTime")
558 self._totalDuration = self._ParseDurationFieldFromYAML(buildInfo, "ElapsedTime")
560 if "TestSuites" in self._yamlDocument:
561 for yamlTestsuite in self._ParseSequenceFromYAML(self._yamlDocument, "TestSuites"):
562 self._ConvertTestsuite(self, yamlTestsuite)
564 self.Aggregate()
566 self._modelConversion = sw.Duration
568 def _ConvertTestsuite(self, parentTestsuite: Testsuite, yamlTestsuite: CommentedMap) -> None:
569 testsuiteName = self._ParseStrFieldFromYAML(yamlTestsuite, "Name")
570 totalDuration = self._ParseDurationFieldFromYAML(yamlTestsuite, "ElapsedTime")
572 testsuite = Testsuite(
573 testsuiteName,
574 totalDuration=totalDuration,
575 parent=parentTestsuite
576 )
578 # if yamlTestsuite['TestCases'] is not None:
579 for yamlTestcase in self._ParseSequenceFromYAML(yamlTestsuite, 'TestCases'):
580 self._ConvertTestcase(testsuite, yamlTestcase)
582 def _ConvertTestcase(self, parentTestsuite: Testsuite, yamlTestcase: CommentedMap) -> None:
583 testcaseName = self._ParseStrFieldFromYAML(yamlTestcase, "TestCaseName")
584 totalDuration = self._ParseDurationFieldFromYAML(yamlTestcase, "ElapsedTime")
585 yamlStatus = self._ParseStrFieldFromYAML(yamlTestcase, "Status").lower()
586 yamlResults = self._ParseMapFromYAML(yamlTestcase, "Results")
587 assertionCount = self._ParseIntFieldFromYAML(yamlResults, "AffirmCount")
588 passedAssertionCount = self._ParseIntFieldFromYAML(yamlResults, "PassedCount")
590 totalErrors = self._ParseIntFieldFromYAML(yamlResults, "TotalErrors")
592 yamlAlertCount = self._ParseMapFromYAML(yamlResults, "AlertCount")
593 warningCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Warning")
594 errorCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Error")
595 failureCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Failure")
597 yamlDisabledAlertCount = self._ParseMapFromYAML(yamlResults, "DisabledAlertCount")
598 disabledWarningCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Warning")
599 disabledErrorCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Error")
600 disabledFailureCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Failure")
602 yamlExpectedAlertCount = self._ParseMapFromYAML(yamlResults, "ExpectedCount")
603 expectedWarningCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Warning")
604 expectedErrorCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Error")
605 expectedFailureCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Failure")
607 # FIXME: write a Parse classmethod in enum
608 if yamlStatus == "passed": 608 ↛ 610line 608 didn't jump to line 610 because the condition on line 608 was always true
609 status = TestcaseStatus.Passed
610 elif yamlStatus == "skipped":
611 status = TestcaseStatus.Skipped
612 elif yamlStatus == "failed":
613 status = TestcaseStatus.Failed
614 else:
615 status = TestcaseStatus.Unknown
617 if (
618 abs(warningDiff := warningCount - expectedWarningCount) +
619 abs(errorDiff := errorCount - expectedErrorCount) +
620 abs(failureDiff := failureCount - expectedFailureCount) -
621 totalErrors
622 ) == 0:
623 if warningDiff > 0: 623 ↛ 624line 623 didn't jump to line 624 because the condition on line 623 was never true
624 status |= TestcaseStatus.Warned
625 if errorDiff > 0: 625 ↛ 626line 625 didn't jump to line 626 because the condition on line 625 was never true
626 status |= TestcaseStatus.Errored
627 if failureDiff > 0: 627 ↛ 628line 627 didn't jump to line 628 because the condition on line 627 was never true
628 status |= TestcaseStatus.Aborted
629 else:
630 status |= TestcaseStatus.Inconsistent
632 _ = Testcase(
633 testcaseName,
634 totalDuration=totalDuration,
635 status=status,
636 assertionCount=assertionCount,
637 passedAssertionCount=passedAssertionCount,
638 warningCount=warningCount,
639 errorCount=errorCount,
640 fatalCount=failureCount,
641 disabledWarningCount=disabledWarningCount,
642 disabledErrorCount=disabledErrorCount,
643 disabledFatalCount=disabledFailureCount,
644 expectedWarningCount=expectedWarningCount,
645 expectedErrorCount=expectedErrorCount,
646 expectedFatalCount=expectedFailureCount,
647 parent=parentTestsuite
648 )
650 def __contains__(self, key: str) -> bool:
651 return key in self._testsuites
653 def __iter__(self) -> Iterator[Testsuite]:
654 return iter(self._testsuites.values())
656 def __getitem__(self, key: str) -> Testsuite:
657 return self._testsuites[key]
659 def __len__(self) -> int:
660 return self._testsuites.__len__()