cleanup, tests for linux.py

This commit is contained in:
sezanzeb 2020-11-16 15:39:15 +01:00
parent 3e4a3091d4
commit f75bbb7d33
3 changed files with 206 additions and 96 deletions

View File

@ -98,34 +98,30 @@ class KeycodeReader:
# keycode_reader = KeycodeReader() # keycode_reader = KeycodeReader()
def _get_devices(pipe): _devices = None
"""Group devices and get relevant infos per group.
Returns a list containing mappings of
{group_name: {paths: [paths], devices: [names]} for input devices.
For example, group_name could be "Logitech USB Keyboard", devices might class GetDevicesProcess(multiprocessing.Process):
contain "Logitech USB Keyboard System Control" and "Logitech USB Keyboard". """Process to get the devices that can be worked with.
paths is a list of files in /dev/input that belong to the devices.
They are grouped by usb port. Since InputDevice destructors take quite some time, do this
asynchronously so that they can take as much time as they want without
slowing down the initialization. To avoid evdevs asyncio stuff spamming
errors, do this with multiprocessing and not multithreading.
""" """
def __init__(self, pipe):
"""Construct the process.
Parameters
----------
pipe : multiprocessing.Pipe
used to communicate the result
""" """
evdev.list_devices -> string[] dev/input/event# paths self.pipe = pipe
device = evdev.InputDevice(path) super().__init__()
device.capabilities().keys() ein array mit evdev.ecodes.EV_KEY oder
irgendn stuff der nicht interessiert
device.phys ->
device.phys usb-0000:03:00.0-4/input2
device.phys usb-0000:03:00.0-4/input1
device.phys usb-0000:03:00.0-4/input0
device.phys usb-0000:03:00.0-3/input1
device.phys usb-0000:03:00.0-3/input1
device.phys usb-0000:03:00.0-3/input0
"""
def run(self):
"""Do what get_devices describes."""
devices = [evdev.InputDevice(path) for path in evdev.list_devices()] devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
# group them together by usb device because there could be stuff like # group them together by usb device because there could be stuff like
@ -153,18 +149,27 @@ def _get_devices(pipe):
'devices': names 'devices': names
} }
pipe.send(result) self.pipe.send(result)
return result
# populate once for the whole app. Since InputDevice destructors take
# quite some time, do this in a process that can take as much time as it
# wants after piping the result.
pipe = multiprocessing.Pipe()
multiprocessing.Process(target=_get_devices, args=(pipe[1],)).start()
# block until devices are available
_devices = pipe[0].recv()
logger.info('Found %s', ', '.join([f'"{name}"' for name in _devices]))
def get_devices(): def get_devices():
"""Group devices and get relevant infos per group.
Returns a list containing mappings of
{group_name: {paths: [paths], devices: [names]} for input devices.
For example, group_name could be "Logitech USB Keyboard", devices might
contain "Logitech USB Keyboard System Control" and "Logitech USB Keyboard".
paths is a list of files in /dev/input that belong to the devices.
They are grouped by usb port.
"""
global _devices
if _devices is None:
pipe = multiprocessing.Pipe()
GetDevicesProcess(pipe[1]).start()
# block until devices are available
_devices = pipe[0].recv()
logger.info('Found %s', ', '.join([f'"{name}"' for name in _devices]))
return _devices return _devices

View File

