Coverage for pyEDAA/OSVVM/Environment.py: 82%

282 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-10 07:04 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ___ ______ ____ ____ __ # 

3# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| # 

7# |_| |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2025-2025 Patrick Lehmann - Boetzingen, Germany # 

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# 

31from pathlib import Path 

32from typing import Optional as Nullable, List, Dict, Mapping, Iterable 

33 

34from pyTooling.Common import getFullyQualifiedName 

35from pyTooling.Decorators import readonly, export 

36from pyTooling.MetaClasses import ExtendedType 

37from pyVHDLModel import VHDLVersion 

38 

39from pyEDAA.OSVVM import OSVVMException 

40 

41 

42__all__ = ["osvvmContext"] 

43 

44 

45@export 

46class Base(metaclass=ExtendedType): 

47 pass 

48 

49 

50@export 

51class SourceFile(Base): 

52 """A base-class describing any source file (VHDL, Verilog, ...) supported by OSVVM Scripts.""" 

53 

54 _path: Path 

55 

56 def __init__( 

57 self, 

58 path: Path 

59 ) -> None: 

60 super().__init__() 

61 

62 if not isinstance(path, Path): # pragma: no cover 

63 ex = TypeError(f"Parameter 'path' is not a Path.") 

64 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.") 

65 raise ex 

66 

67 self._path = path 

68 

69 @readonly 

70 def Path(self) -> Path: 

71 return self._path 

72 

73 

74@export 

75class VHDLSourceFile(SourceFile): 

76 _vhdlVersion: VHDLVersion 

77 _vhdlLibrary: Nullable["VHDLLibrary"] 

78 

79 def __init__( 

80 self, 

81 path: Path, 

82 vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008, 

83 vhdlLibrary: Nullable["VHDLLibrary"] = None 

84 ): 

85 super().__init__(path) 

86 

87 if not isinstance(vhdlVersion, VHDLVersion): # pragma: no cover 

88 ex = TypeError(f"Parameter 'vhdlVersion' is not a VHDLVersion.") 

89 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlVersion)}'.") 

90 raise ex 

91 

92 self._vhdlVersion = vhdlVersion 

93 

94 if vhdlLibrary is None: 94 ↛ 96line 94 didn't jump to line 96 because the condition on line 94 was always true

95 self._vhdlLibrary = None 

96 elif isinstance(vhdlLibrary, VHDLLibrary): 

97 vhdlLibrary._files.append(self) 

98 self._vhdlLibrary = vhdlLibrary 

99 else: # pragma: no cover 

100 ex = TypeError(f"Parameter 'vhdlLibrary' is not a Library.") 

101 ex.add_note(f"Got type '{getFullyQualifiedName(vhdlLibrary)}'.") 

102 raise ex 

103 

104 @property 

105 def VHDLVersion(self) -> VHDLVersion: 

106 return self._vhdlVersion 

107 

108 @VHDLVersion.setter 

109 def VHDLVersion(self, value: VHDLVersion) -> None: 

110 self._vhdlVersion = value 

111 

112 @readonly 

113 def VHDLLibrary(self) -> Nullable["VHDLLibrary"]: 

114 return self._vhdlLibrary 

115 

116 

117@export 

118class VHDLLibrary(Base): 

119 """A VHDL library collecting multiple VHDL files containing VHDL design units.""" 

120 

121 _name: str 

122 _files: List[VHDLSourceFile] 

123 

124 def __init__(self, name: str) -> None: 

125 super().__init__() 

126 

127 if not isinstance(name, str): # pragma: no cover 

128 ex = TypeError(f"Parameter 'name' is not a string.") 

129 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

130 raise ex 

131 

132 self._name = name 

133 self._files = [] 

134 

135 @readonly 

136 def Name(self) -> str: 

137 return self._name 

138 

139 @readonly 

140 def Files(self) -> List[SourceFile]: 

141 return self._files 

142 

143 def AddFile(self, file: VHDLSourceFile) -> None: 

144 if not isinstance(file, VHDLSourceFile): # pragma: no cover 

145 ex = TypeError(f"Parameter 'file' is not a VHDLSourceFile.") 

146 ex.add_note(f"Got type '{getFullyQualifiedName(file)}'.") 

147 raise ex 

148 

149 file._vhdlLibrary = self 

150 self._files.append(file) 

151 

152 def __repr__(self) -> str: 

153 return f"VHDLLibrary: {self._name}" 

