Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/news.d/1234.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The simulator hooks registered by the packages of a VUnit object are no longer lost when another VUnit object is created.
4 changes: 3 additions & 1 deletion docs/vunit_packages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ and use these dependencies.
Use ``context.register_simulator_hooks(simulator_name, ...)`` to register
functions that supply these settings for a particular simulator. Hooks
receive the simulator interface, so they can adapt their results to its
configuration. They apply only to the project in which they are registered.
configuration. They apply only to the project in which they are registered,
that is to the tests of the VUnit object the package is added to. Creating
another VUnit object neither removes them nor gives them to the new object.

The following example builds a native library and registers hooks to load
it with NVC or link it with GHDL:
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/test_builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from vunit.vhdl_standard import VHDL, VHDLStandard
from vunit.project import Project
from vunit.sim_if import hooks
from vunit.sim_if.hooks import SimulatorHooks
from tests.common import create_tempdir
from contextlib import contextmanager
from importlib.machinery import ModuleSpec
Expand Down Expand Up @@ -64,6 +65,7 @@ def add_library(name):

self.vu._output_path = "output_path"
self.vu._run_script_path = Path("run.py")
self.vu._simulator_hooks = SimulatorHooks()

self.builtins = Builtins(self.vu, VHDLStandard("2002"), None)

Expand Down Expand Up @@ -728,8 +730,6 @@ def setup(context):
self.assertEqual(context.simulator_backend, backend)

def test_setup_function_registers_simulator_hooks(self):
self.addCleanup(hooks.clear_hooks)