@ -24,50 +24,99 @@
import sys import sys
import unittest import unittest
# quickly fake some stuff before any other file gets a chance to import
# the original version
from keymapper import paths
paths.X11_SYMBOLS = '/tmp/key-mapper-test/X11/symbols'
paths.USERS_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user'
paths.DEFAULT_SYMBOLS = '/tmp/key-mapper-test/X11/symbols/key-mapper/user/default'
paths.KEYCODES_PATH = '/tmp/key-mapper-test/X11/keycodes/key-mapper'
from keymapper import linux
linux._devices = {
'device 1': {
'paths': [
'/dev/input/event10',
'/dev/input/event11',
'/dev/input/event13'
],
'names': [
'device 1 something',
'device 1',
'device 1'
]
},
'device 2': {
'paths': ['/dev/input/event3'],
'names': ['device 2']
}
}
linux.get_devices = lambda: linux._devices
# don't block tests
from keymapper.gtk import unsaved
unsaved.unsaved_changes_dialog = lambda: unsaved.CONTINUE
from keymapper.logger import update_verbosity from keymapper.logger import update_verbosity
# some class function stubs.
# can be overwritten in tests as well at any time.
linux.KeycodeReader.start_reading = lambda *args: None
linux.KeycodeReader.read = lambda *args: None
tmp = '/tmp/key-mapper-test' tmp = '/tmp/key-mapper-test'
def patch_paths():
from keymapper import paths
prefix = '/tmp/key-mapper-test/X11/'
paths.X11_SYMBOLS = prefix + 'symbols'
paths.USERS_SYMBOLS = prefix + 'symbols/key-mapper/user'
paths.DEFAULT_SYMBOLS = prefix + 'symbols/key-mapper/user/default'
paths.KEYCODES_PATH = prefix + 'keycodes/key-mapper'
def patch_linux():
from keymapper import linux
linux.KeycodeReader.start_reading = lambda *args: None
linux.KeycodeReader.read = lambda *args: None
def patch_evdev():
import evdev
# key-mapper is only interested in devices that have EV_KEY, add some
# random other stuff to test that they are ignored.
fixtures = {
# device 1
'/dev/input/event11': {
'capabilities': {evdev.ecodes.EV_KEY: [], evdev.ecodes.EV_ABS: []},
'phys': 'usb-0000:03:00.0-1/input2',
'name': 'device 1 foo'
},
'/dev/input/event10': {
'capabilities': {evdev.ecodes.EV_KEY: []},
'phys': 'usb-0000:03:00.0-1/input3',
'name': 'device 1'
},
'/dev/input/event13': {
'capabilities': {evdev.ecodes.EV_KEY: [], evdev.ecodes.EV_SYN: []},
'phys': 'usb-0000:03:00.0-1/input1',
'name': 'device 1'
},
'/dev/input/event14': {
'capabilities': {evdev.ecodes.EV_SYN: []},
'phys': 'usb-0000:03:00.0-1/input0',
'name': 'device 1 qux'
},
# device 2
'/dev/input/event20': {
'capabilities': {evdev.ecodes.EV_KEY: []},
'phys': 'usb-0000:03:00.0-2/input1',
'name': 'device 2'
},
# something that is completely ignored
'/dev/input/event30': {
'capabilities': {evdev.ecodes.EV_SYN: []},
'phys': 'usb-0000:03:00.0-3/input1',
'name': 'device 3'
},
}
def list_devices():
return fixtures.keys()
class InputDevice:
def __init__(self, path):
self.path = path
self.phys = fixtures[path]['phys']
self.name = fixtures[path]['name']
def capabilities(self):
return fixtures[self.path]['capabilities']
evdev.list_devices = list_devices
evdev.InputDevice = InputDevice
def patch_unsaved():
# don't block tests
from keymapper.gtk import unsaved
unsaved.unsaved_changes_dialog = lambda: unsaved.CONTINUE
# quickly fake some stuff before any other file gets a chance to import
# the original versions
patch_paths()
patch_evdev()
patch_linux()
patch_unsaved()
if __name__ == "__main__": if __name__ == "__main__":
update_verbosity(True) update_verbosity(True)

56
tests/testcases/linux.py Normal file
View File

@ -0,0 +1,56 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
# Copyright (C) 2020 sezanzeb <proxima@hip70890b.de>
#
# This file is part of key-mapper.
#
# key-mapper 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 3 of the License, or
# (at your option) any later version.
#
# key-mapper 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 key-mapper. If not, see <https://www.gnu.org/licenses/>.
import unittest
from keymapper.linux import GetDevicesProcess
class TestLinux(unittest.TestCase):
def test_create_preset_1(self):
class FakePipe:
def send(self, stuff):
pass
# don't actually start the process, just use the `run` function.
# otherwise the coverage tool can't keep track.
devices = GetDevicesProcess(FakePipe()).run()
self.assertDictEqual(devices, {
'device 1': {
'paths': [
'/dev/input/event11',
'/dev/input/event10',
'/dev/input/event13'],
'devices': [
'device 1 foo',
'device 1',
'device 1'
]
},
'device 2': {
'paths': ['/dev/input/event20'],
'devices': ['device 2']
}
})
if __name__ == "__main__":
unittest.main()