From 79ffbda903e023c7a6d0547437f8a2191d2d17df Mon Sep 17 00:00:00 2001 From: Romain Loutrel Date: Wed, 23 Oct 2024 22:22:22 +0200 Subject: [PATCH] change pairing error values to intenums * refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError * refactoring(logitech_receiver/notifications): change to enums PairingError and BoltPairingError (Fix pre-commit checks) * refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum * refactor(logitech_receiver/base.py): create unit tests for request function before replacing ERROR NamedInts by IntEnum * refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (add exclusion for macOS) * refactor(logitech_receiver/base.py): create unit tests for ping function before replacing ERRORNamedInts by IntEnum (fix for python < 3.10) --- lib/logitech_receiver/hidpp10_constants.py | 13 +++- lib/logitech_receiver/notifications.py | 6 +- tests/logitech_receiver/test_base.py | 75 +++++++++++++++++++ tests/logitech_receiver/test_notifications.py | 14 +++- 4 files changed, 99 insertions(+), 9 deletions(-) diff --git a/lib/logitech_receiver/hidpp10_constants.py b/lib/logitech_receiver/hidpp10_constants.py index 90b76e425..d3e45d8d0 100644 --- a/lib/logitech_receiver/hidpp10_constants.py +++ b/lib/logitech_receiver/hidpp10_constants.py @@ -102,8 +102,17 @@ wrong_pin_code=0x0C, ) -PAIRING_ERRORS = NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06) -BOLT_PAIRING_ERRORS = NamedInts(device_timeout=0x01, failed=0x02) + +class PairingError(IntEnum): + DEVICE_TIMEOUT = 0x01 + DEVICE_NOT_SUPPORTED = 0x02 + TOO_MANY_DEVICES = 0x03 + SEQUENCE_TIMEOUT = 0x06 + + +class BoltPairingError(IntEnum): + DEVICE_TIMEOUT = 0x01 + FAILED = 0x02 class Registers(IntEnum): diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index b85a9a518..6560289a2 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -91,7 +91,7 @@ def _process_receiver_notification(receiver: Receiver, hidpp_notification: HIDPP receiver.pairing.new_device = None pair_error = ord(hidpp_notification.data[:1]) if pair_error: - receiver.pairing.error = error_string = hidpp10_constants.PAIRING_ERRORS[pair_error] + receiver.pairing.error = error_string = hidpp10_constants.PairingError(pair_error) receiver.pairing.new_device = None logger.warning("pairing error %d: %s", pair_error, error_string) receiver.changed(reason=reason) @@ -110,7 +110,7 @@ def _process_receiver_notification(receiver: Receiver, hidpp_notification: HIDPP receiver.pairing.device_passkey = None discover_error = ord(hidpp_notification.data[:1]) if discover_error: - receiver.pairing.error = discover_string = hidpp10_constants.BOLT_PAIRING_ERRORS[discover_error] + receiver.pairing.error = discover_string = hidpp10_constants.BoltPairingError(discover_error) logger.warning("bolt discovering error %d: %s", discover_error, discover_string) receiver.changed(reason=reason) return True @@ -150,7 +150,7 @@ def _process_receiver_notification(receiver: Receiver, hidpp_notification: HIDPP elif hidpp_notification.address == 0x02 and not pair_error: receiver.pairing.new_device = receiver.register_new_device(hidpp_notification.data[7]) if pair_error: - receiver.pairing.error = error_string = hidpp10_constants.BOLT_PAIRING_ERRORS[pair_error] + receiver.pairing.error = error_string = hidpp10_constants.BoltPairingError(pair_error) receiver.pairing.new_device = None logger.warning("pairing error %d: %s", pair_error, error_string) receiver.changed(reason=reason) diff --git a/tests/logitech_receiver/test_base.py b/tests/logitech_receiver/test_base.py index 3908c1e74..1c3d11bdc 100644 --- a/tests/logitech_receiver/test_base.py +++ b/tests/logitech_receiver/test_base.py @@ -1,6 +1,15 @@ +import struct +import sys + +from unittest import mock + import pytest from logitech_receiver import base +from logitech_receiver import exceptions +from logitech_receiver.base import HIDPP_SHORT_MESSAGE_ID +from logitech_receiver.base import request +from logitech_receiver.hidpp10_constants import ERROR @pytest.mark.parametrize( @@ -111,3 +120,69 @@ def test_get_next_sw_id(): assert res1 == 2 assert res2 == 3 + + +@pytest.mark.parametrize( + "prefix, error_code, return_error, raise_exception", + [ + (b"\x8f", ERROR.invalid_SubID__command, False, False), + (b"\x8f", ERROR.invalid_SubID__command, True, False), + (b"\xff", ERROR.invalid_SubID__command, False, True), + ], +) +def test_request_errors(prefix: bytes, error_code: ERROR, return_error: bool, raise_exception: bool): + handle = 0 + device_number = 66 + + next_sw_id = 0x02 + reply_data_sw_id = struct.pack("!H", 0x0000 | next_sw_id) + + with mock.patch( + "logitech_receiver.base._read", + return_value=(HIDPP_SHORT_MESSAGE_ID, device_number, prefix + reply_data_sw_id + struct.pack("B", error_code)), + ), mock.patch("logitech_receiver.base._skip_incoming", return_value=None), mock.patch( + "logitech_receiver.base.write", return_value=None + ), mock.patch("logitech_receiver.base._get_next_sw_id", return_value=next_sw_id): + if raise_exception: + with pytest.raises(exceptions.FeatureCallError) as context: + request(handle, device_number, next_sw_id, return_error=return_error) + assert context.value.number == device_number + assert context.value.request == next_sw_id + assert context.value.error == error_code + assert context.value.params == b"" + + else: + result = request(handle, device_number, next_sw_id, return_error=return_error) + assert result == (error_code if return_error else None) + + +@pytest.mark.skipif(sys.platform == "darwin", reason="Test only runs on Linux") +@pytest.mark.parametrize( + "simulated_error, expected_result", + [ + (ERROR.invalid_SubID__command, 1.0), + (ERROR.resource_error, None), + (ERROR.connection_request_failed, None), + (ERROR.unknown_device, exceptions.NoSuchDevice), + ], +) +def test_ping_errors(simulated_error: ERROR, expected_result): + handle = 1 + device_number = 1 + + next_sw_id = 0x05 + reply_data_sw_id = struct.pack("!H", 0x0010 | next_sw_id) + + with mock.patch( + "logitech_receiver.base._read", + return_value=(HIDPP_SHORT_MESSAGE_ID, device_number, b"\x8f" + reply_data_sw_id + bytes([simulated_error])), + ), mock.patch("logitech_receiver.base._get_next_sw_id", return_value=next_sw_id): + if isinstance(expected_result, type) and issubclass(expected_result, Exception): + with pytest.raises(expected_result) as context: + base.ping(handle=handle, devnumber=device_number) + assert context.value.number == device_number + assert context.value.request == struct.unpack("!H", reply_data_sw_id)[0] + + else: + result = base.ping(handle=handle, devnumber=device_number) + assert result == expected_result diff --git a/tests/logitech_receiver/test_notifications.py b/tests/logitech_receiver/test_notifications.py index 84badb7bc..9cc8dd333 100644 --- a/tests/logitech_receiver/test_notifications.py +++ b/tests/logitech_receiver/test_notifications.py @@ -1,8 +1,10 @@ import pytest -from logitech_receiver import hidpp10_constants from logitech_receiver import notifications from logitech_receiver.base import HIDPPNotification +from logitech_receiver.common import Notification +from logitech_receiver.hidpp10_constants import BoltPairingError +from logitech_receiver.hidpp10_constants import PairingError from logitech_receiver.hidpp10_constants import Registers from logitech_receiver.receiver import Receiver @@ -24,8 +26,12 @@ def request(self, handle, devnumber, request_id, *params, **kwargs): @pytest.mark.parametrize( "sub_id, notification_data, expected_error, expected_new_device", [ - (Registers.DISCOVERY_STATUS_NOTIFICATION, b"\x01", "device_timeout", None), - (Registers.PAIRING_STATUS_NOTIFICATION, b"\x02", "failed", None), + (Registers.DISCOVERY_STATUS_NOTIFICATION, b"\x01", BoltPairingError.DEVICE_TIMEOUT, None), + (Registers.PAIRING_STATUS_NOTIFICATION, b"\x02", BoltPairingError.FAILED, None), + (Notification.PAIRING_LOCK, b"\x01", PairingError.DEVICE_TIMEOUT, None), + (Notification.PAIRING_LOCK, b"\x02", PairingError.DEVICE_NOT_SUPPORTED, None), + (Notification.PAIRING_LOCK, b"\x03", PairingError.TOO_MANY_DEVICES, None), + (Notification.PAIRING_LOCK, b"\x06", PairingError.SEQUENCE_TIMEOUT, None), ], ) def test_process_receiver_notification(sub_id, notification_data, expected_error, expected_new_device): @@ -35,5 +41,5 @@ def test_process_receiver_notification(sub_id, notification_data, expected_error result = notifications._process_receiver_notification(receiver, notification) assert result - assert receiver.pairing.error == hidpp10_constants.BOLT_PAIRING_ERRORS[expected_error] + assert receiver.pairing.error == expected_error assert receiver.pairing.new_device is expected_new_device