From d52f52069fed639113b1014cde5eff8902d3064d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 6 Dec 2023 11:45:56 +0100 Subject: [PATCH] selftests/hid: tablets: move the transitions to PenState Those transitions have nothing to do with `Pen`, so migrate them to `PenState`. The hidden agenda is to remove `Pen` and integrate it into `PenDigitizer` so that we can tweak the events in each state to emulate firmware bugs. Reviewed-by: Peter Hutterer Acked-by: Jiri Kosina Link: https://lore.kernel.org/r/20231206-wip-selftests-v2-5-c0350c2f5986@kernel.org Signed-off-by: Benjamin Tissoires --- .../selftests/hid/tests/test_tablet.py | 273 +++++++++--------- 1 file changed, 138 insertions(+), 135 deletions(-) diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py index cd9c1269afa6..ddf28c245046 100644 --- a/tools/testing/selftests/hid/tests/test_tablet.py +++ b/tools/testing/selftests/hid/tests/test_tablet.py @@ -132,6 +132,136 @@ class PenState(Enum): return tuple() + @staticmethod + def legal_transitions() -> Dict[str, Tuple["PenState", ...]]: + """This is the first half of the Windows Pen Implementation state machine: + we don't have Invert nor Erase bits, so just move in/out-of-range or proximity. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + return { + "in-range": (PenState.PEN_IS_IN_RANGE,), + "in-range -> out-of-range": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_OUT_OF_RANGE, + ), + "in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT), + "in-range -> touch -> release": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + ), + "in-range -> touch -> release -> out-of-range": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_OUT_OF_RANGE, + ), + } + + @staticmethod + def legal_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]: + """This is the second half of the Windows Pen Implementation state machine: + we now have Invert and Erase bits, so move in/out or proximity with the intend + to erase. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + return { + "hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,), + "hover-erasing -> out-of-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + ), + "hover-erasing -> erase": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + ), + "hover-erasing -> erase -> release": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + "hover-erasing -> erase -> release -> out-of-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_OUT_OF_RANGE, + ), + "hover-erasing -> in-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_IN_RANGE, + ), + "in-range -> hover-erasing": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + } + + @staticmethod + def tolerated_transitions() -> Dict[str, Tuple["PenState", ...]]: + """This is not adhering to the Windows Pen Implementation state machine + but we should expect the kernel to behave properly, mostly for historical + reasons.""" + return { + "direct-in-contact": (PenState.PEN_IS_IN_CONTACT,), + "direct-in-contact -> out-of-range": ( + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_OUT_OF_RANGE, + ), + } + + @staticmethod + def tolerated_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]: + """This is the second half of the Windows Pen Implementation state machine: + we now have Invert and Erase bits, so move in/out or proximity with the intend + to erase. + https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states + """ + return { + "direct-erase": (PenState.PEN_IS_ERASING,), + "direct-erase -> out-of-range": ( + PenState.PEN_IS_ERASING, + PenState.PEN_IS_OUT_OF_RANGE, + ), + } + + @staticmethod + def broken_transitions() -> Dict[str, Tuple["PenState", ...]]: + """Those tests are definitely not part of the Windows specification. + However, a half broken device might export those transitions. + For example, a pen that has the eraser button might wobble between + touching and erasing if the tablet doesn't enforce the Windows + state machine.""" + return { + "in-range -> touch -> erase -> hover-erase": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + "in-range -> erase -> hover-erase": ( + PenState.PEN_IS_IN_RANGE, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + ), + "hover-erase -> erase -> touch -> in-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + ), + "hover-erase -> touch -> in-range": ( + PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_IN_RANGE, + ), + "touch -> erase -> touch -> erase": ( + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + PenState.PEN_IS_IN_CONTACT, + PenState.PEN_IS_ERASING, + ), + } + class Pen(object): def __init__(self, x, y): @@ -228,136 +358,6 @@ class Pen(object): assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y assert self.current_state == PenState.from_evdev(evdev) - @staticmethod - def legal_transitions() -> Dict[str, Tuple[PenState, ...]]: - """This is the first half of the Windows Pen Implementation state machine: - we don't have Invert nor Erase bits, so just move in/out-of-range or proximity. - https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states - """ - return { - "in-range": (PenState.PEN_IS_IN_RANGE,), - "in-range -> out-of-range": ( - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_OUT_OF_RANGE, - ), - "in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT), - "in-range -> touch -> release": ( - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_IN_RANGE, - ), - "in-range -> touch -> release -> out-of-range": ( - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_OUT_OF_RANGE, - ), - } - - @staticmethod - def legal_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]: - """This is the second half of the Windows Pen Implementation state machine: - we now have Invert and Erase bits, so move in/out or proximity with the intend - to erase. - https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states - """ - return { - "hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,), - "hover-erasing -> out-of-range": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_OUT_OF_RANGE, - ), - "hover-erasing -> erase": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_ERASING, - ), - "hover-erasing -> erase -> release": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_ERASING, - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - ), - "hover-erasing -> erase -> release -> out-of-range": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_ERASING, - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_OUT_OF_RANGE, - ), - "hover-erasing -> in-range": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_IN_RANGE, - ), - "in-range -> hover-erasing": ( - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - ), - } - - @staticmethod - def tolerated_transitions() -> Dict[str, Tuple[PenState, ...]]: - """This is not adhering to the Windows Pen Implementation state machine - but we should expect the kernel to behave properly, mostly for historical - reasons.""" - return { - "direct-in-contact": (PenState.PEN_IS_IN_CONTACT,), - "direct-in-contact -> out-of-range": ( - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_OUT_OF_RANGE, - ), - } - - @staticmethod - def tolerated_transitions_with_invert() -> Dict[str, Tuple[PenState, ...]]: - """This is the second half of the Windows Pen Implementation state machine: - we now have Invert and Erase bits, so move in/out or proximity with the intend - to erase. - https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states - """ - return { - "direct-erase": (PenState.PEN_IS_ERASING,), - "direct-erase -> out-of-range": ( - PenState.PEN_IS_ERASING, - PenState.PEN_IS_OUT_OF_RANGE, - ), - } - - @staticmethod - def broken_transitions() -> Dict[str, Tuple[PenState, ...]]: - """Those tests are definitely not part of the Windows specification. - However, a half broken device might export those transitions. - For example, a pen that has the eraser button might wobble between - touching and erasing if the tablet doesn't enforce the Windows - state machine.""" - return { - "in-range -> touch -> erase -> hover-erase": ( - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_ERASING, - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - ), - "in-range -> erase -> hover-erase": ( - PenState.PEN_IS_IN_RANGE, - PenState.PEN_IS_ERASING, - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - ), - "hover-erase -> erase -> touch -> in-range": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_ERASING, - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_IN_RANGE, - ), - "hover-erase -> touch -> in-range": ( - PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT, - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_IN_RANGE, - ), - "touch -> erase -> touch -> erase": ( - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_ERASING, - PenState.PEN_IS_IN_CONTACT, - PenState.PEN_IS_ERASING, - ), - } - class PenDigitizer(base.UHIDTestDevice): def __init__( @@ -486,7 +486,7 @@ class BaseTest: @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) @pytest.mark.parametrize( "state_list", - [pytest.param(v, id=k) for k, v in Pen.legal_transitions().items()], + [pytest.param(v, id=k) for k, v in PenState.legal_transitions().items()], ) def test_valid_pen_states(self, state_list, scribble): """This is the first half of the Windows Pen Implementation state machine: @@ -498,7 +498,10 @@ class BaseTest: @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) @pytest.mark.parametrize( "state_list", - [pytest.param(v, id=k) for k, v in Pen.tolerated_transitions().items()], + [ + pytest.param(v, id=k) + for k, v in PenState.tolerated_transitions().items() + ], ) def test_tolerated_pen_states(self, state_list, scribble): """This is not adhering to the Windows Pen Implementation state machine @@ -515,7 +518,7 @@ class BaseTest: "state_list", [ pytest.param(v, id=k) - for k, v in Pen.legal_transitions_with_invert().items() + for k, v in PenState.legal_transitions_with_invert().items() ], ) def test_valid_invert_pen_states(self, state_list, scribble): @@ -535,7 +538,7 @@ class BaseTest: "state_list", [ pytest.param(v, id=k) - for k, v in Pen.tolerated_transitions_with_invert().items() + for k, v in PenState.tolerated_transitions_with_invert().items() ], ) def test_tolerated_invert_pen_states(self, state_list, scribble): @@ -553,7 +556,7 @@ class BaseTest: @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"]) @pytest.mark.parametrize( "state_list", - [pytest.param(v, id=k) for k, v in Pen.broken_transitions().items()], + [pytest.param(v, id=k) for k, v in PenState.broken_transitions().items()], ) def test_tolerated_broken_pen_states(self, state_list, scribble): """Those tests are definitely not part of the Windows specification.