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

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 

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 ) -> None: 

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 :returns: 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 :returns: 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 :returns: 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 :returns: 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 :returns: 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 :returns: 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 :returns: 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] #: Internal YAML document instance. 

369 _version: Nullable[SemanticVersion] #: YAML data structure version. 

370 

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

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

373 

374 self._yamlDocument = None 

375 self._version = None 

376 

377 Document.__init__(self, yamlReportFile, analyzeAndConvert) 

378 

379 @readonly 

380 def Version(self) -> Nullable[SemanticVersion]: 

381 """ 

382 Read-only property to access the YAML file's data structure version (:attr:`_version`). 

383 

384 :returns: The YAML data structures version. 

385 """ 

386 return self._version 

387 

388 def Analyze(self) -> None: 

389 """ 

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

391 

392 .. hint:: 

393 

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

399 

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 

406 

407 self._analysisDuration = sw.Duration 

408 

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. 

413 

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 

422 

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

426 

427 # if regenerate: 

428 # self.Generate(overwrite=True) 

429 

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 

434 

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

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

437 

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 

446 

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 

454 

455 return value 

456 

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 

465 

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 

474 

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 

483 

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

486 

487 return value 

488 

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 

497 

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

500 

501 return value 

502 

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 

511 

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

514 

515 return value 

516 

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 

525 

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

528 

529 return timedelta(seconds=value) 

530 

531 def Convert(self) -> None: 

532 """ 

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

534 

535 This method converts the root element. 

536 

537 .. hint:: 

538 

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

540 

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 

547 

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 

554 

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

559 

560 if "TestSuites" in self._yamlDocument: 

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

562 self._ConvertTestsuite(self, yamlTestsuite) 

563 

564 self.Aggregate() 

565 

566 self._modelConversion = sw.Duration 

567 

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

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

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

571 

572 testsuite = Testsuite( 

573 testsuiteName, 

574 totalDuration=totalDuration, 

575 parent=parentTestsuite 

576 ) 

577 

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

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

580 self._ConvertTestcase(testsuite, yamlTestcase) 

581 

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

589 

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

591 

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

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

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

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

596 

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

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

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

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

601 

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

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

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

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

606 

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 

616 

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 

631 

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 ) 

649 

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

651 return key in self._testsuites 

652 

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

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

655 

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

657 return self._testsuites[key] 

658 

659 def __len__(self) -> int: 

660 return self._testsuites.__len__()