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
« 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
34from pyTooling.Common import getFullyQualifiedName
35from pyTooling.Decorators import readonly, export
36from pyTooling.MetaClasses import ExtendedType
37from pyVHDLModel import VHDLVersion
39from pyEDAA.OSVVM import OSVVMException
42__all__ = ["osvvmContext"]
45@export
46class Base(metaclass=ExtendedType):
47 pass
50@export
51class SourceFile(Base):
52 """A base-class describing any source file (VHDL, Verilog, ...) supported by OSVVM Scripts."""
54 _path: Path
56 def __init__(
57 self,
58 path: Path
59 ) -> None:
60 super().__init__()
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
67 self._path = path
69 @readonly
70 def Path(self) -> Path:
71 return self._path
74@export
75class VHDLSourceFile(SourceFile):
76 _vhdlVersion: VHDLVersion
77 _vhdlLibrary: Nullable["VHDLLibrary"]
79 def __init__(
80 self,
81 path: Path,
82 vhdlVersion: VHDLVersion = VHDLVersion.VHDL2008,
83 vhdlLibrary: Nullable["VHDLLibrary"] = None
84 ):
85 super().__init__(path)
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
92 self._vhdlVersion = vhdlVersion
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
104 @property
105 def VHDLVersion(self) -> VHDLVersion:
106 return self._vhdlVersion
108 @VHDLVersion.setter
109 def VHDLVersion(self, value: VHDLVersion) -> None:
110 self._vhdlVersion = value
112 @readonly
113 def VHDLLibrary(self) -> Nullable["VHDLLibrary"]:
114 return self._vhdlLibrary
117@export
118class VHDLLibrary(Base):
119 """A VHDL library collecting multiple VHDL files containing VHDL design units."""
121 _name: str
122 _files: List[VHDLSourceFile]
124 def __init__(self, name: str) -> None:
125 super().__init__()
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
132 self._name = name
133 self._files = []
135 @readonly
136 def Name(self) -> str:
137 return self._name
139 @readonly
140 def Files(self) -> List[SourceFile]:
141 return self._files
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
149 file._vhdlLibrary = self
150 self._files.append(file)
152 def __repr__(self) -> str:
153 return f"VHDLLibrary: {self._name}"
156@export
157class GenericValue(Base):
158 _name: str
159 _value: str
161 def __init__(self, name: str, value: str) -> None:
162 super().__init__()
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
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
174 self._name = name
175 self._value = value
177 @readonly
178 def Name(self) -> str:
179 return self._name
181 @readonly
182 def Value(self) -> str:
183 return self._value
185 def __repr__(self) -> str:
186 return f"{self._name} = {self._value}"
189@export
190class Testcase(Base):
191 _name: str
192 _toplevelName: Nullable[str]
193 _generics: Dict[str, str]
194 _testsuite: Nullable["Testsuite"]
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__()
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
210 self._name = name
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
217 self._toplevelName = toplevelName
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
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
243 @readonly
244 def Name(self) -> str:
245 return self._name
247 @readonly
248 def ToplevelName(self) -> str:
249 return self._toplevelName
251 @readonly
252 def Generics(self) -> Dict[str, str]:
253 return self._generics
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
261 self._toplevelName = toplevelName
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
269 self._generics[genericValue._name] = genericValue._value
271 def __repr__(self) -> str:
272 return f"Testcase: {self._name} - [{', '.join([f'{n}={v}' for n,v in self._generics.items()])}]"
275@export
276class Testsuite(Base):
277 _name: str
278 _testcases: Dict[str, Testcase]
280 def __init__(
281 self,
282 name: str,
283 testcases: Nullable[Iterable[Testcase] | Mapping[str, Testcase]] = None
284 ) -> None:
285 super().__init__()
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
292 self._name = name
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
310 @readonly
311 def Name(self) -> str:
312 return self._name
314 @readonly
315 def Testcases(self) -> Dict[str, Testcase]:
316 return self._testcases
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
324 testcase._testsuite = self
325 self._testcases[testcase._name] = testcase
327 def __repr__(self) -> str:
328 return f"Testsuite: {self._name}"
331@export
332class Context(Base):
333 # _tcl: TclEnvironment
335 _lastException: Exception
337 _workingDirectory: Path
338 _currentDirectory: Path
339 _includedFiles: List[Path]
341 _vhdlversion: VHDLVersion
343 _libraries: Dict[str, VHDLLibrary]
344 _library: Nullable[VHDLLibrary]
346 _testsuites: Dict[str, Testsuite]
347 _testsuite: Nullable[Testsuite]
348 _testcase: Nullable[Testcase]
349 _options: Dict[int, GenericValue]
351 def __init__(self) -> None:
352 super().__init__()
354 self._processor = None
355 self._lastException = None
357 self._workingDirectory = Path.cwd()
358 self._currentDirectory = self._workingDirectory
359 self._includedFiles = []
361 self._vhdlversion = VHDLVersion.VHDL2008
363 self._library = None
364 self._libraries = {}
366 self._testcase = None
367 self._testsuite = None
368 self._testsuites = {}
369 self._options = {}
371 def Clear(self) -> None:
372 self._processor = None
373 self._lastException = None
375 self._workingDirectory = Path.cwd()
376 self._currentDirectory = self._workingDirectory
377 self._includedFiles = []
379 self._vhdlversion = VHDLVersion.VHDL2008
381 self._library = None
382 self._libraries = {}
384 self._testcase = None
385 self._testsuite = None
386 self._testsuites = {}
387 self._options = {}
389 @readonly
390 def Processor(self): # -> "Tk":
391 return self._processor
393 @property
394 def LastException(self) -> Exception:
395 lastException = self._lastException
396 self._lastException = None
397 return lastException
399 @LastException.setter
400 def LastException(self, value: Exception) -> None:
401 self._lastException = value
403 @readonly
404 def WorkingDirectory(self) -> Path:
405 return self._workingDirectory
407 @readonly
408 def CurrentDirectory(self) -> Path:
409 return self._currentDirectory
411 @property
412 def VHDLVersion(self) -> VHDLVersion:
413 return self._vhdlversion
415 @VHDLVersion.setter
416 def VHDLVersion(self, value: VHDLVersion) -> None:
417 self._vhdlversion = value
419 @readonly
420 def IncludedFiles(self) -> List[Path]:
421 return self._includedFiles
423 @readonly
424 def Libraries(self) -> Dict[str, VHDLLibrary]:
425 return self._libraries
427 @readonly
428 def Library(self) -> VHDLLibrary:
429 return self._library
431 @readonly
432 def Testsuites(self) -> Dict[str, Testsuite]:
433 return self._testsuites
435 @readonly
436 def Testsuite(self) -> Testsuite:
437 return self._testsuite
439 @readonly
440 def TestCase(self) -> Testcase:
441 return self._testcase
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
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
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
479 self._includedFiles.append(proFile)
480 return proFile
482 def EvaluateFile(self, proFile: Path) -> None:
483 self._processor.EvaluateProFile(proFile)
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
492 def AddVHDLFile(self, vhdlFile: VHDLSourceFile) -> None:
493 if self._library is None:
494 self.SetLibrary("default")
496 vhdlFile.VHDLVersion = self._vhdlversion
497 self._library.AddFile(vhdlFile)
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
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")
510 self._testcase = Testcase(testName)
511 self._testsuite._testcases[testName] = self._testcase
513 return self._testcase
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
521 self._testcase.SetToplevel(toplevel)
523 return self._testcase
525 def AddOption(self, genericValue: GenericValue):
526 optionID = id(genericValue)
527 self._options[optionID] = genericValue
529 return optionID
532osvvmContext: Context = Context()