154 

155 

156@export 

157class GenericValue(Base): 

158 _name: str 

159 _value: str 

160 

161 def __init__(self, name: str, value: str) -> None: 

162 super().__init__() 

163 

164 if not isinstance(name, str): # pragma: no cover 

165 ex = TypeError(f"Parameter 'name' is not a string.") 

166 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

167 raise ex 

168 

169 if not isinstance(value, str): # pragma: no cover 

170 ex = TypeError(f"Parameter 'value' is not a string.") 

171 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

172 raise ex 

173 

174 self._name = name 

175 self._value = value 

176 

177 @readonly 

178 def Name(self) -> str: 

179 return self._name 

180 

181 @readonly 

182 def Value(self) -> str: 

183 return self._value 

184 

185 def __repr__(self) -> str: 

186 return f"{self._name} = {self._value}" 

187 

188 

189@export 

190class Testcase(Base): 

191 _name: str 

192 _toplevelName: Nullable[str] 

193 _generics: Dict[str, str] 

194 _testsuite: Nullable["Testsuite"] 

195 

196 def __init__( 

197 self, 

198 name: str, 

199 toplevelName: Nullable[str] = None, 

200 generics: Nullable[Iterable[GenericValue] | Mapping[str, str]] = None, 

201 testsuite: Nullable["Testsuite"] = None 

202 ) -> None: 

203 super().__init__() 

204 

205 if not isinstance(name, str): # pragma: no cover 

206 ex = TypeError(f"Parameter 'name' is not a string.") 

207 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

208 raise ex 

209 

210 self._name = name 

211 

212 if not (toplevelName is None or isinstance(toplevelName, str)): # pragma: no cover 

213 ex = TypeError(f"Parameter 'toplevelName' is not a string.") 

214 ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.") 

215 raise ex 

216 

217 self._toplevelName = toplevelName 

218 

219 self._generics = {} 

220 if generics is None: 220 ↛ 222line 220 didn't jump to line 222 because the condition on line 220 was always true

221 pass 

222 elif isinstance(generics, list): 

223 for item in generics: 

224 self._generics[item._name] = item._value 

225 elif isinstance(generics, dict): 

226 for key, value in generics.items(): 

227 self._generics[key] = value 

228 else: # pragma: no cover 

229 ex = TypeError(f"Parameter 'generics' is not a list of GenericValue nor a dictionary of strings.") 

230 ex.add_note(f"Got type '{getFullyQualifiedName(generics)}'.") 

231 raise ex 

232 

233 if testsuite is None: 233 ↛ 235line 233 didn't jump to line 235 because the condition on line 233 was always true

234 self._vhdlLibrary = None 

235 elif isinstance(testsuite, Testsuite): 

236 testsuite._testcases[name] = self 

237 self._testsuite = testsuite 

238 else: # pragma: no cover 

239 ex = TypeError(f"Parameter 'testsuite' is not a Testsuite.") 

240 ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.") 

241 raise ex 

242 

243 @readonly 

244 def Name(self) -> str: 

245 return self._name 

246 

247 @readonly 

248 def ToplevelName(self) -> str: 

249 return self._toplevelName 

250 

251 @readonly 

252 def Generics(self) -> Dict[str, str]: 

253 return self._generics 

254 

255 def SetToplevel(self, toplevelName: str) -> None: 

256 if not isinstance(toplevelName, str): # pragma: no cover 

257 ex = TypeError(f"Parameter 'toplevelName' is not a string.") 

258 ex.add_note(f"Got type '{getFullyQualifiedName(toplevelName)}'.") 

259 raise ex 

260 

261 self._toplevelName = toplevelName 

262 

263 def AddGeneric(self, genericValue: GenericValue): 

264 if not isinstance(genericValue, GenericValue): # pragma: no cover 

265 ex = TypeError(f"Parameter 'genericValue' is not a GenericValue.") 

266 ex.add_note(f"Got type '{getFullyQualifiedName(genericValue)}'.") 

267 raise ex 

268 

269 self._generics[genericValue._name] = genericValue._value 

270 

271 def __repr__(self) -> str: 

272 return f"Testcase: {self._name} - [{', '.join([f'{n}={v}' for n,v in self._generics.items()])}]" 

273 

274 

275@export 

276class Testsuite(Base): 

277 _name: str 

278 _testcases: Dict[str, Testcase] 

279 

