kernel
This commit is contained in:
parent
b59f84ee36
commit
cfab1d3ce7
83468 changed files with 37938688 additions and 0 deletions
linux-6.8.1/tools/testing/selftests/hid/tests
344
linux-6.8.1/tools/testing/selftests/hid/tests/base.py
Normal file
344
linux-6.8.1/tools/testing/selftests/hid/tests/base.py
Normal file
|
@ -0,0 +1,344 @@
|
|||
#!/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
|
||||
# Copyright (c) 2017 Red Hat, Inc.
|
||||
|
||||
import libevdev
|
||||
import os
|
||||
import pytest
|
||||
import time
|
||||
|
||||
import logging
|
||||
|
||||
from hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile
|
||||
from pathlib import Path
|
||||
from typing import Final, List, Tuple
|
||||
|
||||
logger = logging.getLogger("hidtools.test.base")
|
||||
|
||||
# application to matches
|
||||
application_matches: Final = {
|
||||
# pyright: ignore
|
||||
"Accelerometer": EvdevMatch(
|
||||
req_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
]
|
||||
),
|
||||
"Game Pad": EvdevMatch( # in systemd, this is a lot more complex, but that will do
|
||||
requires=[
|
||||
libevdev.EV_ABS.ABS_X,
|
||||
libevdev.EV_ABS.ABS_Y,
|
||||
libevdev.EV_ABS.ABS_RX,
|
||||
libevdev.EV_ABS.ABS_RY,
|
||||
libevdev.EV_KEY.BTN_START,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Joystick": EvdevMatch( # in systemd, this is a lot more complex, but that will do
|
||||
requires=[
|
||||
libevdev.EV_ABS.ABS_RX,
|
||||
libevdev.EV_ABS.ABS_RY,
|
||||
libevdev.EV_KEY.BTN_START,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Key": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_KEY.KEY_A,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
libevdev.INPUT_PROP_DIRECT,
|
||||
libevdev.INPUT_PROP_POINTER,
|
||||
],
|
||||
),
|
||||
"Mouse": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_REL.REL_X,
|
||||
libevdev.EV_REL.REL_Y,
|
||||
libevdev.EV_KEY.BTN_LEFT,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Pad": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_KEY.BTN_0,
|
||||
],
|
||||
excludes=[
|
||||
libevdev.EV_KEY.BTN_TOOL_PEN,
|
||||
libevdev.EV_KEY.BTN_TOUCH,
|
||||
libevdev.EV_ABS.ABS_DISTANCE,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Pen": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_KEY.BTN_STYLUS,
|
||||
libevdev.EV_ABS.ABS_X,
|
||||
libevdev.EV_ABS.ABS_Y,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Stylus": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_KEY.BTN_STYLUS,
|
||||
libevdev.EV_ABS.ABS_X,
|
||||
libevdev.EV_ABS.ABS_Y,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Touch Pad": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_KEY.BTN_LEFT,
|
||||
libevdev.EV_ABS.ABS_X,
|
||||
libevdev.EV_ABS.ABS_Y,
|
||||
],
|
||||
excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
|
||||
req_properties=[
|
||||
libevdev.INPUT_PROP_POINTER,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
"Touch Screen": EvdevMatch(
|
||||
requires=[
|
||||
libevdev.EV_KEY.BTN_TOUCH,
|
||||
libevdev.EV_ABS.ABS_X,
|
||||
libevdev.EV_ABS.ABS_Y,
|
||||
],
|
||||
excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
|
||||
req_properties=[
|
||||
libevdev.INPUT_PROP_DIRECT,
|
||||
],
|
||||
excl_properties=[
|
||||
libevdev.INPUT_PROP_ACCELEROMETER,
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class UHIDTestDevice(BaseDevice):
|
||||
def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None):
|
||||
super().__init__(name, application, rdesc_str, rdesc, input_info)
|
||||
self.application_matches = application_matches
|
||||
if name is None:
|
||||
name = f"uhid test {self.__class__.__name__}"
|
||||
if not name.startswith("uhid test "):
|
||||
name = "uhid test " + self.name
|
||||
self.name = name
|
||||
|
||||
|
||||
class BaseTestCase:
|
||||
class TestUhid(object):
|
||||
syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) # type: ignore
|
||||
key_event = libevdev.InputEvent(libevdev.EV_KEY) # type: ignore
|
||||
abs_event = libevdev.InputEvent(libevdev.EV_ABS) # type: ignore
|
||||
rel_event = libevdev.InputEvent(libevdev.EV_REL) # type: ignore
|
||||
msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN) # type: ignore
|
||||
|
||||
# List of kernel modules to load before starting the test
|
||||
# if any module is not available (not compiled), the test will skip.
|
||||
# Each element is a tuple '(kernel driver name, kernel module)',
|
||||
# for example ("playstation", "hid-playstation")
|
||||
kernel_modules: List[Tuple[str, str]] = []
|
||||
|
||||
def assertInputEventsIn(self, expected_events, effective_events):
|
||||
effective_events = effective_events.copy()
|
||||
for ev in expected_events:
|
||||
assert ev in effective_events
|
||||
effective_events.remove(ev)
|
||||
return effective_events
|
||||
|
||||
def assertInputEvents(self, expected_events, effective_events):
|
||||
remaining = self.assertInputEventsIn(expected_events, effective_events)
|
||||
assert remaining == []
|
||||
|
||||
@classmethod
|
||||
def debug_reports(cls, reports, uhdev=None, events=None):
|
||||
data = [" ".join([f"{v:02x}" for v in r]) for r in reports]
|
||||
|
||||
if uhdev is not None:
|
||||
human_data = [
|
||||
uhdev.parsed_rdesc.format_report(r, split_lines=True)
|
||||
for r in reports
|
||||
]
|
||||
try:
|
||||
human_data = [
|
||||
f'\n\t {" " * h.index("/")}'.join(h.split("\n"))
|
||||
for h in human_data
|
||||
]
|
||||
except ValueError:
|
||||
# '/' not found: not a numbered report
|
||||
human_data = ["\n\t ".join(h.split("\n")) for h in human_data]
|
||||
data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)]
|
||||
|
||||
reports = data
|
||||
|
||||
if len(reports) == 1:
|
||||
print("sending 1 report:")
|
||||
else:
|
||||
print(f"sending {len(reports)} reports:")
|
||||
for report in reports:
|
||||
print("\t", report)
|
||||
|
||||
if events is not None:
|
||||
print("events received:", events)
|
||||
|
||||
def create_device(self):
|
||||
raise Exception("please reimplement me in subclasses")
|
||||
|
||||
def _load_kernel_module(self, kernel_driver, kernel_module):
|
||||
sysfs_path = Path("/sys/bus/hid/drivers")
|
||||
if kernel_driver is not None:
|
||||
sysfs_path /= kernel_driver
|
||||
else:
|
||||
# special case for when testing all available modules:
|
||||
# we don't know beforehand the name of the module from modinfo
|
||||
sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
|
||||
if not sysfs_path.exists():
|
||||
import subprocess
|
||||
|
||||
ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
|
||||
if ret.returncode != 0:
|
||||
pytest.skip(
|
||||
f"module {kernel_module} could not be loaded, skipping the test"
|
||||
)
|
||||
|
||||
@pytest.fixture()
|
||||
def load_kernel_module(self):
|
||||
for kernel_driver, kernel_module in self.kernel_modules:
|
||||
self._load_kernel_module(kernel_driver, kernel_module)
|
||||
yield
|
||||
|
||||
@pytest.fixture()
|
||||
def new_uhdev(self, load_kernel_module):
|
||||
return self.create_device()
|
||||
|
||||
def assertName(self, uhdev):
|
||||
evdev = uhdev.get_evdev()
|
||||
assert uhdev.name in evdev.name
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def context(self, new_uhdev, request):
|
||||
try:
|
||||
with HIDTestUdevRule.instance():
|
||||
with new_uhdev as self.uhdev:
|
||||
for skip_cond in request.node.iter_markers("skip_if_uhdev"):
|
||||
test, message, *rest = skip_cond.args
|
||||
|
||||
if test(self.uhdev):
|
||||
pytest.skip(message)
|
||||
|
||||
self.uhdev.create_kernel_device()
|
||||
now = time.time()
|
||||
while not self.uhdev.is_ready() and time.time() - now < 5:
|
||||
self.uhdev.dispatch(1)
|
||||
if self.uhdev.get_evdev() is None:
|
||||
logger.warning(
|
||||
f"available list of input nodes: (default application is '{self.uhdev.application}')"
|
||||
)
|
||||
logger.warning(self.uhdev.input_nodes)
|
||||
yield
|
||||
self.uhdev = None
|
||||
except PermissionError:
|
||||
pytest.skip("Insufficient permissions, run me as root")
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_taint(self):
|
||||
# we are abusing SysfsFile here, it's in /proc, but meh
|
||||
taint_file = SysfsFile("/proc/sys/kernel/tainted")
|
||||
taint = taint_file.int_value
|
||||
|
||||
yield
|
||||
|
||||
assert taint_file.int_value == taint
|
||||
|
||||
def test_creation(self):
|
||||
"""Make sure the device gets processed by the kernel and creates
|
||||
the expected application input node.
|
||||
|
||||
If this fail, there is something wrong in the device report
|
||||
descriptors."""
|
||||
uhdev = self.uhdev
|
||||
assert uhdev is not None
|
||||
assert uhdev.get_evdev() is not None
|
||||
self.assertName(uhdev)
|
||||
assert len(uhdev.next_sync_events()) == 0
|
||||
assert uhdev.get_evdev() is not None
|
||||
|
||||
|
||||
class HIDTestUdevRule(object):
|
||||
_instance = None
|
||||
"""
|
||||
A context-manager compatible class that sets up our udev rules file and
|
||||
deletes it on context exit.
|
||||
|
||||
This class is tailored to our test setup: it only sets up the udev rule
|
||||
on the **second** context and it cleans it up again on the last context
|
||||
removed. This matches the expected pytest setup: we enter a context for
|
||||
the session once, then once for each test (the first of which will
|
||||
trigger the udev rule) and once the last test exited and the session
|
||||
exited, we clean up after ourselves.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.refs = 0
|
||||
self.rulesfile = None
|
||||
|
||||
def __enter__(self):
|
||||
self.refs += 1
|
||||
if self.refs == 2 and self.rulesfile is None:
|
||||
self.create_udev_rule()
|
||||
self.reload_udev_rules()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.refs -= 1
|
||||
if self.refs == 0 and self.rulesfile:
|
||||
os.remove(self.rulesfile.name)
|
||||
self.reload_udev_rules()
|
||||
|
||||
def reload_udev_rules(self):
|
||||
import subprocess
|
||||
|
||||
subprocess.run("udevadm control --reload-rules".split())
|
||||
subprocess.run("systemd-hwdb update".split())
|
||||
|
||||
def create_udev_rule(self):
|
||||
import tempfile
|
||||
|
||||
os.makedirs("/run/udev/rules.d", exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
prefix="91-uhid-test-device-REMOVEME-",
|
||||
suffix=".rules",
|
||||
mode="w+",
|
||||
dir="/run/udev/rules.d",
|
||||
delete=False,
|
||||
) as f:
|
||||
f.write(
|
||||
'KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"\n'
|
||||
)
|
||||
f.write(
|
||||
'KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"\n'
|
||||
)
|
||||
self.rulesfile = f
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not cls._instance:
|
||||
cls._instance = HIDTestUdevRule()
|
||||
return cls._instance
|
Loading…
Add table
Add a link
Reference in a new issue