-
-
Notifications
You must be signed in to change notification settings - Fork 236
reliability: guard crash paths in DHCP/network teardown #3291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
geraldo-netto
wants to merge
4
commits into
blueman-project:main
Choose a base branch
from
geraldo-netto:reliability/crash-guards
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+187
−4
Open
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
7ea9799
reliability: guard crash paths in DHCP/network teardown
geraldo-netto 072c30e
Use logging.exception for rfcomm watcher failures
geraldo-netto fbc5c0b
docs: remove stale DhcpClient timeout comment
geraldo-netto 71e145d
Merge remote-tracking branch 'origin/main' into reliability/crash-guards
geraldo-netto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| from unittest import TestCase | ||
| from unittest.mock import patch, Mock | ||
|
|
||
| from blueman.main.DhcpClient import DhcpClient | ||
|
|
||
|
|
||
| class TestDhcpClientGuards(TestCase): | ||
| def _make(self): | ||
| # __init__ only probes for a client binary via have(); no process runs. | ||
| with patch("blueman.main.DhcpClient.have", return_value=None): | ||
| return DhcpClient("pan1") | ||
|
|
||
| def test_client_initialised_to_none(self): | ||
| client = self._make() | ||
| self.assertIsNone(client._client) | ||
|
|
||
| def test_check_client_before_run_does_not_crash(self): | ||
| # Previously self._client was undefined until run() -> AttributeError. | ||
| client = self._make() | ||
| self.assertFalse(client._check_client()) | ||
|
|
||
| def test_on_timeout_before_run_does_not_crash(self): | ||
| client = self._make() | ||
| self.assertFalse(client._on_timeout()) | ||
|
|
||
| def test_on_timeout_terminates_only_running_client(self): | ||
| # poll() == None -> still running -> terminate. | ||
| client = self._make() | ||
| client._client = Mock() | ||
| client._client.poll.return_value = None | ||
| client._on_timeout() | ||
| client._client.terminate.assert_called_once_with() | ||
|
|
||
| def test_on_timeout_ignores_finished_client(self): | ||
| # poll() == 0 (success) or a non-zero exit code -> already done -> leave. | ||
| for status in (0, 1, 5, 255): | ||
| with self.subTest(status=status): | ||
| client = self._make() | ||
| client._client = Mock() | ||
| client._client.poll.return_value = status | ||
| client._on_timeout() | ||
| client._client.terminate.assert_not_called() | ||
|
|
||
| def test_on_timeout_fuzz_poll_values(self): | ||
| for status in [None, 0, 1, -1, 2, 127, 255, 1000]: | ||
| with self.subTest(status=status): | ||
| client = self._make() | ||
| client._client = Mock() | ||
| client._client.poll.return_value = status | ||
| # Must never raise; terminate iff still running. | ||
| client._on_timeout() | ||
| if status is None: | ||
| client._client.terminate.assert_called_once_with() | ||
| else: | ||
| client._client.terminate.assert_not_called() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| from unittest import TestCase | ||
| from unittest.mock import patch, Mock | ||
|
|
||
| from blueman.plugins.mechanism import Network as network_module | ||
| from blueman.plugins.mechanism.Network import Network, DHCPDHANDLERS | ||
|
|
||
|
|
||
| def _make_network(): | ||
| # MechanismPlugin.__init__ takes timer/confirm_authorization off the parent | ||
| # and calls on_load(), which only registers methods on the (mock) parent. | ||
| return Network(Mock()) | ||
|
|
||
|
|
||
| class TestEnableNetworkHandlerValidation(TestCase): | ||
| def test_known_handlers_dispatch(self): | ||
| for name, cls in DHCPDHANDLERS.items(): | ||
| with self.subTest(handler=name): | ||
| net = _make_network() | ||
| with patch.object(network_module.NetConf, "apply_settings") as apply_mock: | ||
| net._enable_network("203.0.113.1", "255.255.255.0", name, False, ":1.1") | ||
| apply_mock.assert_called_once_with("203.0.113.1", "255.255.255.0", cls, False) | ||
|
|
||
| def test_unknown_handler_raises_before_apply(self): | ||
| net = _make_network() | ||
| with patch.object(network_module.NetConf, "apply_settings") as apply_mock: | ||
| with self.assertRaises(ValueError): | ||
| net._enable_network("203.0.113.1", "255.255.255.0", "bogus", False, ":1.1") | ||
| apply_mock.assert_not_called() | ||
|
|
||
| def test_fuzz_unknown_keys_never_keyerror(self): | ||
| bad_keys = [ | ||
| "", "dnsmasqhandler", "DnsMasqHandler ", " DnsMasqHandler", | ||
| "__class__", "../etc", "DnsMasq", "None", "0", "🚀", | ||
| "DnsMasqHandler\n", "DHCPDHANDLERS", | ||
| ] | ||
| for key in bad_keys: | ||
| with self.subTest(key=key): | ||
| net = _make_network() | ||
| with patch.object(network_module.NetConf, "apply_settings") as apply_mock: | ||
| with self.assertRaises(ValueError): | ||
| net._enable_network("203.0.113.1", "255.255.255.0", key, False, ":1.1") | ||
| apply_mock.assert_not_called() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| from unittest import TestCase | ||
| from unittest.mock import patch, Mock | ||
|
|
||
| from blueman.plugins.mechanism.Rfcomm import Rfcomm | ||
|
|
||
|
|
||
| def _make_rfcomm(): | ||
| # MechanismPlugin.__init__ pulls timer/confirm_authorization off parent and | ||
| # calls on_load(), which only registers D-Bus methods on the (mock) parent. | ||
| return Rfcomm(Mock()) | ||
|
|
||
|
|
||
| class TestOpenRfcomm(TestCase): | ||
| def test_open_spawns_watcher(self): | ||
| rfcomm = _make_rfcomm() | ||
| with patch("blueman.plugins.mechanism.Rfcomm.subprocess.Popen") as popen_mock: | ||
| rfcomm._open_rfcomm(3) | ||
| args = popen_mock.call_args.args[0] | ||
| self.assertEqual(args[-1], "/dev/rfcomm3") | ||
|
|
||
| def test_open_failure_logs_and_propagates(self): | ||
| rfcomm = _make_rfcomm() | ||
| with patch("blueman.plugins.mechanism.Rfcomm.subprocess.Popen", | ||
| side_effect=OSError("no such file")): | ||
| with self.assertLogs(level="ERROR"): | ||
| with self.assertRaises(OSError): | ||
| rfcomm._open_rfcomm(0) | ||
|
|
||
| def test_open_fuzz_port_ids(self): | ||
| for port_id in [0, 1, 7, 15, 99, 12345]: | ||
| with self.subTest(port_id=port_id): | ||
| rfcomm = _make_rfcomm() | ||
| with patch("blueman.plugins.mechanism.Rfcomm.subprocess.Popen") as popen_mock: | ||
| rfcomm._open_rfcomm(port_id) | ||
| self.assertEqual(popen_mock.call_args.args[0][-1], f"/dev/rfcomm{port_id}") |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.