1ffb85d5cSBenjamin Tissoires#!/bin/env python3 2ffb85d5cSBenjamin Tissoires# SPDX-License-Identifier: GPL-2.0 3ffb85d5cSBenjamin Tissoires# -*- coding: utf-8 -*- 4ffb85d5cSBenjamin Tissoires# 5ffb85d5cSBenjamin Tissoires# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com> 6ffb85d5cSBenjamin Tissoires# Copyright (c) 2017 Red Hat, Inc. 7ffb85d5cSBenjamin Tissoires 8ffb85d5cSBenjamin Tissoiresimport libevdev 9ffb85d5cSBenjamin Tissoiresimport os 10ffb85d5cSBenjamin Tissoiresimport pytest 11*89ea968aSBenjamin Tissoiresimport shutil 12e9064630SBenjamin Tissoiresimport subprocess 13ffb85d5cSBenjamin Tissoiresimport time 14ffb85d5cSBenjamin Tissoires 15ffb85d5cSBenjamin Tissoiresimport logging 16ffb85d5cSBenjamin Tissoires 17a7def2e5SBenjamin Tissoiresfrom .base_device import BaseDevice, EvdevMatch, SysfsFile 18ffb85d5cSBenjamin Tissoiresfrom pathlib import Path 19ed5bc56cSBenjamin Tissoiresfrom typing import Final, List, Tuple 20ffb85d5cSBenjamin Tissoires 21ffb85d5cSBenjamin Tissoireslogger = logging.getLogger("hidtools.test.base") 22ffb85d5cSBenjamin Tissoires 23ffb85d5cSBenjamin Tissoires# application to matches 24ffb85d5cSBenjamin Tissoiresapplication_matches: Final = { 25ffb85d5cSBenjamin Tissoires # pyright: ignore 26ffb85d5cSBenjamin Tissoires "Accelerometer": EvdevMatch( 27ffb85d5cSBenjamin Tissoires req_properties=[ 28ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 29ffb85d5cSBenjamin Tissoires ] 30ffb85d5cSBenjamin Tissoires ), 31ffb85d5cSBenjamin Tissoires "Game Pad": EvdevMatch( # in systemd, this is a lot more complex, but that will do 32ffb85d5cSBenjamin Tissoires requires=[ 33ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_X, 34ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_Y, 35ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_RX, 36ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_RY, 37ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_START, 38ffb85d5cSBenjamin Tissoires ], 39ffb85d5cSBenjamin Tissoires excl_properties=[ 40ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 41ffb85d5cSBenjamin Tissoires ], 42ffb85d5cSBenjamin Tissoires ), 43ffb85d5cSBenjamin Tissoires "Joystick": EvdevMatch( # in systemd, this is a lot more complex, but that will do 44ffb85d5cSBenjamin Tissoires requires=[ 45ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_RX, 46ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_RY, 47ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_START, 48ffb85d5cSBenjamin Tissoires ], 49ffb85d5cSBenjamin Tissoires excl_properties=[ 50ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 51ffb85d5cSBenjamin Tissoires ], 52ffb85d5cSBenjamin Tissoires ), 53ffb85d5cSBenjamin Tissoires "Key": EvdevMatch( 54ffb85d5cSBenjamin Tissoires requires=[ 55ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.KEY_A, 56ffb85d5cSBenjamin Tissoires ], 57ffb85d5cSBenjamin Tissoires excl_properties=[ 58ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 59ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_DIRECT, 60ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_POINTER, 61ffb85d5cSBenjamin Tissoires ], 62ffb85d5cSBenjamin Tissoires ), 63ffb85d5cSBenjamin Tissoires "Mouse": EvdevMatch( 64ffb85d5cSBenjamin Tissoires requires=[ 65ffb85d5cSBenjamin Tissoires libevdev.EV_REL.REL_X, 66ffb85d5cSBenjamin Tissoires libevdev.EV_REL.REL_Y, 67ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_LEFT, 68ffb85d5cSBenjamin Tissoires ], 69ffb85d5cSBenjamin Tissoires excl_properties=[ 70ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 71ffb85d5cSBenjamin Tissoires ], 72ffb85d5cSBenjamin Tissoires ), 73ffb85d5cSBenjamin Tissoires "Pad": EvdevMatch( 74ffb85d5cSBenjamin Tissoires requires=[ 75ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_0, 76ffb85d5cSBenjamin Tissoires ], 77ffb85d5cSBenjamin Tissoires excludes=[ 78ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_TOOL_PEN, 79ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_TOUCH, 80ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_DISTANCE, 81ffb85d5cSBenjamin Tissoires ], 82ffb85d5cSBenjamin Tissoires excl_properties=[ 83ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 84ffb85d5cSBenjamin Tissoires ], 85ffb85d5cSBenjamin Tissoires ), 86ffb85d5cSBenjamin Tissoires "Pen": EvdevMatch( 87ffb85d5cSBenjamin Tissoires requires=[ 88ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_STYLUS, 89ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_X, 90ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_Y, 91ffb85d5cSBenjamin Tissoires ], 92ffb85d5cSBenjamin Tissoires excl_properties=[ 93ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 94ffb85d5cSBenjamin Tissoires ], 95ffb85d5cSBenjamin Tissoires ), 96ffb85d5cSBenjamin Tissoires "Stylus": EvdevMatch( 97ffb85d5cSBenjamin Tissoires requires=[ 98ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_STYLUS, 99ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_X, 100ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_Y, 101ffb85d5cSBenjamin Tissoires ], 102ffb85d5cSBenjamin Tissoires excl_properties=[ 103ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 104ffb85d5cSBenjamin Tissoires ], 105ffb85d5cSBenjamin Tissoires ), 106ffb85d5cSBenjamin Tissoires "Touch Pad": EvdevMatch( 107ffb85d5cSBenjamin Tissoires requires=[ 108ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_LEFT, 109ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_X, 110ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_Y, 111ffb85d5cSBenjamin Tissoires ], 112ffb85d5cSBenjamin Tissoires excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS], 113ffb85d5cSBenjamin Tissoires req_properties=[ 114ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_POINTER, 115ffb85d5cSBenjamin Tissoires ], 116ffb85d5cSBenjamin Tissoires excl_properties=[ 117ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 118ffb85d5cSBenjamin Tissoires ], 119ffb85d5cSBenjamin Tissoires ), 120ffb85d5cSBenjamin Tissoires "Touch Screen": EvdevMatch( 121ffb85d5cSBenjamin Tissoires requires=[ 122ffb85d5cSBenjamin Tissoires libevdev.EV_KEY.BTN_TOUCH, 123ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_X, 124ffb85d5cSBenjamin Tissoires libevdev.EV_ABS.ABS_Y, 125ffb85d5cSBenjamin Tissoires ], 126ffb85d5cSBenjamin Tissoires excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS], 127ffb85d5cSBenjamin Tissoires req_properties=[ 128ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_DIRECT, 129ffb85d5cSBenjamin Tissoires ], 130ffb85d5cSBenjamin Tissoires excl_properties=[ 131ffb85d5cSBenjamin Tissoires libevdev.INPUT_PROP_ACCELEROMETER, 132ffb85d5cSBenjamin Tissoires ], 133ffb85d5cSBenjamin Tissoires ), 134ffb85d5cSBenjamin Tissoires} 135ffb85d5cSBenjamin Tissoires 136ffb85d5cSBenjamin Tissoires 137ffb85d5cSBenjamin Tissoiresclass UHIDTestDevice(BaseDevice): 138ffb85d5cSBenjamin Tissoires def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None): 139ffb85d5cSBenjamin Tissoires super().__init__(name, application, rdesc_str, rdesc, input_info) 140ffb85d5cSBenjamin Tissoires self.application_matches = application_matches 141ffb85d5cSBenjamin Tissoires if name is None: 142ffb85d5cSBenjamin Tissoires name = f"uhid test {self.__class__.__name__}" 143ffb85d5cSBenjamin Tissoires if not name.startswith("uhid test "): 144ffb85d5cSBenjamin Tissoires name = "uhid test " + self.name 145ffb85d5cSBenjamin Tissoires self.name = name 146ffb85d5cSBenjamin Tissoires 147ffb85d5cSBenjamin Tissoires 148ffb85d5cSBenjamin Tissoiresclass BaseTestCase: 149ffb85d5cSBenjamin Tissoires class TestUhid(object): 150ffb85d5cSBenjamin Tissoires syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) # type: ignore 151ffb85d5cSBenjamin Tissoires key_event = libevdev.InputEvent(libevdev.EV_KEY) # type: ignore 152ffb85d5cSBenjamin Tissoires abs_event = libevdev.InputEvent(libevdev.EV_ABS) # type: ignore 153ffb85d5cSBenjamin Tissoires rel_event = libevdev.InputEvent(libevdev.EV_REL) # type: ignore 154ffb85d5cSBenjamin Tissoires msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN) # type: ignore 155ffb85d5cSBenjamin Tissoires 156ffb85d5cSBenjamin Tissoires # List of kernel modules to load before starting the test 157ffb85d5cSBenjamin Tissoires # if any module is not available (not compiled), the test will skip. 158ffb85d5cSBenjamin Tissoires # Each element is a tuple '(kernel driver name, kernel module)', 159ffb85d5cSBenjamin Tissoires # for example ("playstation", "hid-playstation") 160ed5bc56cSBenjamin Tissoires kernel_modules: List[Tuple[str, str]] = [] 161ffb85d5cSBenjamin Tissoires 162e9064630SBenjamin Tissoires # List of in kernel HID-BPF object files to load 163e9064630SBenjamin Tissoires # before starting the test 164e9064630SBenjamin Tissoires # Any existing pre-loaded HID-BPF module will be removed 165e9064630SBenjamin Tissoires # before the ones in this list will be manually loaded. 166e9064630SBenjamin Tissoires # Each Element is a tuple '(hid_bpf_object, rdesc_fixup_present)', 167e9064630SBenjamin Tissoires # for example '("xppen-ArtistPro16Gen2.bpf.o", True)' 168e9064630SBenjamin Tissoires # If 'rdesc_fixup_present' is True, the test needs to wait 169e9064630SBenjamin Tissoires # for one unbind and rebind before it can be sure the kernel is 170e9064630SBenjamin Tissoires # ready 171e9064630SBenjamin Tissoires hid_bpfs: List[Tuple[str, bool]] = [] 172e9064630SBenjamin Tissoires 173ffb85d5cSBenjamin Tissoires def assertInputEventsIn(self, expected_events, effective_events): 174ffb85d5cSBenjamin Tissoires effective_events = effective_events.copy() 175ffb85d5cSBenjamin Tissoires for ev in expected_events: 176ffb85d5cSBenjamin Tissoires assert ev in effective_events 177ffb85d5cSBenjamin Tissoires effective_events.remove(ev) 178ffb85d5cSBenjamin Tissoires return effective_events 179ffb85d5cSBenjamin Tissoires 180ffb85d5cSBenjamin Tissoires def assertInputEvents(self, expected_events, effective_events): 181ffb85d5cSBenjamin Tissoires remaining = self.assertInputEventsIn(expected_events, effective_events) 182ffb85d5cSBenjamin Tissoires assert remaining == [] 183ffb85d5cSBenjamin Tissoires 184ffb85d5cSBenjamin Tissoires @classmethod 185ffb85d5cSBenjamin Tissoires def debug_reports(cls, reports, uhdev=None, events=None): 186ffb85d5cSBenjamin Tissoires data = [" ".join([f"{v:02x}" for v in r]) for r in reports] 187ffb85d5cSBenjamin Tissoires 188ffb85d5cSBenjamin Tissoires if uhdev is not None: 189ffb85d5cSBenjamin Tissoires human_data = [ 190ffb85d5cSBenjamin Tissoires uhdev.parsed_rdesc.format_report(r, split_lines=True) 191ffb85d5cSBenjamin Tissoires for r in reports 192ffb85d5cSBenjamin Tissoires ] 193ffb85d5cSBenjamin Tissoires try: 194ffb85d5cSBenjamin Tissoires human_data = [ 195ffb85d5cSBenjamin Tissoires f'\n\t {" " * h.index("/")}'.join(h.split("\n")) 196ffb85d5cSBenjamin Tissoires for h in human_data 197ffb85d5cSBenjamin Tissoires ] 198ffb85d5cSBenjamin Tissoires except ValueError: 199ffb85d5cSBenjamin Tissoires # '/' not found: not a numbered report 200ffb85d5cSBenjamin Tissoires human_data = ["\n\t ".join(h.split("\n")) for h in human_data] 201ffb85d5cSBenjamin Tissoires data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)] 202ffb85d5cSBenjamin Tissoires 203ffb85d5cSBenjamin Tissoires reports = data 204ffb85d5cSBenjamin Tissoires 205ffb85d5cSBenjamin Tissoires if len(reports) == 1: 206ffb85d5cSBenjamin Tissoires print("sending 1 report:") 207ffb85d5cSBenjamin Tissoires else: 208ffb85d5cSBenjamin Tissoires print(f"sending {len(reports)} reports:") 209ffb85d5cSBenjamin Tissoires for report in reports: 210ffb85d5cSBenjamin Tissoires print("\t", report) 211ffb85d5cSBenjamin Tissoires 212ffb85d5cSBenjamin Tissoires if events is not None: 213ffb85d5cSBenjamin Tissoires print("events received:", events) 214ffb85d5cSBenjamin Tissoires 215ffb85d5cSBenjamin Tissoires def create_device(self): 216ffb85d5cSBenjamin Tissoires raise Exception("please reimplement me in subclasses") 217ffb85d5cSBenjamin Tissoires 218ffb85d5cSBenjamin Tissoires def _load_kernel_module(self, kernel_driver, kernel_module): 219ffb85d5cSBenjamin Tissoires sysfs_path = Path("/sys/bus/hid/drivers") 220ffb85d5cSBenjamin Tissoires if kernel_driver is not None: 221ffb85d5cSBenjamin Tissoires sysfs_path /= kernel_driver 222ffb85d5cSBenjamin Tissoires else: 223ffb85d5cSBenjamin Tissoires # special case for when testing all available modules: 224ffb85d5cSBenjamin Tissoires # we don't know beforehand the name of the module from modinfo 225ffb85d5cSBenjamin Tissoires sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_") 226ffb85d5cSBenjamin Tissoires if not sysfs_path.exists(): 227ffb85d5cSBenjamin Tissoires ret = subprocess.run(["/usr/sbin/modprobe", kernel_module]) 228ffb85d5cSBenjamin Tissoires if ret.returncode != 0: 229ffb85d5cSBenjamin Tissoires pytest.skip( 230ffb85d5cSBenjamin Tissoires f"module {kernel_module} could not be loaded, skipping the test" 231ffb85d5cSBenjamin Tissoires ) 232ffb85d5cSBenjamin Tissoires 233ffb85d5cSBenjamin Tissoires @pytest.fixture() 234ffb85d5cSBenjamin Tissoires def load_kernel_module(self): 235ffb85d5cSBenjamin Tissoires for kernel_driver, kernel_module in self.kernel_modules: 236ffb85d5cSBenjamin Tissoires self._load_kernel_module(kernel_driver, kernel_module) 237ffb85d5cSBenjamin Tissoires yield 238ffb85d5cSBenjamin Tissoires 239e9064630SBenjamin Tissoires def load_hid_bpfs(self): 240e9064630SBenjamin Tissoires script_dir = Path(os.path.dirname(os.path.realpath(__file__))) 241e9064630SBenjamin Tissoires root_dir = (script_dir / "../../../../..").resolve() 242e9064630SBenjamin Tissoires bpf_dir = root_dir / "drivers/hid/bpf/progs" 243e9064630SBenjamin Tissoires 244*89ea968aSBenjamin Tissoires udev_hid_bpf = shutil.which("udev-hid-bpf") 245*89ea968aSBenjamin Tissoires if not udev_hid_bpf: 246*89ea968aSBenjamin Tissoires pytest.skip("udev-hid-bpf not found in $PATH, skipping") 247*89ea968aSBenjamin Tissoires 248e9064630SBenjamin Tissoires wait = False 249e9064630SBenjamin Tissoires for _, rdesc_fixup in self.hid_bpfs: 250e9064630SBenjamin Tissoires if rdesc_fixup: 251e9064630SBenjamin Tissoires wait = True 252e9064630SBenjamin Tissoires 253e9064630SBenjamin Tissoires for hid_bpf, _ in self.hid_bpfs: 254e9064630SBenjamin Tissoires # We need to start `udev-hid-bpf` in the background 255e9064630SBenjamin Tissoires # and dispatch uhid events in case the kernel needs 256e9064630SBenjamin Tissoires # to fetch features on the device 257e9064630SBenjamin Tissoires process = subprocess.Popen( 258e9064630SBenjamin Tissoires [ 259e9064630SBenjamin Tissoires "udev-hid-bpf", 260e9064630SBenjamin Tissoires "--verbose", 261e9064630SBenjamin Tissoires "add", 262e9064630SBenjamin Tissoires str(self.uhdev.sys_path), 263e9064630SBenjamin Tissoires str(bpf_dir / hid_bpf), 264e9064630SBenjamin Tissoires ], 265e9064630SBenjamin Tissoires ) 266e9064630SBenjamin Tissoires while process.poll() is None: 267e9064630SBenjamin Tissoires self.uhdev.dispatch(1) 268e9064630SBenjamin Tissoires 269e9064630SBenjamin Tissoires if process.poll() != 0: 270e9064630SBenjamin Tissoires pytest.fail( 271e9064630SBenjamin Tissoires f"Couldn't insert hid-bpf program '{hid_bpf}', marking the test as failed" 272e9064630SBenjamin Tissoires ) 273e9064630SBenjamin Tissoires 274e9064630SBenjamin Tissoires if wait: 275e9064630SBenjamin Tissoires # the HID-BPF program exports a rdesc fixup, so it needs to be 276e9064630SBenjamin Tissoires # unbound by the kernel and then rebound. 277e9064630SBenjamin Tissoires # Ensure we get the bound event exactly 2 times (one for the normal 278e9064630SBenjamin Tissoires # uhid loading, and then the reload from HID-BPF) 279e9064630SBenjamin Tissoires now = time.time() 280e9064630SBenjamin Tissoires while self.uhdev.kernel_ready_count < 2 and time.time() - now < 2: 281e9064630SBenjamin Tissoires self.uhdev.dispatch(1) 282e9064630SBenjamin Tissoires 283e9064630SBenjamin Tissoires if self.uhdev.kernel_ready_count < 2: 284e9064630SBenjamin Tissoires pytest.fail( 285e9064630SBenjamin Tissoires f"Couldn't insert hid-bpf programs, marking the test as failed" 286e9064630SBenjamin Tissoires ) 287e9064630SBenjamin Tissoires 288e9064630SBenjamin Tissoires def unload_hid_bpfs(self): 289e9064630SBenjamin Tissoires ret = subprocess.run( 290e9064630SBenjamin Tissoires ["udev-hid-bpf", "--verbose", "remove", str(self.uhdev.sys_path)], 291e9064630SBenjamin Tissoires ) 292e9064630SBenjamin Tissoires if ret.returncode != 0: 293e9064630SBenjamin Tissoires pytest.fail( 294e9064630SBenjamin Tissoires f"Couldn't unload hid-bpf programs, marking the test as failed" 295e9064630SBenjamin Tissoires ) 296e9064630SBenjamin Tissoires 297ffb85d5cSBenjamin Tissoires @pytest.fixture() 298ffb85d5cSBenjamin Tissoires def new_uhdev(self, load_kernel_module): 299ffb85d5cSBenjamin Tissoires return self.create_device() 300ffb85d5cSBenjamin Tissoires 301ffb85d5cSBenjamin Tissoires def assertName(self, uhdev): 302ffb85d5cSBenjamin Tissoires evdev = uhdev.get_evdev() 303ffb85d5cSBenjamin Tissoires assert uhdev.name in evdev.name 304ffb85d5cSBenjamin Tissoires 305ffb85d5cSBenjamin Tissoires @pytest.fixture(autouse=True) 306ffb85d5cSBenjamin Tissoires def context(self, new_uhdev, request): 307ffb85d5cSBenjamin Tissoires try: 308ffb85d5cSBenjamin Tissoires with HIDTestUdevRule.instance(): 309ffb85d5cSBenjamin Tissoires with new_uhdev as self.uhdev: 310110292a7SBenjamin Tissoires for skip_cond in request.node.iter_markers("skip_if_uhdev"): 311ffb85d5cSBenjamin Tissoires test, message, *rest = skip_cond.args 312ffb85d5cSBenjamin Tissoires 313ffb85d5cSBenjamin Tissoires if test(self.uhdev): 314ffb85d5cSBenjamin Tissoires pytest.skip(message) 315ffb85d5cSBenjamin Tissoires 316ffb85d5cSBenjamin Tissoires self.uhdev.create_kernel_device() 317ffb85d5cSBenjamin Tissoires now = time.time() 318ffb85d5cSBenjamin Tissoires while not self.uhdev.is_ready() and time.time() - now < 5: 319ffb85d5cSBenjamin Tissoires self.uhdev.dispatch(1) 320e9064630SBenjamin Tissoires 321e9064630SBenjamin Tissoires if self.hid_bpfs: 322e9064630SBenjamin Tissoires self.load_hid_bpfs() 323e9064630SBenjamin Tissoires 324ffb85d5cSBenjamin Tissoires if self.uhdev.get_evdev() is None: 325ffb85d5cSBenjamin Tissoires logger.warning( 326ffb85d5cSBenjamin Tissoires f"available list of input nodes: (default application is '{self.uhdev.application}')" 327ffb85d5cSBenjamin Tissoires ) 328ffb85d5cSBenjamin Tissoires logger.warning(self.uhdev.input_nodes) 329ffb85d5cSBenjamin Tissoires yield 330e9064630SBenjamin Tissoires if self.hid_bpfs: 331e9064630SBenjamin Tissoires self.unload_hid_bpfs() 332ffb85d5cSBenjamin Tissoires self.uhdev = None 333ffb85d5cSBenjamin Tissoires except PermissionError: 334ffb85d5cSBenjamin Tissoires pytest.skip("Insufficient permissions, run me as root") 335ffb85d5cSBenjamin Tissoires 336ffb85d5cSBenjamin Tissoires @pytest.fixture(autouse=True) 337ffb85d5cSBenjamin Tissoires def check_taint(self): 338ffb85d5cSBenjamin Tissoires # we are abusing SysfsFile here, it's in /proc, but meh 339ffb85d5cSBenjamin Tissoires taint_file = SysfsFile("/proc/sys/kernel/tainted") 340ffb85d5cSBenjamin Tissoires taint = taint_file.int_value 341ffb85d5cSBenjamin Tissoires 342ffb85d5cSBenjamin Tissoires yield 343ffb85d5cSBenjamin Tissoires 344ffb85d5cSBenjamin Tissoires assert taint_file.int_value == taint 345ffb85d5cSBenjamin Tissoires 346ffb85d5cSBenjamin Tissoires def test_creation(self): 347ffb85d5cSBenjamin Tissoires """Make sure the device gets processed by the kernel and creates 348ffb85d5cSBenjamin Tissoires the expected application input node. 349ffb85d5cSBenjamin Tissoires 350ffb85d5cSBenjamin Tissoires If this fail, there is something wrong in the device report 351ffb85d5cSBenjamin Tissoires descriptors.""" 352ffb85d5cSBenjamin Tissoires uhdev = self.uhdev 353ffb85d5cSBenjamin Tissoires assert uhdev is not None 354ffb85d5cSBenjamin Tissoires assert uhdev.get_evdev() is not None 355ffb85d5cSBenjamin Tissoires self.assertName(uhdev) 356ffb85d5cSBenjamin Tissoires assert len(uhdev.next_sync_events()) == 0 357ffb85d5cSBenjamin Tissoires assert uhdev.get_evdev() is not None 358ffb85d5cSBenjamin Tissoires 359ffb85d5cSBenjamin Tissoires 360ffb85d5cSBenjamin Tissoiresclass HIDTestUdevRule(object): 361ffb85d5cSBenjamin Tissoires _instance = None 362ffb85d5cSBenjamin Tissoires """ 363ffb85d5cSBenjamin Tissoires A context-manager compatible class that sets up our udev rules file and 364ffb85d5cSBenjamin Tissoires deletes it on context exit. 365ffb85d5cSBenjamin Tissoires 366ffb85d5cSBenjamin Tissoires This class is tailored to our test setup: it only sets up the udev rule 367ffb85d5cSBenjamin Tissoires on the **second** context and it cleans it up again on the last context 368ffb85d5cSBenjamin Tissoires removed. This matches the expected pytest setup: we enter a context for 369ffb85d5cSBenjamin Tissoires the session once, then once for each test (the first of which will 370ffb85d5cSBenjamin Tissoires trigger the udev rule) and once the last test exited and the session 371ffb85d5cSBenjamin Tissoires exited, we clean up after ourselves. 372ffb85d5cSBenjamin Tissoires """ 373ffb85d5cSBenjamin Tissoires 374ffb85d5cSBenjamin Tissoires def __init__(self): 375ffb85d5cSBenjamin Tissoires self.refs = 0 376ffb85d5cSBenjamin Tissoires self.rulesfile = None 377ffb85d5cSBenjamin Tissoires 378ffb85d5cSBenjamin Tissoires def __enter__(self): 379ffb85d5cSBenjamin Tissoires self.refs += 1 380ffb85d5cSBenjamin Tissoires if self.refs == 2 and self.rulesfile is None: 381ffb85d5cSBenjamin Tissoires self.create_udev_rule() 382ffb85d5cSBenjamin Tissoires self.reload_udev_rules() 383ffb85d5cSBenjamin Tissoires 384ffb85d5cSBenjamin Tissoires def __exit__(self, exc_type, exc_value, traceback): 385ffb85d5cSBenjamin Tissoires self.refs -= 1 386ffb85d5cSBenjamin Tissoires if self.refs == 0 and self.rulesfile: 387ffb85d5cSBenjamin Tissoires os.remove(self.rulesfile.name) 388ffb85d5cSBenjamin Tissoires self.reload_udev_rules() 389ffb85d5cSBenjamin Tissoires 390ffb85d5cSBenjamin Tissoires def reload_udev_rules(self): 391ffb85d5cSBenjamin Tissoires subprocess.run("udevadm control --reload-rules".split()) 392ffb85d5cSBenjamin Tissoires subprocess.run("systemd-hwdb update".split()) 393ffb85d5cSBenjamin Tissoires 394ffb85d5cSBenjamin Tissoires def create_udev_rule(self): 395ffb85d5cSBenjamin Tissoires import tempfile 396ffb85d5cSBenjamin Tissoires 397ffb85d5cSBenjamin Tissoires os.makedirs("/run/udev/rules.d", exist_ok=True) 398ffb85d5cSBenjamin Tissoires with tempfile.NamedTemporaryFile( 399ffb85d5cSBenjamin Tissoires prefix="91-uhid-test-device-REMOVEME-", 400ffb85d5cSBenjamin Tissoires suffix=".rules", 401ffb85d5cSBenjamin Tissoires mode="w+", 402ffb85d5cSBenjamin Tissoires dir="/run/udev/rules.d", 403ffb85d5cSBenjamin Tissoires delete=False, 404ffb85d5cSBenjamin Tissoires ) as f: 405ffb85d5cSBenjamin Tissoires f.write( 406e9064630SBenjamin Tissoires """ 407e9064630SBenjamin TissoiresKERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1" 408e9064630SBenjamin TissoiresKERNELS=="*hid*", ENV{HID_NAME}=="*uhid test *", ENV{HID_BPF_IGNORE_DEVICE}="1" 409e9064630SBenjamin TissoiresKERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1" 410e9064630SBenjamin Tissoires""" 411ffb85d5cSBenjamin Tissoires ) 412ffb85d5cSBenjamin Tissoires self.rulesfile = f 413ffb85d5cSBenjamin Tissoires 414ffb85d5cSBenjamin Tissoires @classmethod 415ffb85d5cSBenjamin Tissoires def instance(cls): 416ffb85d5cSBenjamin Tissoires if not cls._instance: 417ffb85d5cSBenjamin Tissoires cls._instance = HIDTestUdevRule() 418ffb85d5cSBenjamin Tissoires return cls._instance 419