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

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 

35 

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 

42 

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 

46 

47 

48@export 

49class OsvvmException: 

50 pass 

51 

52 

53@export 

54@InheritDocString(UnittestException) 

55class UnittestException(UnittestException, OsvvmException): 

56 """@InheritDocString(UnittestException)""" 

57 

58 

59@export 

60@InheritDocString(ut_Testcase) 

61class Testcase(ut_Testcase): 

62 """@InheritDocString(ut_Testcase)""" 

63 

64 _disabledWarningCount: int 

65 _disabledErrorCount: int 

66 _disabledFatalCount: int 

67 

68 _requirementsCount: Nullable[int] 

69 _passedRequirementsCount: Nullable[int] 

70 _failedRequirementsCount: Nullable[int] 

71 _functionalCoverage: Nullable[float] 

72 

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. 

103 

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 ) 

141 

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 

146 

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 

151 

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 

156 

157 self._disabledWarningCount = disabledWarningCount 

158 self._disabledErrorCount = disabledErrorCount 

159 self._disabledFatalCount = disabledFatalCount 

160 

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 

165 

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 

170 

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 

175 

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 

198 

199 self._requirementsCount = requirementsCount 

200 self._passedRequirementsCount = passedRequirementsCount 

201 self._failedRequirementsCount = failedRequirementsCount 

202 

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 

208 

209 if not (0.0 <= functionalCoverage <= 1.0): 

210 raise ValueError(f"Parameter 'functionalCoverage' is not in range 0.0..1.0.") 

211 

212 self._functionalCoverage = functionalCoverage 

213 

214 @readonly 

215 def DisabledWarningCount(self) -> int: 

216 """ 

217 Read-only property returning the number of disabled warnings. 

218 

219 :return: Count of disabled warnings. 

220 """ 

221 return self._disabledWarningCount 

222 

223 @readonly 

224 def DisabledErrorCount(self) -> int: 

225 """ 

226 Read-only property returning the number of disabled errors. 

227 

228 :return: Count of disabled errors. 

229 """ 

230 return self._disabledErrorCount 

231 

232 @readonly 

233 def DisabledFatalCount(self) -> int: 

234 """ 

235 Read-only property returning the number of disabled fatal errors. 

236 

237 :return: Count of disabled fatal errors. 

238 """ 

239 return self._disabledFatalCount 

240 

241 @readonly 

242 def RequirementsCount(self) -> int: 

243 """ 

244 Read-only property returning the number of requirements. 

245 

246 :return: Count of requirements. 

247 """ 

248 return self._requirementsCount 

249 

250 @readonly 

251 def PassedRequirementsCount(self) -> int: 

252 """ 

253 Read-only property returning the number of passed requirements. 

254 

255 :return: Count of passed rerquirements. 

256 """ 

257 return self._passedRequirementsCount 

258 

259 @readonly 

260 def FailedRequirementsCount(self) -> int: 

261 """ 

262 Read-only property returning the number of failed requirements. 

263 

264 :return: Count of failed requirements. 

265 """ 

266 return self._failedRequirementsCount 

267 

268 @readonly 

269 def FunctionalCoverage(self) -> float: 

270 """ 

271 Read-only property returning the functional coverage. 

272 

273 :return: Percentage of functional coverage. 

274 """ 

275 return self._functionalCoverage 

276 

277 

278@export 

279@InheritDocString(ut_Testsuite) 

280class Testsuite(ut_Testsuite): 

281 """@InheritDocString(ut_Testsuite)""" 

282 

283 

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 

295 

296 def __init__(self) -> None: 

297 pass 

298 

299 

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] 

310 

311 def __init__(self) -> None: 

312 pass 

313 

314 

315@export 

316@InheritDocString(ut_TestsuiteSummary) 

317class TestsuiteSummary(ut_TestsuiteSummary): 

318 """@InheritDocString(ut_TestsuiteSummary)""" 

319 

320 _datetime: datetime 

321 

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. 

340 

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 ) 

364 

365 

366@export 

367class BuildSummaryDocument(TestsuiteSummary, Document): 

368 _yamlDocument: Nullable[YAML] 