280 def __init__( 

281 self, 

282 name: str, 

283 testcases: Nullable[Iterable[Testcase] | Mapping[str, Testcase]] = None 

284 ) -> None: 

285 super().__init__() 

286 

287 if not isinstance(name, str): # pragma: no cover 

288 ex = TypeError(f"Parameter 'name' is not a string.") 

289 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

290 raise ex 

291 

292 self._name = name 

293 

294 self._testcases = {} 

295 if testcases is None: 295 ↛ 297line 295 didn't jump to line 297 because the condition on line 295 was always true

296 pass 

297 elif isinstance(testcases, list): 

298 for item in testcases: 

299 item._testsuite = self 

300 self._testcases[item._name] = item 

301 elif isinstance(testcases, dict): 

302 for key, value in testcases.items(): 

303 value._testsuite = self 

304 self._testcases[key] = value 

305 else: # pragma: no cover 

306 ex = TypeError(f"Parameter 'testcases' is not a list of Testcase nor a mapping of Testcase.") 

307 ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.") 

308 raise ex 

309 

310 @readonly 

311 def Name(self) -> str: 

312 return self._name 

313 

314 @readonly 

315 def Testcases(self) -> Dict[str, Testcase]: 

316 return self._testcases 

317 

318 def AddTestcase(self, testcase: Testcase) -> None: 

319 if not isinstance(testcase, Testcase): # pragma: no cover 

320 ex = TypeError(f"Parameter 'testcase' is not a Testcase.") 

321 ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.") 

322 raise ex 

323 

324 testcase._testsuite = self 

325 self._testcases[testcase._name] = testcase 

326 

327 def __repr__(self) -> str: 

328 return f"Testsuite: {self._name}" 

329 

330 

331@export 

332class Context(Base): 

333 # _tcl: TclEnvironment 

334 

335 _lastException: Exception 

336 

337 _workingDirectory: Path 

338 _currentDirectory: Path 

339 _includedFiles: List[Path] 

340 

341 _vhdlversion: VHDLVersion 

342 

343 _libraries: Dict[str, VHDLLibrary] 

344 _library: Nullable[VHDLLibrary] 

345 

346 _testsuites: Dict[str, Testsuite] 

347 _testsuite: Nullable[Testsuite] 

348 _testcase: Nullable[Testcase] 

349 _options: Dict[int, GenericValue] 

350 

351 def __init__(self) -> None: 

352 super().__init__() 

353 

354 self._processor = None 

355 self._lastException = None 

356 

357 self._workingDirectory = Path.cwd() 

358 self._currentDirectory = self._workingDirectory 

359 self._includedFiles = [] 

360 

361 self._vhdlversion = VHDLVersion.VHDL2008 

362 

363 self._library = None 

364 self._libraries = {} 

365 

366 self._testcase = None 

367 self._testsuite = None 

368 self._testsuites = {} 

369 self._options = {} 

370 

371 def Clear(self) -> None: 

372 self._processor = None 

373 self._lastException = None 

374 

375 self._workingDirectory = Path.cwd() 

376 self._currentDirectory = self._workingDirectory 

377 self._includedFiles = [] 

378 

379 self._vhdlversion = VHDLVersion.VHDL2008 

380 

381 self._library = None 

382 self._libraries = {} 

383 

384 self._testcase = None 

385 self._testsuite = None 

386 self._testsuites = {} 

387 self._options = {} 

388 

389 @readonly 

390 def Processor(self): # -> "Tk": 

391 return self._processor 

392 

393 @property 

394 def LastException(self) -> Exception: 

395 lastException = self._lastException 

396 self._lastException = None 

397 return lastException 

398 

399 @LastException.setter 

400 def LastException(self, value: Exception) -> None: 

401 self._lastException = value 

402 

403 @readonly 

404 def WorkingDirectory(self) -> Path: 

405 return self._workingDirectory 

406 

407 @readonly 

408 def CurrentDirectory(self) -> Path: 

409 return self._currentDirectory 

410 

411 @property 

412 def VHDLVersion(self) -> VHDLVersion: 

413 return self._vhdlversion 

414 

415 @VHDLVersion.setter 

416 def VHDLVersion(self, value: VHDLVersion) -> None: 

417 self._vhdlversion = value 

418 

419 @readonly 

420 def IncludedFiles(self) -> List[Path]: 

421 return self._includedFiles 

422 

423 @readonly 

424 def Libraries(self) -> Dict[str, VHDLLibrary]: 

