tests: add tests for setting_templates

This commit is contained in:
Peter F. Patel-Schneider 2024-03-22 15:11:48 -04:00
parent 17bbc9c4ea
commit 5e351399f5
2 changed files with 486 additions and 0 deletions

View File

@ -16,10 +16,16 @@
"""HID++ data and functions common to several logitech_receiver test files"""
from dataclasses import dataclass
from dataclasses import field
from struct import pack
from typing import Any
from typing import Optional
from logitech_receiver import device
from logitech_receiver import hidpp20
def open_path(path: Optional[str]) -> Optional[int]:
return int(path, 16) if path is not None else None
@ -117,3 +123,41 @@ r_mouse_3 = [ # a HID++ 2.0 mouse
Response("414241424142414241424142414241", 0x0510, "00"), # name - first 15 characters
Response("444544000000000000000000000000", 0x0510, "0F"), # name - last 3 characters
]
@dataclass
class Device:
name: str = "TESTD"
online: bool = True
protocol: float = 2.0
codename: str = "TESTC"
responses: Any = field(default_factory=list)
feature: Optional[int] = None
features: Any = None
_backlight: Any = None
_keys: Any = None
def __post_init__(self):
self.features = hidpp20.FeaturesArray(self)
self.responses += [Response("010001", 0x0000, "0001"), Response("20", 0x0100)]
if self.feature is not None:
self.responses.append(Response("040001", 0x0000, f"{self.feature:0>4X}"))
def request(self, id, *params, no_reply=False):
if params is None:
params = []
params = b"".join(pack("B", p) if isinstance(p, int) else p for p in params)
print("REQUEST ", self.name, hex(id), params.hex())
for r in self.responses:
if id == r.id and params == bytes.fromhex(r.params):
print("RESPONSE", self.name, hex(r.id), r.params, r.response)
return bytes.fromhex(r.response) if r.response is not None else None
def feature_request(self, feature, function=0x00, *params, no_reply=False):
if self.protocol >= 2.0:
return hidpp20.feature_request(self, feature, function, *params, no_reply=no_reply)
read_register = device.Device.read_register
write_register = device.Device.write_register
backlight = device.Device.backlight
keys = device.Device.keys

View File

