diff --git a/poetry.lock b/poetry.lock index afe1777..9ad89fe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1536,6 +1536,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.11.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -2012,4 +2029,4 @@ h11 = ">=0.9.0,<1" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "409dcbb9f27079667033d1111bdc1ca99436c0e28568562dece75b590aa80e08" +content-hash = "b2763b13a2e00c8094a0c30f6157e72c118bab47ab2d9a0357345b33693e6afa" diff --git a/pyproject.toml b/pyproject.toml index 29182d5..a25d589 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ flake8-pep604 = "^0.1.0" flake8-eradicate = "^1.4.0" matplotlib = "^3.7.2" pyright = "^1.1.323" +pytest-mock = "^3.11.1" [tool.poetry.group.docs.dependencies] diff --git a/src/pydase/server/server.py b/src/pydase/server/server.py index 454739f..3b32396 100644 --- a/src/pydase/server/server.py +++ b/src/pydase/server/server.py @@ -358,20 +358,16 @@ class Server: # Signals can only be listened to from the main thread. return - try: - for sig in HANDLED_SIGNALS: - self._loop.add_signal_handler(sig, self.handle_exit, sig, None) - except NotImplementedError: - # Windows - for sig in HANDLED_SIGNALS: - signal.signal(sig, self.handle_exit) + for sig in HANDLED_SIGNALS: + signal.signal(sig, self.handle_exit) def handle_exit(self, sig: int = 0, frame: Optional[FrameType] = None) -> None: - logger.info("Handling exit") if self.should_exit and sig == signal.SIGINT: - self.force_exit = True + logger.warning(f"Received signal {sig}, forcing exit...") + os._exit(1) else: self.should_exit = True + logger.warning(f"Received signal {sig}, exiting...") def custom_exception_handler( self, loop: asyncio.AbstractEventLoop, context: dict[str, Any] diff --git a/tests/server/test_server.py b/tests/server/test_server.py new file mode 100644 index 0000000..82e89bd --- /dev/null +++ b/tests/server/test_server.py @@ -0,0 +1,35 @@ +import signal + +from pytest_mock import MockerFixture + +import pydase + + +def test_signal_handling(mocker: MockerFixture): + # Mock os._exit and signal.signal + mock_exit = mocker.patch("os._exit") + mock_signal = mocker.patch("signal.signal") + + class MyService(pydase.DataService): + pass + + # Instantiate your server object + server = pydase.Server(MyService()) + + # Call the method to install signal handlers + server.install_signal_handlers() + + # Check if the signal handlers were registered correctly + assert mock_signal.call_args_list == [ + mocker.call(signal.SIGINT, server.handle_exit), + mocker.call(signal.SIGTERM, server.handle_exit), + ] + + # Simulate receiving a SIGINT signal for the first time + server.handle_exit(signal.SIGINT, None) + assert server.should_exit # assuming should_exit is public + mock_exit.assert_not_called() + + # Simulate receiving a SIGINT signal for the second time + server.handle_exit(signal.SIGINT, None) + mock_exit.assert_called_once_with(1)