diff --git a/certbot/error_handler.py b/certbot/error_handler.py index 5e72f8153..64e9a1488 100644 --- a/certbot/error_handler.py +++ b/certbot/error_handler.py @@ -19,14 +19,30 @@ logger = logging.getLogger(__name__) # potentially occur from inside Python. Signals such as SIGILL were not # included as they could be a sign of something devious and we should terminate # immediately. -_SIGNALS = [signal.SIGTERM] if os.name != "nt": + _SIGNALS = [signal.SIGTERM] for signal_code in [signal.SIGHUP, signal.SIGQUIT, signal.SIGXCPU, signal.SIGXFSZ]: # Adding only those signals that their default action is not Ignore. # This is platform-dependent, so we check it dynamically. if signal.getsignal(signal_code) != signal.SIG_IGN: _SIGNALS.append(signal_code) +else: + # POSIX signals are not implemented natively in Windows, but emulated from the C runtime. + # As consumed by CPython, most of handlers on theses signals are useless, in particular + # SIGTERM: for instance, os.kill(pid, signal.SIGTERM) will call TerminateProcess, that stops + # immediately the process without calling the attached handler. Besides, non-POSIX signals + # (CTRL_C_EVENT and CTRL_BREAK_EVENT) are implemented in a console context to handle the + # CTRL+C event to a process launched from the console. Only CTRL_C_EVENT has a reliable + # behavior in fact, and maps to the handler to SIGINT. However in this case, a + # KeyboardInterrupt is raised, that will be handled by ErrorHandler through the context manager + # protocol. Finally, no signal on Windows is electable to be handled using ErrorHandler. + # + # Refs: https://stackoverflow.com/a/35792192, https://maruel.ca/post/python_windows_signal, + # https://docs.python.org/2/library/os.html#os.kill, + # https://www.reddit.com/r/Python/comments/1dsblt/windows_command_line_automation_ctrlc_question + _SIGNALS = [] + class ErrorHandler(object): """Context manager for running code that must be cleaned up on failure. diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index 8508a3df5..bc6d4fe3f 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -10,7 +10,6 @@ import mock from acme.magic_typing import Callable, Dict, Union # pylint: enable=unused-import, no-name-in-module -import certbot.tests.util as test_util def get_signals(signums): """Get the handlers for an iterable of signums.""" @@ -66,9 +65,9 @@ class ErrorHandlerTest(unittest.TestCase): self.init_func.assert_called_once_with(*self.init_args, **self.init_kwargs) - # On Windows, this test kills pytest itself ! - @test_util.broken_on_windows def test_context_manager_with_signal(self): + if not self.signals: + self.skipTest(reason='Signals cannot be handled on Windows.') init_signals = get_signals(self.signals) with signal_receiver(self.signals) as signals_received: with self.handler: @@ -98,9 +97,9 @@ class ErrorHandlerTest(unittest.TestCase): **self.init_kwargs) bad_func.assert_called_once_with() - # On Windows, this test kills pytest itself ! - @test_util.broken_on_windows def test_bad_recovery_with_signal(self): + if not self.signals: + self.skipTest(reason='Signals cannot be handled on Windows.') sig1 = self.signals[0] sig2 = self.signals[-1] bad_func = mock.MagicMock(side_effect=lambda: send_signal(sig1)) @@ -149,10 +148,9 @@ class ExitHandlerTest(ErrorHandlerTest): **self.init_kwargs) func.assert_called_once_with() - # On Windows, this test kills pytest itself ! - @test_util.broken_on_windows def test_bad_recovery_with_signal(self): super(ExitHandlerTest, self).test_bad_recovery_with_signal() + if __name__ == "__main__": unittest.main() # pragma: no cover