@ -0,0 +1,442 @@
## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from dataclasses import dataclass
from struct import pack
from typing import Any
from typing import Optional
import pytest
from logitech_receiver import common
from logitech_receiver import hidpp20
from logitech_receiver import settings_templates
from solaar import configuration
from . import hidpp
# TODO OnboardProfiles, Report Rate, ExtendedReportRate, DpiSlidingXY, MouseGesturesXY, DivertKeys
# TODO SpeedChange and onward
@dataclass
class RegisterTest:
sclass: Any
initial_value: Any = False
write_value: Any = True
write_params: str = ""
tests = [
[
RegisterTest(settings_templates.RegisterHandDetection, False, True, [b"\x00\x00\x00"]),
hidpp.Response("000030", 0x8101), # keyboard_hand_detection
hidpp.Response("000000", 0x8001, "000000"),
],
[
RegisterTest(settings_templates.RegisterHandDetection, True, False, [b"\x00\x00\x30"]),
hidpp.Response("000000", 0x8101), # keyboard_hand_detection
hidpp.Response("000030", 0x8001, "000030"),
],
[
RegisterTest(settings_templates.RegisterSmoothScroll, False, True, [b"\x40"]),
hidpp.Response("00", 0x8101), # mouse_button_flags
hidpp.Response("40", 0x8001, "40"),
],
[
RegisterTest(settings_templates.RegisterSideScroll, True, False, [b"\x00"]),
hidpp.Response("02", 0x8101), # mouse_button_flags
hidpp.Response("00", 0x8001, "00"),
],
[
RegisterTest(settings_templates.RegisterFnSwap, False, True, [b"\x00\x01"]),
hidpp.Response("0000", 0x8109), # keyboard_fn_swap
hidpp.Response("0001", 0x8009, "0001"),
],
[
RegisterTest(
settings_templates._PerformanceMXDpi, common.NamedInt(0x88, "800"), common.NamedInt(0x89, "900"), [b"\x89"]
),
hidpp.Response("88", 0x8163), # mouse_dpi
hidpp.Response("89", 0x8063, "89"),
],
]
@pytest.mark.parametrize("test", tests)
def test_register_template(test, mocker):
device = hidpp.Device(responses=test[1:])
device.persister = configuration._DeviceEntry()
device.protocol = 1.0
spy_request = mocker.spy(device, "request")
setting = test[0].sclass.build(device)
value = setting.read(cached=False)
cached_value = setting.read(cached=True)
write_value = setting.write(test[0].write_value)
assert setting is not None
assert value == test[0].initial_value
assert cached_value == test[0].initial_value
assert write_value == test[0].write_value
spy_request.assert_called_with(test[0].sclass.register + 0x8000, *test[0].write_params)
@dataclass
class FeatureTest:
sclass: Any
initial_value: Any = False
write_value: Any = True
write_fnid: int = 0x10
write_params: str = ""
no_reply: Optional[bool] = False
tests = [
[
FeatureTest(settings_templates.K375sFnSwap, False, True, 0x10, "FF01"),
hidpp.Response("060001", 0x0000, "40A3"), # K375_FN_INVERSION
hidpp.Response("FF0001", 0x0600, "FF"),
hidpp.Response("FF0101", 0x0610, "FF01"),
],
[
FeatureTest(settings_templates.FnSwap, True, False, 0x10, "00"),
hidpp.Response("040001", 0x0000, "40A0"), # FN_INVERSION
hidpp.Response("01", 0x0400),
hidpp.Response("00", 0x0410, "00"),
],
[
FeatureTest(settings_templates.NewFnSwap, True, False, 0x10, "00"),
hidpp.Response("040001", 0x0000, "40A2"), # NEW_FN_INVERSION
hidpp.Response("01", 0x0400),
hidpp.Response("00", 0x0410, "00"),
],
[
FeatureTest(settings_templates.Backlight, 0, 5, 0x10, "05"),
hidpp.Response("060001", 0x0000, "1981"), # BACKLIGHT
hidpp.Response("00", 0x0600),
hidpp.Response("05", 0x0610, "05"),
],
[
FeatureTest(settings_templates.Backlight2DurationHandsOut, 0x20, 0x40, 0x10, "0118FF000D0040006000", None),
hidpp.Response("040003", 0x0000, "1982"), # BACKLIGHT2
hidpp.Response("011830000000200040006000", 0x0400),
hidpp.Response("0118FF000000040004000600", 0x0410, "0118FF000D0040006000"),
],
[
FeatureTest(settings_templates.Backlight2DurationPowered, 0x60, 0x70, 0x10, "0118FF00200040001700", None),
hidpp.Response("040003", 0x0000, "1982"), # BACKLIGHT2
hidpp.Response("011830000000200040006000", 0x0400),
hidpp.Response("0118FF00200040001700", 0x0410, "0118FF00200040001700"),
],
[
FeatureTest(settings_templates.HiResScroll, True, False, 0x10, "00"),
hidpp.Response("040001", 0x0000, "2120"), # HI_RES_SCROLLING
hidpp.Response("01", 0x0400),
hidpp.Response("00", 0x0410, "00"),
],
[
FeatureTest(settings_templates.LowresMode, False, True, 0x10, "01"),
hidpp.Response("040001", 0x0000, "2130"), # LOWRES_WHEEL
hidpp.Response("00", 0x0400),
hidpp.Response("01", 0x0410, "01"),
],
[
FeatureTest(settings_templates.HiresSmoothInvert, True, False, 0x20, "02"),
hidpp.Response("040001", 0x0000, "2121"), # HIRES_WHEEL
hidpp.Response("06", 0x0410),
hidpp.Response("02", 0x0420, "02"),
],
[
FeatureTest(settings_templates.HiresSmoothResolution, True, False, 0x20, "04"),
hidpp.Response("040001", 0x0000, "2121"), # HIRES_WHEEL
hidpp.Response("06", 0x0410),
hidpp.Response("04", 0x0420, "04"),
],
[
FeatureTest(settings_templates.HiresMode, False, True, 0x20, "07"),
hidpp.Response("040001", 0x0000, "2121"), # HIRES_WHEEL
hidpp.Response("06", 0x0410),
hidpp.Response("07", 0x0420, "07"),
],
[
FeatureTest(settings_templates.PointerSpeed, 0x0100, 0x0120, 0x10, "0120"),
hidpp.Response("040001", 0x0000, "2205"), # POINTER_SPEED
hidpp.Response("0100", 0x0400),
hidpp.Response("0120", 0x0410, "0120"),
],
[
FeatureTest(settings_templates.ThumbMode, True, False, 0x20, "0000"),
hidpp.Response("040001", 0x0000, "2150"), # THUMB_WHEEL
hidpp.Response("0100", 0x0410),
hidpp.Response("0000", 0x0420, "0000"),
],
[
FeatureTest(settings_templates.ThumbInvert, False, True, 0x20, "0101"),
hidpp.Response("040001", 0x0000, "2150"), # THUMB_WHEEL
hidpp.Response("0100", 0x0410),
hidpp.Response("0101", 0x0420, "0101"),
],
[
FeatureTest(settings_templates.DivertCrown, False, True, 0x20, "02"),
hidpp.Response("040001", 0x0000, "4600"), # CROWN
hidpp.Response("01", 0x0410),
hidpp.Response("02", 0x0420, "02"),
],
[
FeatureTest(settings_templates.CrownSmooth, True, False, 0x20, "0002"),
hidpp.Response("040001", 0x0000, "4600"), # CROWN
hidpp.Response("0001", 0x0410),
hidpp.Response("0002", 0x0420, "0002"),
],
[
FeatureTest(settings_templates.DivertGkeys, False, True, 0x20, "01"),
hidpp.Response("040001", 0x0000, "8010"), # GKEY
hidpp.Response("01", 0x0420, "01"),
],
[
FeatureTest(settings_templates.ScrollRatchet, 2, 1, 0x10, "01"),
hidpp.Response("040001", 0x0000, "2110"), # SMART_SHIFT
hidpp.Response("02", 0x0400),
hidpp.Response("01", 0x0410, "01"),
],
[
FeatureTest(settings_templates.SmartShift, 1, 10, 0x10, "000A"),
hidpp.Response("040001", 0x0000, "2110"), # SMART_SHIFT
hidpp.Response("0100", 0x0400),
hidpp.Response("000A", 0x0410, "000A"),
],
[
FeatureTest(settings_templates.SmartShift, 5, 50, 0x10, "00FF"),
hidpp.Response("040001", 0x0000, "2110"), # SMART_SHIFT
hidpp.Response("0005", 0x0400),
hidpp.Response("00FF", 0x0410, "00FF"),
],
[
FeatureTest(settings_templates.SmartShiftEnhanced, 5, 50, 0x20, "00FF"),
hidpp.Response("040001", 0x0000, "2111"), # SMART_SHIFT_ENHANCED
hidpp.Response("0005", 0x0410),
hidpp.Response("00FF", 0x0420, "00FF"),
],
]
@pytest.mark.parametrize("test", tests)
def test_simple_template(test, mocker):
setup_responses = [hidpp.Response("010001", 0x0000, "0001"), hidpp.Response("20", 0x0100)]
device = hidpp.Device(responses=test[1:] + setup_responses)
device.persister = configuration._DeviceEntry()
device.features = hidpp20.FeaturesArray(device)
spy_feature_request = mocker.spy(device, "feature_request")
setting = settings_templates.check_feature(device, test[0].sclass)
value = setting.read(cached=False)
cached_value = setting.read(cached=True)
write_value = setting.write(test[0].write_value)
assert setting is not None
assert value == test[0].initial_value
assert cached_value == test[0].initial_value
assert write_value == test[0].write_value
params = bytes.fromhex(test[0].write_params)
no_reply = {"no_reply": test[0].no_reply} if test[0].no_reply is not None else {}
spy_feature_request.assert_called_with(test[0].sclass.feature, test[0].write_fnid, params, **no_reply)
tests = [
[
FeatureTest(settings_templates.Backlight2, 0xFF, 0x00, 0x10, "0102ff00000000000000", None),
common.NamedInts(Disabled=0xFF, Enabled=0x00),
hidpp.Response("040001", 0x0000, "1982"), # BACKLIGHT2
hidpp.Response("000201000000000000000000", 0x0400),
hidpp.Response("010201", 0x0410, "0102ff00000000000000"),
],
[
FeatureTest(settings_templates.Backlight2, 0x03, 0xFF, 0x10, "0018ff00000000000000", None),
common.NamedInts(Disabled=0xFF, Manual=0x03),
hidpp.Response("040001", 0x0000, "1982"), # BACKLIGHT2
hidpp.Response("011830000000000000000000", 0x0400),
hidpp.Response("001801", 0x0410, "0018ff00000000000000"),
],
[
FeatureTest(settings_templates.Backlight2Level, 0, 3, 0x10, "0118ff03000000000000", None),
[0, 4],
hidpp.Response("040003", 0x0000, "1982"), # BACKLIGHT2
hidpp.Response("011830000000000000000000", 0x0400),
hidpp.Response("01180103000000000000", 0x0410, "0118ff03000000000000"),
hidpp.Response("05", 0x0420),
],
[
FeatureTest(settings_templates.Backlight2Level, 0, 2, 0x10, "0118ff02000000000000", None),
[0, 4],
hidpp.Response("040003", 0x0000, "1982"), # BACKLIGHT2
hidpp.Response("011830000000000000000000", 0x0400),
hidpp.Response("01180102000000000000", 0x0410, "0118ff02000000000000"),
hidpp.Response("05", 0x0420),
],
[
FeatureTest(settings_templates.AdjustableDpi, 800, 400, 0x30, "000190"),
common.NamedInts.list([400, 800, 1600]),
hidpp.Response("040003", 0x0000, "2201"), # ADJUSTABLE_DPI
hidpp.Response("000190032006400000", 0x0410, "000000"),
hidpp.Response("000320", 0x0420),
hidpp.Response("000190", 0x0430, "000190"),
],
[
FeatureTest(settings_templates.AdjustableDpi, 256, 512, 0x30, "000200"),
common.NamedInts.list([256, 512]),
hidpp.Response("040003", 0x0000, "2201"), # ADJUSTABLE_DPI
hidpp.Response("000100e10002000000", 0x0410, "000000"),
hidpp.Response("000100", 0x0420),
hidpp.Response("000200", 0x0430, "000200"),
],
[
FeatureTest(settings_templates.ExtendedAdjustableDpi, 256, 512, 0x60, "000200"),
common.NamedInts.list([256, 512]),
hidpp.Response("090000", 0x0000, "2202"), # EXTENDED_ADJUSTABLE_DPI
hidpp.Response("0000000100e10002000000", 0x0920, "000000"),
hidpp.Response("000100", 0x0950),
hidpp.Response("000200", 0x0960, "000200"),
],
]
@pytest.mark.parametrize("test", tests)
def test_variable_template(test, mocker):
setup_responses = [hidpp.Response("010001", 0x0000, "0001"), hidpp.Response("20", 0x0100)]
device = hidpp.Device(responses=test[2:] + setup_responses)
device.persister = configuration._DeviceEntry()
device.features = hidpp20.FeaturesArray(device)
spy_feature_request = mocker.spy(device, "feature_request")
setting = settings_templates.check_feature(device, test[0].sclass)
value = setting.read(cached=False)
cached_value = setting.read(cached=True)
write_value = setting.write(test[0].write_value)
assert setting is not None
if isinstance(test[1], common.NamedInts):
assert len(setting.choices) == len(test[1])
for setting_choice, expected_choice in zip(setting.choices, test[1]):
assert setting_choice == expected_choice
if isinstance(test[1], list):
assert setting._validator.min_value == test[1][0]
assert setting._validator.max_value == test[1][1]
assert value == test[0].initial_value
assert cached_value == test[0].initial_value
assert write_value == test[0].write_value
params = bytes.fromhex(test[0].write_params)
no_reply = {"no_reply": test[0].no_reply} if test[0].no_reply is not None else {}
spy_feature_request.assert_called_with(test[0].sclass.feature, test[0].write_fnid, params, **no_reply)
tests = [
[
FeatureTest(
settings_templates.ReprogrammableKeys, {0x50: 0x50, 0x51: 0x50, 0xC4: 0xC4}, {0x51: 0x51}, 0x30, "0051000051"
),
{
common.NamedInt(0x50, "Left Button"): [0x50, 0x51],
common.NamedInt(0x51, "Right Button"): [0x51, 0x50],
common.NamedInt(0xC4, "Smart Shift"): [0xC4, 0x50, 0x51],
},
hidpp.Response("050001", 0x0000, "1B04"), # REPROG_CONTROLS_V4
hidpp.Response("03", 0x0500),
hidpp.Response("00500038010001010400000000000000", 0x0510, "00"), # left button
hidpp.Response("00510039010001010400000000000000", 0x0510, "01"), # right button
hidpp.Response("00C4009D310003070500000000000000", 0x0510, "02"), # smart shift
hidpp.Response("00500000000000000000000000000000", 0x0520, "0050"), # left button current
hidpp.Response("00510000500000000000000000000000", 0x0520, "0051"), # right button current
hidpp.Response("00C40000000000000000000000000000", 0x0520, "00C4"), # smart shift current
],
]
@pytest.mark.parametrize("test", tests)
def test_key_template(test, mocker):
setup_responses = [hidpp.Response("010001", 0x0000, "0001"), hidpp.Response("20", 0x0100)]
device = hidpp.Device(responses=test[2:] + setup_responses)
device.persister = configuration._DeviceEntry()
device.features = hidpp20.FeaturesArray(device)
spy_feature_request = mocker.spy(device, "feature_request")
setting = settings_templates.check_feature(device, test[0].sclass)
assert setting is not None
assert len(setting.choices) == len(test[1])
for k, v in setting.choices.items():
assert len(v) == len(test[1][k])
for setting_key, test_key in zip(v, test[1][k]):
assert setting_key == test_key
value = setting.read(cached=False)
for k, v in test[0].initial_value.items():
assert value[k] == v
for key, value in test[0].write_value.items():
write_value = setting.write_key_value(key, value)
assert write_value == value
assert spy_feature_request.call_args_list[-1][0][0] == test[0].sclass.feature
assert spy_feature_request.call_args_list[-1][0][1] == test[0].write_fnid
param = b"".join(pack("B", p) if isinstance(p, int) else p for p in spy_feature_request.call_args_list[-1][0][2:])
assert param == bytes.fromhex(test[0].write_params)
tests = [ # needs settings to be set up!!
[
FeatureTest(settings_templates.SpeedChange, 0, 0xED, 0x60, "000200"),
common.NamedInts(**{"Off": 0, "DPI Change": 0xED}),
hidpp.Response("040001", 0x0000, "2205"), # POINTER_SPEED
hidpp.Response("0100", 0x0400),
hidpp.Response("0120", 0x0410, "0120"),
hidpp.Response("050001", 0x0000, "1B04"), # REPROG_CONTROLS_V4
hidpp.Response("01", 0x0500),
hidpp.Response("00ED009D310003070500000000000000", 0x0510, "00"), # DPI Change
hidpp.Response("00ED0000000000000000000000000000", 0x0520, "00ED"), # DPI Change current
],
]
@pytest.mark.parametrize("test", tests)
def XX_action_template(test, mocker): # needs settings to be set up!!
setup_responses = [hidpp.Response("010001", 0x0000, "0001"), hidpp.Response("20", 0x0100)]
device = hidpp.Device(responses=test[2:] + setup_responses)
device.persister = configuration._DeviceEntry()
device.features = hidpp20.FeaturesArray(device)
spy_feature_request = mocker.spy(device, "feature_request")
setting = settings_templates.check_feature(device, test[0].sclass)
print("SETTING", setting)
value = setting.read(cached=False)
cached_value = setting.read(cached=True)
write_value = setting.write(test[0].write_value)
assert setting is not None
if isinstance(test[1], common.NamedInts):
assert len(setting.choices) == len(test[1])
for setting_choice, expected_choice in zip(setting.choices, test[1]):
assert setting_choice == expected_choice
if isinstance(test[1], list):
assert setting._validator.min_value == test[1][0]
assert setting._validator.max_value == test[1][1]
assert value == test[0].initial_value
assert cached_value == test[0].initial_value
assert write_value == test[0].write_value
params = bytes.fromhex(test[0].write_params)
no_reply = {"no_reply": test[0].no_reply} if test[0].no_reply is not None else {}
spy_feature_request.assert_called_with(test[0].sclass.feature, test[0].write_fnid, params, **no_reply)