with (
create_tempdir() as tempdir,
pkg_env(tempdir),
Expand Down Expand Up @@ -760,6 +760,7 @@ def setup(context):

simulator_interface = mock.Mock()
simulator_interface.name = "ghdl"
simulator_interface.simulator_hooks = self.vu._simulator_hooks
self.assertEqual(hooks.get_flags(simulator_interface, "elab_flags"), ["-Wl,-lfoo"])
self.assertEqual(hooks.get_flags(simulator_interface, "run_flags"), ["--load=foo"])
self.assertEqual(hooks.get_flags(simulator_interface, "process_flags"), ["-noautoldlibpath"])
Expand Down
132 changes: 82 additions & 50 deletions tests/unit/test_sim_if_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

from vunit import VUnit
from vunit.project import Project
from vunit.sim_if import hooks
from vunit.sim_if import SimulatorInterface, hooks
from vunit.sim_if.hooks import SimulatorHooks
from vunit.sim_if.activehdl import ActiveHDLInterface
from vunit.sim_if.ghdl import GHDLInterface
from vunit.sim_if.modelsim import ModelSimInterface
Expand Down Expand Up @@ -51,14 +52,10 @@ class TestSimulatorHooks(unittest.TestCase):
"""

def setUp(self):
hooks.clear_hooks()
self.hooks = SimulatorHooks()

def tearDown(self):
hooks.clear_hooks()

@staticmethod
def _simulator(name):
return SimpleNamespace(name=name)
def _simulator(self, name):
return SimpleNamespace(name=name, simulator_hooks=self.hooks)

def test_no_hooks_by_default(self):
simulator = self._simulator("ghdl")
Expand All @@ -69,7 +66,7 @@ def test_no_hooks_by_default(self):

def test_calls_hooks_with_the_simulator_interface(self):
simulator = self._simulator("ghdl")
hooks.register_hooks(
self.hooks.register(
"ghdl",
elab_flags=lambda interface: [f"-Wl,-l{interface.name}"],
run_flags=lambda interface: ["--load"],
Expand All @@ -85,54 +82,89 @@ def test_calls_hooks_with_the_simulator_interface(self):
def test_combines_hooks_in_registration_order(self):
simulator = self._simulator("ghdl")
for kind in FLAG_KINDS:
hooks.register_hooks("ghdl", **{kind: lambda interface: ["first"]})
hooks.register_hooks("ghdl", **{kind: lambda interface: ["second"]})
self.hooks.register("ghdl", **{kind: lambda interface: ["first"]})
self.hooks.register("ghdl", **{kind: lambda interface: ["second"]})

self.assertEqual(hooks.get_flags(simulator, kind), ["first", "second"])

def test_hooks_are_specific_to_a_simulator(self):
for kind in FLAG_KINDS:
hooks.register_hooks("ghdl", **{kind: lambda interface: ["--load"]})
self.hooks.register("ghdl", **{kind: lambda interface: ["--load"]})

self.assertEqual(hooks.get_flags(self._simulator("ghdl"), kind), ["--load"])
self.assertEqual(hooks.get_flags(self._simulator("nvc"), kind), [])

def test_copies_the_environment_when_there_is_none(self):
simulator = self._simulator("ghdl")
hooks.register_hooks("ghdl", run_env=lambda interface, env: dict(env, FOO="1"))
self.hooks.register("ghdl", run_env=lambda interface, env: dict(env, FOO="1"))

env = hooks.get_run_env(simulator)
self.assertEqual(env["FOO"], "1")
self.assertIn("PATH", env)

def test_clears_hooks(self):
simulator = self._simulator("ghdl")
hooks.register_hooks("ghdl", elab_flags=lambda interface: ["--load"])
hooks.clear_hooks()

self.assertEqual(hooks.get_flags(simulator, "elab_flags"), [])

def test_raises_if_hook_is_not_callable(self):
with self.assertRaisesRegex(ValueError, "run_flags hook for simulator ghdl is not callable."):
hooks.register_hooks("ghdl", run_flags="--load")
self.hooks.register("ghdl", run_flags="--load")

with self.assertRaisesRegex(ValueError, "process_flags hook for simulator ghdl is not callable."):
hooks.register_hooks("ghdl", process_flags="-noautoldlibpath")
self.hooks.register("ghdl", process_flags="-noautoldlibpath")

def test_raises_if_simulator_name_is_invalid(self):
with self.assertRaisesRegex(ValueError, "Simulator name must be a non-empty string."):
hooks.register_hooks("", run_flags=lambda interface: [])
self.hooks.register("", run_flags=lambda interface: [])

def test_hooks_are_cleared_when_a_vunit_object_is_created(self):
hooks.register_hooks("ghdl", elab_flags=lambda interface: ["--load"])
def test_no_hooks_for_an_interface_not_created_by_a_vunit_object(self):
self.hooks.register("ghdl", elab_flags=lambda interface: ["--load"])
simulator = SimpleNamespace(name="ghdl")

self.assertEqual(hooks.get_flags(simulator, "elab_flags"), [])
self.assertEqual(hooks.get_run_env(simulator, {"FOO": "1"}), {"FOO": "1"})

with (
create_tempdir() as tempdir,
mock.patch("vunit.sim_if.factory.SIMULATOR_FACTORY.select_simulator", new=lambda: None),
):
VUnit.from_argv(argv=["--output-path=%s" % tempdir])

self.assertEqual(hooks.get_flags(self._simulator("ghdl"), "elab_flags"), [])
class _FakeSimulator(SimulatorInterface):
"""
A simulator class creating a plain interface
"""

name = "ghdl"

@classmethod
def from_args(cls, args, output_path): # pylint: disable=unused-argument
return SimpleNamespace(name=cls.name)


class TestSimulatorHooksOfVUnitObjects(unittest.TestCase):
"""
Test that the hooks belong to the VUnit object of the packages registering them
"""

@staticmethod
def _vunit(output_path):
with mock.patch("vunit.sim_if.factory.SIMULATOR_FACTORY.select_simulator", new=lambda: _FakeSimulator):
return VUnit.from_argv(argv=[f"--output-path={output_path!s}"])

def test_simulator_interface_gets_the_hooks_of_its_vunit_object(self):
with create_tempdir() as tempdir:
vu = self._vunit(tempdir)
vu._simulator_hooks.register( # pylint: disable=protected-access
"ghdl", elab_flags=lambda interface: ["--load"]
)
simulator_if = vu._create_simulator_if() # pylint: disable=protected-access

self.assertEqual(hooks.get_flags(simulator_if, "elab_flags"), ["--load"])

def test_hooks_are_kept_when_another_vunit_object_is_created(self):
with create_tempdir() as tempdir:
first = self._vunit(Path(tempdir) / "first")
first._simulator_hooks.register( # pylint: disable=protected-access
"ghdl", elab_flags=lambda interface: ["--load"]
)
second = self._vunit(Path(tempdir) / "second")
first_if = first._create_simulator_if() # pylint: disable=protected-access
second_if = second._create_simulator_if() # pylint: disable=protected-access

self.assertEqual(hooks.get_flags(first_if, "elab_flags"), ["--load"])
self.assertEqual(hooks.get_flags(second_if, "elab_flags"), [])


class TestSimulatorHooksAreUsed(unittest.TestCase):
Expand All @@ -141,10 +173,7 @@ class TestSimulatorHooksAreUsed(unittest.TestCase):
"""

def setUp(self):
hooks.clear_hooks()

def tearDown(self):
hooks.clear_hooks()
self.hooks = SimulatorHooks()

@staticmethod
def _project():
Expand All @@ -154,14 +183,15 @@ def _project():

@mock.patch.object(GHDLInterface, "determine_version", return_value=5.0)
def test_ghdl_uses_hooks(self, determine_version):
hooks.register_hooks(
self.hooks.register(
"ghdl",
elab_flags=lambda interface: ["-Wl,-lfoo"],
run_flags=lambda interface: ["--load=foo"],
run_env=lambda interface, env: dict(env, FOO="1"),
)

simif = GHDLInterface(prefix="prefix", output_path="")
simif.simulator_hooks = self.hooks
simif._project = self._project() # pylint: disable=protected-access
simif._vhdl_standard = VHDL.standard("2008") # pylint: disable=protected-access

Expand All @@ -182,14 +212,15 @@ def test_ghdl_uses_hooks(self, determine_version):

@mock.patch.object(NVCInterface, "determine_version", return_value=(1, 16))
def test_nvc_uses_hooks(self, determine_version):
hooks.register_hooks(
self.hooks.register(
"nvc",
elab_flags=lambda interface: ["--jit"],
run_flags=lambda interface: ["--load=foo"],
run_env=lambda interface, env: dict(env, FOO="1"),
)

simif = NVCInterface(prefix="prefix", output_path="", num_threads=1)
simif.simulator_hooks = self.hooks
simif._project = self._project() # pylint: disable=protected-access
simif._vhdl_standard = VHDL.standard("2008") # pylint: disable=protected-access

Expand All @@ -207,26 +238,26 @@ def test_nvc_uses_hooks(self, determine_version):
self.assertEqual(process.call_args[1]["env"]["FOO"], "1")

def test_modelsim_uses_hooks(self):
hooks.register_hooks(
self.hooks.register(
"modelsim",
elab_flags=lambda interface: ["-foo"],
run_flags=lambda interface: ["-bar"],
)

simif = SimpleNamespace(name=ModelSimInterface.name, _gui=False)
simif = SimpleNamespace(name=ModelSimInterface.name, _gui=False, simulator_hooks=self.hooks)
self.assertEqual(ModelSimInterface._vopt_extra_args(simif, make_config()), "-foo")
self.assertEqual(ModelSimInterface._vsim_extra_args(simif, make_config()), "-bar")

def test_rivierapro_uses_hooks(self):
hooks.register_hooks("rivierapro", run_flags=lambda interface: ["-bar"])
self.hooks.register("rivierapro", run_flags=lambda interface: ["-bar"])

simif = SimpleNamespace(name=RivieraProInterface.name, _gui=False)
simif = SimpleNamespace(name=RivieraProInterface.name, _gui=False, simulator_hooks=self.hooks)
self.assertEqual(RivieraProInterface._vsim_extra_args(simif, make_config()), "-bar")

def test_activehdl_uses_hooks(self):
hooks.register_hooks("activehdl", run_flags=lambda interface: ["-bar"])
self.hooks.register("activehdl", run_flags=lambda interface: ["-bar"])

simif = SimpleNamespace(name=ActiveHDLInterface.name, _gui=False)
simif = SimpleNamespace(name=ActiveHDLInterface.name, _gui=False, simulator_hooks=self.hooks)
self.assertEqual(ActiveHDLInterface._vsim_extra_args(simif, make_config()), "-bar")


Expand All @@ -252,17 +283,15 @@ class TestVsimProcessHooks(unittest.TestCase):
"""

def setUp(self):
hooks.clear_hooks()

def tearDown(self):
hooks.clear_hooks()
self.hooks = SimulatorHooks()

def test_no_process_hooks_by_default(self):
with (
create_tempdir() as tempdir,
mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process,
):
simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=True)
simif.simulator_hooks = self.hooks
simif._persistent_shell._create_process(1) # pylint: disable=protected-access
self.assertNotIn("-noautoldlibpath", process.call_args[0][0])
self.assertEqual(process.call_args[1]["env"], {"BASE": "1"})
Expand All @@ -271,7 +300,7 @@ def test_no_process_hooks_by_default(self):
self.assertIsNone(process.call_args[1]["env"])

def test_persistent_vsim_process_uses_hooks(self):
hooks.register_hooks(
self.hooks.register(
"modelsim",
process_flags=lambda interface: ["-noautoldlibpath"],
run_env=lambda interface, env: dict(env, FOO="1"),
Expand All @@ -282,14 +311,15 @@ def test_persistent_vsim_process_uses_hooks(self):
mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process,
):
simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=True)
simif.simulator_hooks = self.hooks
simif._persistent_shell._create_process(1) # pylint: disable=protected-access

args = process.call_args[0][0]
self.assertEqual(args[-1], "-noautoldlibpath")
self.assertEqual(process.call_args[1]["env"], {"BASE": "1", "FOO": "1"})

def test_batch_vsim_process_uses_hooks(self):
hooks.register_hooks(
self.hooks.register(
"modelsim",
process_flags=lambda interface: ["-noautoldlibpath"],
run_env=lambda interface, env: dict(env, FOO="1"),
Expand All @@ -300,20 +330,22 @@ def test_batch_vsim_process_uses_hooks(self):
mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process,
):
simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=False)
simif.simulator_hooks = self.hooks
simif._run_batch_file(str(Path(tempdir) / "batch.do")) # pylint: disable=protected-access

args = process.call_args[0][0]
self.assertEqual(args[-1], "-noautoldlibpath")
self.assertEqual(process.call_args[1]["env"]["FOO"], "1")

def test_process_flags_are_specific_to_a_simulator(self):
hooks.register_hooks("rivierapro", process_flags=lambda interface: ["-noautoldlibpath"])
self.hooks.register("rivierapro", process_flags=lambda interface: ["-noautoldlibpath"])

with (
create_tempdir() as tempdir,
mock.patch("vunit.sim_if.vsim_simulator_mixin.Process", autospec=True) as process,
):
simif = _VsimSimulator(str(Path(tempdir) / "modelsim.ini"), persistent=False)
simif.simulator_hooks = self.hooks
simif._run_batch_file(str(Path(tempdir) / "batch.do")) # pylint: disable=protected-access

self.assertNotIn("-noautoldlibpath", process.call_args[0][0])
Expand Down
34 changes: 31 additions & 3 deletions vunit/package_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from pathlib import Path
from typing import Any, Optional

from vunit.sim_if.hooks import register_hooks
from vunit.sim_if.hooks import EnvHook, FlagsHook
from vunit.vhdl_standard import VHDLStandard


Expand Down Expand Up @@ -43,8 +43,36 @@ class PackageContext:
simulator_class: Any
_vunit_obj: Any

#: Register hooks extending what a simulator does for a test, see :ref:`vunit_packages`.
register_simulator_hooks = staticmethod(register_hooks)
def register_simulator_hooks(
self,
simulator_name: str,
*,
elab_flags: Optional[FlagsHook] = None,
run_flags: Optional[FlagsHook] = None,
process_flags: Optional[FlagsHook] = None,
run_env: Optional[EnvHook] = None,
) -> None:
"""
Register hooks extending what a simulator does for a test, see :ref:`vunit_packages`. The
hooks apply to the tests of the VUnit object the package is added to.

:param simulator_name: The name of the simulator the hooks apply to, for example "ghdl".
:param elab_flags: A ``elab_flags(simulator_interface)`` function returning extra flags for
the elaboration of a test.
:param run_flags: A ``run_flags(simulator_interface)`` function returning extra flags for the
simulation of a test.
:param process_flags: A ``process_flags(simulator_interface)`` function returning extra flags
for the simulator process VUnit starts.
:param run_env: A ``run_env(simulator_interface, env)`` function returning the environment
of the simulation of a test, given the environment it would otherwise have.
"""
self._vunit_obj._simulator_hooks.register( # pylint: disable=protected-access
simulator_name,
elab_flags=elab_flags,
run_flags=run_flags,
process_flags=process_flags,
run_env=run_env,
)

@property
def simulator_name(self) -> Optional[str]:
Expand Down
Loading
Loading