369 

370 def __init__(self, yamlReportFile: Path, analyzeAndConvert: bool = False) -> None: 

371 super().__init__("Unprocessed OSVVM YAML file") 

372 

373 self._yamlDocument = None 

374 

375 Document.__init__(self, yamlReportFile, analyzeAndConvert) 

376 

377 def Analyze(self) -> None: 

378 """ 

379 Analyze the YAML file, parse the content into an YAML data structure. 

380 

381 .. hint:: 

382 

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

388 

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 

395 

396 self._analysisDuration = sw.Duration 

397 

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. 

402 

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 

411 

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

415 

416 # if regenerate: 

417 # self.Generate(overwrite=True) 

418 

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 

423 

424 # with path.open("w", encoding="utf-8") as file: 

425 # self._yamlDocument.writexml(file, addindent="\t", encoding="utf-8", newl="\n") 

426 

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 

435 

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 

443 

444 return value 

445 

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 

454 

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 

463 

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 

472 

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

475 

476 return value 

477 

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 

486 

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

489 

490 return value 

491 

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 

500 

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

503 

504 return value 

505 

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 

514 

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

517 

518 return timedelta(seconds=value) 

519 

520 def Convert(self) -> None: 

521 """ 

522 Convert the parsed YAML data structure into a test entity hierarchy. 

523 

524 This method converts the root element. 

525 

526 .. hint:: 

527 

528 The time spend for model conversion will be made available via property :data:`ModelConversionDuration`. 

529 

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 

536 

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

542 

543 if "TestSuites" in self._yamlDocument: 

544 for yamlTestsuite in self._ParseSequenceFromYAML(self._yamlDocument, "TestSuites"): 

545 self._ConvertTestsuite(self, yamlTestsuite) 

546 

547 self.Aggregate() 

548 

549 self._modelConversion = sw.Duration 

550 

551 def _ConvertTestsuite(self, parentTestsuite: Testsuite, yamlTestsuite: CommentedMap) -> None: 

552 testsuiteName = self._ParseStrFieldFromYAML(yamlTestsuite, "Name") 

553 totalDuration = self._ParseDurationFieldFromYAML(yamlTestsuite, "ElapsedTime") 

554 

555 testsuite = Testsuite( 

556 testsuiteName, 

557 totalDuration=totalDuration, 

558 parent=parentTestsuite 

559 ) 

560 

561 # if yamlTestsuite['TestCases'] is not None: 

562 for yamlTestcase in self._ParseSequenceFromYAML(yamlTestsuite, 'TestCases'): 

563 self._ConvertTestcase(testsuite, yamlTestcase) 

564 

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

572 

573 totalErrors = self._ParseIntFieldFromYAML(yamlResults, "TotalErrors") 

574 

575 yamlAlertCount = self._ParseMapFromYAML(yamlResults, "AlertCount") 

576 warningCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Warning") 

577 errorCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Error") 

578 failureCount = self._ParseIntFieldFromYAML(yamlAlertCount, "Failure") 

579 

580 yamlDisabledAlertCount = self._ParseMapFromYAML(yamlResults, "DisabledAlertCount") 

581 disabledWarningCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Warning") 

582 disabledErrorCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Error") 

583 disabledFailureCount = self._ParseIntFieldFromYAML(yamlDisabledAlertCount, "Failure") 

584 

585 yamlExpectedAlertCount = self._ParseMapFromYAML(yamlResults, "ExpectedCount") 

586 expectedWarningCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Warning") 

587 expectedErrorCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Error") 

588 expectedFailureCount = self._ParseIntFieldFromYAML(yamlExpectedAlertCount, "Failure") 

589 

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 

599 

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 

614 

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 ) 

632 

633 def __contains__(self, key: str) -> bool: 

634 return key in self._testsuites 

635 

636 def __iter__(self) -> Iterator[Testsuite]: 

637 return iter(self._testsuites.values()) 

638 

639 def __getitem__(self, key: str) -> Testsuite: 

640 return self._testsuites[key] 

641 

642 def __len__(self) -> int: 

643 return self._testsuites.__len__()