blob: 1a232414b111c23bed617c3be290dfc2828e47f5 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import signal
import subprocess
import unittest
from typing import Any
from unittest import mock
import signal_utils
class SignalUtilsTest(unittest.TestCase):
@mock.patch("signal_utils.os.killpg")
@mock.patch("signal_utils.os.getpgid", return_value=1234)
@mock.patch("signal_utils.signal.signal")
def test_forward_signals(
self,
mock_signal: mock.Mock,
mock_getpgid: mock.Mock,
mock_killpg: mock.Mock,
) -> None:
mock_process = mock.Mock(spec=subprocess.Popen)
mock_process.pid = 5678
registered_handlers = {}
def fake_signal(sig: int, handler: Any) -> int:
registered_handlers[sig] = handler
return signal.SIG_DFL
mock_signal.side_effect = fake_signal
with signal_utils.forward_signals(mock_process):
# Check that signals were registered
self.assertIn(signal.SIGINT, registered_handlers)
self.assertIn(signal.SIGHUP, registered_handlers)
self.assertIn(signal.SIGTERM, registered_handlers)
# Trigger a handler
handler = registered_handlers[signal.SIGINT]
handler(signal.SIGINT, None)
# Check forwarding
mock_getpgid.assert_called_with(5678)
mock_killpg.assert_called_once_with(1234, signal.SIGINT)
# Check that original handlers were restored (mock_signal called with SIG_DFL)
# Each signal is called twice: once to set, once to restore.
expected_calls = []
sigs = [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]
if hasattr(signal, "SIGQUIT"):
sigs.append(signal.SIGQUIT)
for sig in sigs:
expected_calls.append(mock.call(sig, mock.ANY)) # Set
for sig in sigs:
expected_calls.append(mock.call(sig, signal.SIG_DFL)) # Restore
mock_signal.assert_has_calls(expected_calls, any_order=True)
@mock.patch("signal_utils.os.killpg")
@mock.patch("signal_utils.os.getpgid", side_effect=ProcessLookupError)
@mock.patch("signal_utils.signal.signal")
def test_forward_signals_process_finished(
self,
mock_signal: mock.Mock,
mock_getpgid: mock.Mock,
mock_killpg: mock.Mock,
) -> None:
mock_process = mock.Mock(spec=subprocess.Popen)
mock_process.pid = 5678
registered_handlers = {}
def fake_signal(sig: int, handler: Any) -> int:
registered_handlers[sig] = handler
return signal.SIG_DFL
mock_signal.side_effect = fake_signal
with signal_utils.forward_signals(mock_process):
handler = registered_handlers[signal.SIGINT]
# Should not raise exception if process is gone
handler(signal.SIGINT, None)
mock_killpg.assert_not_called()
if __name__ == "__main__":
unittest.main()