425 return self._libraries 

426 

427 @readonly 

428 def Library(self) -> VHDLLibrary: 

429 return self._library 

430 

431 @readonly 

432 def Testsuites(self) -> Dict[str, Testsuite]: 

433 return self._testsuites 

434 

435 @readonly 

436 def Testsuite(self) -> Testsuite: 

437 return self._testsuite 

438 

439 @readonly 

440 def TestCase(self) -> Testcase: 

441 return self._testcase 

442 

443 def IncludeFile(self, proFileOrBuildDirectory: Path) -> Path: 

444 if not isinstance(proFileOrBuildDirectory, Path): # pragma: no cover 

445 ex = TypeError(f"Parameter 'proFileOrBuildDirectory' is not a Path.") 

446 ex.add_note(f"Got type '{getFullyQualifiedName(proFileOrBuildDirectory)}'.") 

447 self._lastException = ex 

448 raise ex 

449 

450 if proFileOrBuildDirectory.is_absolute(): 450 ↛ 451line 450 didn't jump to line 451 because the condition on line 450 was never true

451 ex = OSVVMException(f"Absolute path '{proFileOrBuildDirectory}' not supported.") 

452 self._lastException = ex 

453 raise ex 

454 

455 path = (self._currentDirectory / proFileOrBuildDirectory).resolve() 

456 if path.is_file(): 

457 if path.suffix == ".pro": 457 ↛ 461line 457 didn't jump to line 461 because the condition on line 457 was always true

458 self._currentDirectory = path.parent.relative_to(self._workingDirectory, walk_up=True) 

459 proFile = self._currentDirectory / path.name 

460 else: 

461 ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file.") 

462 self._lastException = ex 

463 raise ex 

464 elif path.is_dir(): 

465 self._currentDirectory = path 

466 proFile = path / "build.pro" 

467 if not proFile.exists(): 

468 proFile = path / f"{path.name}.pro" 

469 if not proFile.exists(): # pragma: no cover 

470 ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a build directory.") 

471 ex.__cause__ = FileNotFoundError(path / "build.pro") 

472 self._lastException = ex 

473 raise ex 

474 else: # pragma: no cover 

475 ex = OSVVMException(f"Path '{proFileOrBuildDirectory}' is not a *.pro file or build directory.") 

476 self._lastException = ex 

477 raise ex 

478 

479 self._includedFiles.append(proFile) 

480 return proFile 

481 

482 def EvaluateFile(self, proFile: Path) -> None: 

483 self._processor.EvaluateProFile(proFile) 

484 

485 def SetLibrary(self, name: str): 

486 try: 

487 self._library = self._libraries[name] 

488 except KeyError: 

489 self._library = VHDLLibrary(name) 

490 self._libraries[name] = self._library 

491 

492 def AddVHDLFile(self, vhdlFile: VHDLSourceFile) -> None: 

493 if self._library is None: 

494 self.SetLibrary("default") 

495 

496 vhdlFile.VHDLVersion = self._vhdlversion 

497 self._library.AddFile(vhdlFile) 

498 

499 def SetTestsuite(self, testsuiteName: str): 

500 try: 

501 self._testsuite = self._testsuites[testsuiteName] 

502 except KeyError: 

503 self._testsuite = Testsuite(testsuiteName) 

504 self._testsuites[testsuiteName] = self._testsuite 

505 

506 def AddTestcase(self, testName: str) -> TestCase: 

507 if self._testsuite is None: 507 ↛ 510line 507 didn't jump to line 510 because the condition on line 507 was always true

508 self.SetTestsuite("default") 

509 

510 self._testcase = Testcase(testName) 

511 self._testsuite._testcases[testName] = self._testcase 

512 

513 return self._testcase 

514 

515 def SetTestcaseToplevel(self, toplevel: str) -> TestCase: 

516 if self._testcase is None: 516 ↛ 517line 516 didn't jump to line 517 because the condition on line 516 was never true

517 ex = OSVVMException("Can't set testcase toplevel, because no testcase was setup.") 

518 self._lastException = ex 

519 raise ex 

520 

521 self._testcase.SetToplevel(toplevel) 

522 

523 return self._testcase 

524 

525 def AddOption(self, genericValue: GenericValue): 

526 optionID = id(genericValue) 

527 self._options[optionID] = genericValue 

528 

529 return optionID 

530 

531 

532osvvmContext: Context = Context()