mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-04 19:43:49 +08:00
Handle unavailable Python Bluetooth socket connections
This commit is contained in:
@@ -88,7 +88,11 @@ def _libc_connect(proto: int, kind: int, address: str, port: int, timeout: float
|
||||
def _connect(proto: int, kind: int, address: str, port: int, timeout: float) -> socket.socket:
|
||||
address = normalize_address(address)
|
||||
if hasattr(socket, "AF_BLUETOOTH") and hasattr(socket, "BTPROTO_RFCOMM"):
|
||||
return _native(proto, kind, address, port, timeout)
|
||||
try:
|
||||
return _native(proto, kind, address, port, timeout)
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.ENOSYS:
|
||||
raise
|
||||
return _libc_connect(proto, kind, address, port, timeout)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import errno
|
||||
import socket
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from openpilot.starpilot.system.android_auto import bt_sockets
|
||||
|
||||
|
||||
class TestBluetoothSocketFallback(unittest.TestCase):
|
||||
ADDRESS = '50:5A:65:8B:8B:82'
|
||||
|
||||
def routes(self):
|
||||
return ((bt_sockets.connect_rfcomm, 1), (bt_sockets.connect_l2cap, 1))
|
||||
|
||||
def capabilities(self):
|
||||
return patch.multiple(socket, AF_BLUETOOTH=31, BTPROTO_RFCOMM=3, create=True)
|
||||
|
||||
def test_native_success_does_not_retry(self):
|
||||
with self.capabilities(), patch.object(bt_sockets, '_native') as native, patch.object(bt_sockets, '_libc_connect') as libc:
|
||||
for connect, port in self.routes():
|
||||
self.assertIs(connect(self.ADDRESS, port, 4), native.return_value)
|
||||
self.assertEqual(native.call_count, 2)
|
||||
libc.assert_not_called()
|
||||
|
||||
def test_unimplemented_connect_uses_same_protocol_and_timeout(self):
|
||||
with self.capabilities(), patch.object(bt_sockets, '_native', side_effect=OSError(errno.ENOSYS, 'Function not implemented')), \
|
||||
patch.object(bt_sockets, '_libc_connect') as libc:
|
||||
for connect, port in self.routes():
|
||||
self.assertIs(connect(self.ADDRESS.lower(), port, 4), libc.return_value)
|
||||
self.assertEqual(libc.call_args_list[0].args, (3, socket.SOCK_STREAM, self.ADDRESS, 1, 4))
|
||||
self.assertEqual(libc.call_args_list[1].args, (0, socket.SOCK_SEQPACKET, self.ADDRESS, 1, 4))
|
||||
|
||||
def test_real_connection_failures_do_not_retry(self):
|
||||
for code in (errno.EHOSTDOWN, errno.ECONNREFUSED, errno.ETIMEDOUT, errno.EPERM, errno.EAFNOSUPPORT):
|
||||
with self.subTest(errno=code), self.capabilities(), \
|
||||
patch.object(bt_sockets, '_native', side_effect=OSError(code, 'connection failed')), \
|
||||
patch.object(bt_sockets, '_libc_connect') as libc:
|
||||
for connect, port in self.routes():
|
||||
with self.assertRaises(OSError) as caught:
|
||||
connect(self.ADDRESS, port)
|
||||
self.assertEqual(caught.exception.errno, code)
|
||||
libc.assert_not_called()
|
||||
|
||||
def test_fallback_failure_propagates_without_loop(self):
|
||||
with self.capabilities(), patch.object(bt_sockets, '_native', side_effect=OSError(errno.ENOSYS, 'unimplemented')) as native, \
|
||||
patch.object(bt_sockets, '_libc_connect', side_effect=OSError(errno.EHOSTDOWN, 'host down')) as libc:
|
||||
with self.assertRaises(OSError) as caught:
|
||||
bt_sockets.connect_rfcomm(self.ADDRESS, 1)
|
||||
self.assertEqual(caught.exception.errno, errno.EHOSTDOWN)
|
||||
native.assert_called_once()
|
||||
libc.assert_called_once()
|
||||
|
||||
def test_native_failure_closes_original_descriptor(self):
|
||||
original = Mock()
|
||||
original.connect.side_effect = OSError(errno.ENOSYS, 'unimplemented')
|
||||
with self.capabilities(), patch.object(socket, 'socket', return_value=original), patch.object(bt_sockets, '_libc_connect') as libc:
|
||||
self.assertIs(bt_sockets.connect_rfcomm(self.ADDRESS, 1), libc.return_value)
|
||||
original.close.assert_called_once()
|
||||
|
||||
def test_missing_python_capability_uses_libc(self):
|
||||
with patch.object(bt_sockets, 'socket') as python_socket, patch.object(bt_sockets, '_libc_connect') as libc:
|
||||
del python_socket.AF_BLUETOOTH
|
||||
self.assertIs(bt_sockets.connect_rfcomm(self.ADDRESS, 1), libc.return_value)
|
||||
|
||||
def test_invalid_inputs_never_open_socket(self):
|
||||
with patch.object(bt_sockets, '_native') as native, patch.object(bt_sockets, '_libc_connect') as libc:
|
||||
for address, channel in (('bad', 1), (self.ADDRESS, 0), (self.ADDRESS, 31)):
|
||||
with self.assertRaises(ValueError):
|
||||
bt_sockets.connect_rfcomm(address, channel)
|
||||
native.assert_not_called()
|
||||
libc.assert_not_called()
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "49536cbf72bd1f27ce146a2f062d911757d40c87e5a2247d67d9bae44600cc40", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "4653c7b94bf628c49e1d16cf6a4c617f3342dcdeffbed7142631858a1f666807", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user