summaryrefslogtreecommitdiff
path: root/test/test_udev_rules.py
blob: 041890537bbc3fd18b9f2d87316f07a7857a6a54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python3
#
# This test will only work where /dev/uinput is available.
# This test will reload the hwdb and udev rules
#
# Execute via pytest, it will:
# - load all data files and extract the matches
# - create a uinput device for each match
# - check if that device has the udev properties set we expect

import configparser
import libevdev
import os
from pathlib import Path
import pyudev
import pytest
import time
import logging
import sys


@pytest.fixture(scope='session', autouse=True)
def systemd_reload():
    '''Make sure our hwdb and udev rules are up-to-date'''
    import subprocess
    try:
        subprocess.run(['systemd-hwdb', 'update'])
        subprocess.run(['systemctl', 'daemon-reload'])
    except FileNotFoundError:
        # If any of the commands above are not found (most likely the system
        # simply does not use systemd), just skip.
        raise pytest.skip()


def pytest_generate_tests(metafunc):
    # for any function that takes a "tablet" argument return a Tablet object
    # filled with exactly one DeviceMatch from the list of all .tablet files
    # in the data dir. Where the tablet also has touch/buttons generate an
    # extra Finger or Pad device
    if 'tablet' in metafunc.fixturenames:
        datadir = Path(os.getenv('MESON_SOURCE_ROOT') or '.') / 'data'
        tablets = []
        for f in datadir.glob('*.tablet'):
            config = configparser.ConfigParser()
            config.read(f)
            name = config['Device']['Name']
            want_pad = config['Device'].get('Buttons', 0)
            want_finger = config['Features'].get('Touch') == 'true'
            integrated_in = config['Device'].get('IntegratedIn', '').split(';')
            is_touchscreen = set(integrated_in) & set(['Display', 'System'])

            for match in config['Device']['DeviceMatch'].split(';'):
                if not match or match == 'generic':
                    continue

                bus, vid, pid = match.split(':')[:3]  # skip the name part of the match
                if bus not in ['usb', 'bluetooth']:
                    continue

                vid = int(vid, 16)
                pid = int(pid, 16)
                if bus == 'usb':
                    bus = 0x3
                elif bus == 'bluetooth':
                    bus = 0x5

                class Tablet(object):
                    def __init__(self, name, bus, vid, pid, is_touchscreen=False):
                        self.name = name
                        self.bus = bus
                        self.vid = vid
                        self.pid = pid
                        self.is_touchscreen = is_touchscreen

                tablets.append(Tablet(name, bus, vid, pid))

                if want_pad:
                    tablets.append(Tablet(name + ' Pad', bus, vid, pid))

                if want_finger:
                    tablets.append(Tablet(name + ' Finger', bus, vid, pid, is_touchscreen))

        # our tablets list now becomes the list of arguments passed to the
        # test functions taking a 'tablet' argument - one-by-one. So where
        # tablets contains 10 entries, our test function will be called 10
        # times.
        metafunc.parametrize('tablet', tablets)


@pytest.fixture
def uinput(tablet):
    dev = libevdev.Device()
    dev.name = tablet.name
    dev.id = {
        'vendor': tablet.vid,
        'product': tablet.pid,
        'bustype': tablet.bus
    }
    # Our rules match on pid/vid, so purposely make this look like a
    # non-tablet to verify that our rules apply anyway and not others
    dev.enable(libevdev.EV_REL.REL_X)
    dev.enable(libevdev.EV_REL.REL_Y)
    dev.enable(libevdev.EV_KEY.BTN_LEFT)
    dev.enable(libevdev.EV_KEY.BTN_RIGHT)

    try:
        uinput = dev.create_uinput_device()
        # We'll need the is_touchscreen later, so let's hide it in the
        # uinput device to pass it to the actual test
        try:
            uinput.is_touchscreen = tablet.is_touchscreen
        except AttributeError:
            pass
        time.sleep(0.3)
        return uinput
    except OSError:
        raise pytest.skip()


@pytest.mark.skipif(sys.platform != 'linux', reason='This test requires udev')
def test_hwdb_files(uinput):
    logging.debug('{:04x}:{:04x} {}'.format(uinput.id['vendor'], uinput.id['product'], uinput.name))
    udev = pyudev.Context()
    dev = pyudev.Devices.from_device_file(udev, uinput.devnode)
    props = list(dev.properties)  # convert to list for better error messages

    assert 'ID_INPUT' in props
    assert dev.properties['ID_INPUT'] == '1'

    assert 'ID_INPUT_TABLET' in props
    assert dev.properties['ID_INPUT_TABLET'] == '1'

    assert 'ID_INPUT_JOYSTICK' not in props

    if 'Finger' in uinput.name:
        if uinput.is_touchscreen:
            assert 'ID_INPUT_TOUCHSCREEN' in props
        else:
            assert 'ID_INPUT_TOUCHPAD' in props

    # For the Wacom Bamboo Pad we check for "Pad Pad" in the device name
    if 'Pad' in uinput.name:
        if 'Wacom Bamboo Pad' not in uinput.name or 'Pad Pad' in uinput.name:
            assert 'ID_INPUT_TABLET_PAD' in props