From 4632c46e3035d72834020492d962423e91da74ea Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sun, 10 Mar 2024 14:49:06 -0400 Subject: [PATCH] tests: expand coverage of hidpp20 --- .../logitech_receiver/test_hidpp20_simple.py | 295 +++++++++++++++++- 1 file changed, 287 insertions(+), 8 deletions(-) diff --git a/tests/logitech_receiver/test_hidpp20_simple.py b/tests/logitech_receiver/test_hidpp20_simple.py index b92e2823..c7b69aaf 100644 --- a/tests/logitech_receiver/test_hidpp20_simple.py +++ b/tests/logitech_receiver/test_hidpp20_simple.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from functools import partial -from typing import Any +from typing import Any, Optional from unittest import mock import pytest @@ -8,13 +8,19 @@ import pytest from lib.logitech_receiver import hidpp20 from lib.logitech_receiver import hidpp20_constants -DEVICE = "test_device" + +@dataclass +class Device: + name: str = "TEST DEVICE" + + +DEVICE = Device _hidpp20 = hidpp20.Hidpp20() @dataclass class Response: - response: str + response: Optional[str] device: Any feature: int function: int @@ -26,9 +32,7 @@ def feature_request(responses, device, feature, function=0x00, *params, no_reply r = responses[0] responses.pop(0) assert r.device == device - assert r.feature == feature - assert r.function == function - assert r.params == params + assert (r.feature, r.function, r.params) == (feature, function, params) return bytes.fromhex(r.response) if r.response is not None else None @@ -38,6 +42,219 @@ def mock_feature_request(): yield mock_feature_request +def test_get_firmware(mock_feature_request): + responses = [ + Response("02FFFF", DEVICE, hidpp20_constants.FEATURE.DEVICE_FW_VERSION, 0x00, ()), + Response("01414243030401000101000102030405", DEVICE, hidpp20_constants.FEATURE.DEVICE_FW_VERSION, 0x10, (0,)), + Response("02414243030401000101000102030405", DEVICE, hidpp20_constants.FEATURE.DEVICE_FW_VERSION, 0x10, (1,)), + ] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_firmware(DEVICE) + + assert len(result) == 2 + assert isinstance(result[0], common.FirmwareInfo) + assert isinstance(result[1], common.FirmwareInfo) + + +def test_get_ids(mock_feature_request): + responses = [Response("FF12345678000D123456789ABC", DEVICE, hidpp20_constants.FEATURE.DEVICE_FW_VERSION, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + unitId, modelId, tid_map = _hidpp20.get_ids(DEVICE) + + assert unitId == "12345678" + assert modelId == "123456789ABC" + assert tid_map == {"btid": "1234", "wpid": "5678", "usbid": "9ABC"} + + +def test_get_kind(mock_feature_request): + responses = [Response("00", DEVICE, hidpp20_constants.FEATURE.DEVICE_NAME, 0x20, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_kind(DEVICE) + + assert result == "keyboard" + assert result == 1 + + +def test_get_name(mock_feature_request): + responses = [ + Response("12", DEVICE, hidpp20_constants.FEATURE.DEVICE_NAME, 0x00, ()), + Response("4142434445464748494A4B4C4D4E4F", DEVICE, hidpp20_constants.FEATURE.DEVICE_NAME, 0x10, (0,)), + Response("505152530000000000000000000000", DEVICE, hidpp20_constants.FEATURE.DEVICE_NAME, 0x10, (15,)), + ] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_name(DEVICE) + + assert result == "ABCDEFGHIJKLMNOPQR" + + +def test_get_friendly_name(mock_feature_request): + responses = [ + Response("12", DEVICE, hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME, 0x00, ()), + Response("004142434445464748494A4B4C4D4E", DEVICE, hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME, 0x10, (0,)), + Response("0E4F50515253000000000000000000", DEVICE, hidpp20_constants.FEATURE.DEVICE_FRIENDLY_NAME, 0x10, (14,)), + ] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_friendly_name(DEVICE) + + assert result == "ABCDEFGHIJKLMNOPQR" + + +def test_get_battery_status(mock_feature_request): + responses = [Response("502000FFFF", DEVICE, hidpp20_constants.FEATURE.BATTERY_STATUS, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + feature, battery = _hidpp20.get_battery_status(DEVICE) + + assert feature == hidpp20_constants.FEATURE.BATTERY_STATUS + assert battery.level == 80 + assert battery.next_level == 32 + assert battery.status == common.Battery.STATUS.discharging + + +def test_get_battery_voltage(mock_feature_request): + responses = [Response("1000FFFFFF", DEVICE, hidpp20_constants.FEATURE.BATTERY_VOLTAGE, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + feature, battery = _hidpp20.get_battery_voltage(DEVICE) + + assert feature == hidpp20_constants.FEATURE.BATTERY_VOLTAGE + assert battery.level == 90 + assert battery.status == common.Battery.STATUS.recharging + assert battery.voltage == 0x1000 + + +def test_get_battery_unified(mock_feature_request): + responses = [Response("500100FFFF", DEVICE, hidpp20_constants.FEATURE.UNIFIED_BATTERY, 0x10, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + feature, battery = _hidpp20.get_battery_unified(DEVICE) + + assert feature == hidpp20_constants.FEATURE.UNIFIED_BATTERY + assert battery.level == 80 + assert battery.status == common.Battery.STATUS.discharging + + +def test_get_adc_measurement(mock_feature_request): + responses = [Response("100003", DEVICE, hidpp20_constants.FEATURE.ADC_MEASUREMENT, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + feature, battery = _hidpp20.get_adc_measurement(DEVICE) + + assert feature == hidpp20_constants.FEATURE.ADC_MEASUREMENT + assert battery.level == 90 + assert battery.status == common.Battery.STATUS.recharging + assert battery.voltage == 0x1000 + + +def test_get_battery(mock_feature_request): + responses = [Response("502000FFFF", DEVICE, hidpp20_constants.FEATURE.BATTERY_STATUS, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + feature, battery = _hidpp20.get_battery(DEVICE, hidpp20_constants.FEATURE.BATTERY_STATUS) + + assert feature == hidpp20_constants.FEATURE.BATTERY_STATUS + assert battery.level == 80 + assert battery.next_level == 32 + assert battery.status == common.Battery.STATUS.discharging + + +def test_get_battery_none(mock_feature_request): + responses = [ + Response(None, DEVICE, hidpp20_constants.FEATURE.BATTERY_STATUS, 0x00, ()), + Response(None, DEVICE, hidpp20_constants.FEATURE.BATTERY_VOLTAGE, 0x00, ()), + Response("500100ffff", DEVICE, hidpp20_constants.FEATURE.UNIFIED_BATTERY, 0x10, ()), + ] + mock_feature_request.side_effect = partial(feature_request, responses) + + feature, battery = _hidpp20.get_battery(DEVICE, None) + + assert feature == hidpp20_constants.FEATURE.UNIFIED_BATTERY + assert battery.level == 80 + assert battery.status == common.Battery.STATUS.discharging + + +# get_keys is complex +# get_remap_keys is comples +# get_gestures is complex +# get_backlight is complex +# get_profiles is complex + + +def test_get_mouse_pointer_info(mock_feature_request): + responses = [Response("01000A", DEVICE, hidpp20_constants.FEATURE.MOUSE_POINTER, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_mouse_pointer_info(DEVICE) + + assert result == { + "dpi": 0x100, + "acceleration": "med", + "suggest_os_ballistics": False, + "suggest_vertical_orientation": True, + } + + +def test_get_vertical_scrolling_info(mock_feature_request): + responses = [Response("01080C", DEVICE, hidpp20_constants.FEATURE.VERTICAL_SCROLLING, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_vertical_scrolling_info(DEVICE) + + assert result == {"roller": "standard", "ratchet": 8, "lines": 12} + + +def test_get_hi_res_scrolling_info(mock_feature_request): + responses = [Response("0102", DEVICE, hidpp20_constants.FEATURE.HI_RES_SCROLLING, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + mode, resolution = _hidpp20.get_hi_res_scrolling_info(DEVICE) + + assert mode == 1 + assert resolution == 2 + + +def test_get_pointer_speed_info(mock_feature_request): + responses = [Response("0102", DEVICE, hidpp20_constants.FEATURE.POINTER_SPEED, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_pointer_speed_info(DEVICE) + + assert result == 0x0102 / 256 + + +def test_get_lowres_wheel_status(mock_feature_request): + responses = [Response("01", DEVICE, hidpp20_constants.FEATURE.LOWRES_WHEEL, 0x00, ())] + mock_feature_request.side_effect = partial(feature_request, responses) + + result = _hidpp20.get_lowres_wheel_status(DEVICE) + + assert result == "HID++" + + +def test_get_hires_wheel(mock_feature_request): + responses = [ + Response("010C", DEVICE, hidpp20_constants.FEATURE.HIRES_WHEEL, 0x00, ()), + Response("05FF", DEVICE, hidpp20_constants.FEATURE.HIRES_WHEEL, 0x10, ()), + Response("03FF", DEVICE, hidpp20_constants.FEATURE.HIRES_WHEEL, 0x30, ()), + ] + mock_feature_request.side_effect = partial(feature_request, responses) + + multi, has_invert, has_ratchet, inv, res, target, ratchet = _hidpp20.get_hires_wheel(DEVICE) + + assert multi == 1 + assert has_invert is True + assert has_ratchet is True + assert inv is True + assert res is False + assert target is True + assert ratchet is True + + def test_get_new_fn_inversion(mock_feature_request): responses = [Response("0300", DEVICE, hidpp20_constants.FEATURE.NEW_FN_INVERSION, 0x00, ())] mock_feature_request.side_effect = partial(feature_request, responses) @@ -49,14 +266,33 @@ def test_get_new_fn_inversion(mock_feature_request): assert len(responses) == 0 +@pytest.fixture +def mock_gethostname(mocker): + mocker.patch("socket.gethostname", return_value="getafix.foo.org") + + @pytest.mark.parametrize( "responses, expected_result", [ ([Response(None, DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x00, ())], {}), - ([Response("02000002", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x00, ())], {}), + ([Response("02000000", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x00, ())], {}), + ( + [ + Response("03000200", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x00, ()), + Response("FF01FFFF05FFFF", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x10, (0x00,)), + Response("0000414243444500FFFFFFFFFF", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x30, (0x00, 0x00)), + Response("FF01FFFF10FFFF", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x10, (0x01,)), + Response("01004142434445464748494A4B4C4D", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x30, (0x01, 0)), + Response("01134E4F5000FFFFFFFFFFFFFFFFFF", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x30, (0x01, 14)), + Response("03000200", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x00, ()), + Response("000000000008", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x10, (0x0,)), + Response("0208", DEVICE, hidpp20.FEATURE.HOSTS_INFO, 0x40, (0x0, 0x0, bytearray("getafix", "utf-8"))), + ], + {0: (True, "getafix"), 1: (True, "ABCDEFGHIJKLMNO")}, + ), ], ) -def test_get_host_names(responses, expected_result, mock_feature_request): +def test_get_host_names(responses, expected_result, mock_feature_request, mock_gethostname): mock_feature_request.side_effect = partial(feature_request, responses) result = _hidpp20.get_host_names(DEVICE) @@ -158,3 +394,46 @@ def test_config_change(mock_feature_request): assert result == bytes.fromhex("03FFFF") assert len(responses) == 0 + + +def test_decipher_battery_status(): + report = b"\x50\x20\x00\xff\xff" + + feature, battery = hidpp20.decipher_battery_status(report) + + assert feature == hidpp20_constants.FEATURE.BATTERY_STATUS + assert battery.level == 80 + assert battery.next_level == 32 + assert battery.status == common.Battery.STATUS.discharging + + +def test_decipher_battery_voltage(): + report = b"\x10\x00\xFF\xff\xff" + + feature, battery = hidpp20.decipher_battery_voltage(report) + + assert feature == hidpp20_constants.FEATURE.BATTERY_VOLTAGE + assert battery.level == 90 + assert battery.status == common.Battery.STATUS.recharging + assert battery.voltage == 0x1000 + + +def test_decipher_battery_unified(): + report = b"\x50\x01\x00\xff\xff" + + feature, battery = hidpp20.decipher_battery_unified(report) + + assert feature == hidpp20_constants.FEATURE.UNIFIED_BATTERY + assert battery.level == 80 + assert battery.status == common.Battery.STATUS.discharging + + +def test_decipher_adc_measurement(): + report = b"\x10\x00\x03" + + feature, battery = hidpp20.decipher_adc_measurement(report) + + assert feature == hidpp20_constants.FEATURE.ADC_MEASUREMENT + assert battery.level == 90 + assert battery.status == common.Battery.STATUS.recharging + assert battery.voltage == 0x1000