some more tests

This commit is contained in:
sezanzeb 2020-12-03 20:03:53 +01:00
parent e54a08f3be
commit d75082bdf7
12 changed files with 144 additions and 32 deletions

View File

@ -32,8 +32,8 @@ Documentation:
##### Names ##### Names
For a list of supported keystrokes and their names, check the output of For a list of supported keystrokes and their names for the middle column,
`xmodmap -pke` check the output of `xmodmap -pke`
- Alphanumeric `a` to `z` and `0` to `9` - Alphanumeric `a` to `z` and `0` to `9`
- Modifiers `Alt_L` `Control_L` `Control_R` `Shift_L` `Shift_R` - Modifiers `Alt_L` `Control_L` `Control_R` `Shift_L` `Shift_R`
@ -42,6 +42,7 @@ If you can't find what you need, consult
[linux/input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h) [linux/input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h)
- Mouse buttons `BTN_LEFT` `BTN_RIGHT` `BTN_MIDDLE` `BTN_SIDE` - Mouse buttons `BTN_LEFT` `BTN_RIGHT` `BTN_MIDDLE` `BTN_SIDE`
- Multimedia keys `KEY_NEXTSONG`, `KEY_PLAYPAUSE`, ...
- Macro special keys `KEY_MACRO1` `KEY_MACRO2` ... - Macro special keys `KEY_MACRO1` `KEY_MACRO2` ...
##### Gamepads ##### Gamepads

View File

@ -143,7 +143,6 @@ class KeycodeInjector:
needed = False needed = False
for (ev_type, keycode), _ in self.mapping: for (ev_type, keycode), _ in self.mapping:
# TODO test ev_type
if keycode in capabilities.get(ev_type, []): if keycode in capabilities.get(ev_type, []):
needed = True needed = True
break break
@ -161,13 +160,13 @@ class KeycodeInjector:
attempts = 0 attempts = 0
while True: while True:
device = evdev.InputDevice(path)
try: try:
# grab to avoid e.g. the disabled keycode of 10 to confuse # grab to avoid e.g. the disabled keycode of 10 to confuse
# X, especially when one of the buttons of your mouse also # X, especially when one of the buttons of your mouse also
# uses 10. This also avoids having to load an empty xkb # uses 10. This also avoids having to load an empty xkb
# symbols file to prevent writing any unwanted keys. # symbols file to prevent writing any unwanted keys.
device.grab() device.grab()
logger.debug('Grab %s', path)
break break
except IOError: except IOError:
attempts += 1 attempts += 1
@ -224,7 +223,6 @@ class KeycodeInjector:
capabilities[ecodes.EV_KEY] = [] capabilities[ecodes.EV_KEY] = []
# for reasons I don't know, it is also required to have # for reasons I don't know, it is also required to have
# any keyboard button in capabilities. # any keyboard button in capabilities.
# TODO test that this is always present when abs_to_rel
capabilities[ecodes.EV_KEY].append(ecodes.KEY_0) capabilities[ecodes.EV_KEY].append(ecodes.KEY_0)
# just like what python-evdev does in from_device # just like what python-evdev does in from_device

View File

@ -43,7 +43,6 @@ def should_map_event_as_btn(type, code):
code : int code : int
linux keycode linux keycode
""" """
# TODO test
if type == evdev.events.EV_KEY: if type == evdev.events.EV_KEY:
return True return True

View File

@ -108,8 +108,6 @@ class _KeycodeReader:
logger.debug('Pipe closed, reader stops.') logger.debug('Pipe closed, reader stops.')
sys.exit(0) sys.exit(0)
# TODO write a test to map event `type 3 (EV_ABS), code 16
# (ABS_HAT0X), value 0` to a button
if should_map_event_as_btn(event.type, event.code): if should_map_event_as_btn(event.type, event.code):
logger.spam( logger.spam(
'got code:%s value:%s type:%s', 'got code:%s value:%s type:%s',

View File

@ -38,7 +38,6 @@ CTX_KEYCODE = 2
def to_string(ev_type, code): def to_string(ev_type, code):
"""A nice to show description of the pressed key.""" """A nice to show description of the pressed key."""
# TODO test
try: try:
name = evdev.ecodes.bytype[ev_type][code] name = evdev.ecodes.bytype[ev_type][code]
if isinstance(name, list): if isinstance(name, list):

View File

@ -86,7 +86,7 @@ class Mapping:
prev_keycode = int(prev_keycode) prev_keycode = int(prev_keycode)
if prev_type is not None: if prev_type is not None:
prev_type = int(prev_type) prev_type = int(prev_type)
except ValueError: except (TypeError, ValueError):
logger.error('Can only use numbers in the tuples') logger.error('Can only use numbers in the tuples')
return False return False
@ -100,9 +100,6 @@ class Mapping:
if code_changed and prev_keycode is not None: if code_changed and prev_keycode is not None:
# clear previous mapping of that code, because the line # clear previous mapping of that code, because the line
# representing that one will now represent a different one. # representing that one will now represent a different one.
# TODO test that None won't reach the clear function.
# TODO test that overwriting an entry with a different type
# works
self.clear(prev_type, prev_keycode) self.clear(prev_type, prev_keycode)
self.changed = True self.changed = True
return True return True

View File

@ -25,6 +25,7 @@
import os import os
import sys import sys
import time import time
import copy
import unittest import unittest
import subprocess import subprocess
import multiprocessing import multiprocessing
@ -99,7 +100,16 @@ fixtures = {
}, },
'/dev/input/event30': { '/dev/input/event30': {
'capabilities': {evdev.ecodes.EV_SYN: [], evdev.ecodes.EV_ABS: [0, 1]}, # this device is expected to not have EV_KEY capabilities in tests
# yet. Only when the injector is running EV_KEY stuff is added
'capabilities': {
evdev.ecodes.EV_SYN: [],
evdev.ecodes.EV_ABS: [
evdev.ecodes.ABS_X,
evdev.ecodes.ABS_Y,
evdev.ecodes.ABS_HAT0X
]
},
'phys': 'usb-0000:03:00.0-3/input1', 'phys': 'usb-0000:03:00.0-3/input1',
'name': 'gamepad' 'name': 'gamepad'
}, },
@ -252,7 +262,7 @@ def patch_evdev():
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
def capabilities(self, absinfo=True): def capabilities(self, absinfo=True):
return fixtures[self.path]['capabilities'] return copy.deepcopy(fixtures[self.path]['capabilities'])
class UInput: class UInput:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -288,6 +298,7 @@ def clear_write_history():
while uinput_write_history_pipe[0].poll(): while uinput_write_history_pipe[0].poll():
uinput_write_history_pipe[0].recv() uinput_write_history_pipe[0].recv()
# quickly fake some stuff before any other file gets a chance to import # quickly fake some stuff before any other file gets a chance to import
# the original versions # the original versions
patch_paths() patch_paths()

View File

@ -24,7 +24,7 @@ import unittest
import time import time
import evdev import evdev
from evdev.ecodes import EV_REL, EV_KEY, EV_ABS from evdev.ecodes import EV_REL, EV_KEY, EV_ABS, ABS_HAT0X
from keymapper.dev.injector import is_numlock_on, toggle_numlock,\ from keymapper.dev.injector import is_numlock_on, toggle_numlock,\
ensure_numlock, KeycodeInjector ensure_numlock, KeycodeInjector
@ -65,6 +65,7 @@ class TestInjector(unittest.TestCase):
for key in keys: for key in keys:
del pending_events[key] del pending_events[key]
clear_write_history() clear_write_history()
custom_mapping.empty()
def test_modify_capabilities(self): def test_modify_capabilities(self):
class FakeDevice: class FakeDevice:
@ -108,6 +109,15 @@ class TestInjector(unittest.TestCase):
# success on the third try # success on the third try
device.name = fixtures[path]['name'] device.name = fixtures[path]['name']
def test_prepare_device_1(self):
# according to the fixtures, /dev/input/event30 can do ABS_HAT0X
custom_mapping.change((EV_ABS, ABS_HAT0X), 'a')
self.injector = KeycodeInjector('foobar', custom_mapping)
_prepare_device = self.injector._prepare_device
self.assertIsNone(_prepare_device('/dev/input/event10')[0])
self.assertIsNotNone(_prepare_device('/dev/input/event30')[0])
def test_gamepad_capabilities(self): def test_gamepad_capabilities(self):
self.injector = KeycodeInjector('gamepad', custom_mapping) self.injector = KeycodeInjector('gamepad', custom_mapping)
@ -119,6 +129,11 @@ class TestInjector(unittest.TestCase):
self.assertNotIn(evdev.ecodes.EV_ABS, capabilities) self.assertNotIn(evdev.ecodes.EV_ABS, capabilities)
self.assertIn(evdev.ecodes.EV_REL, capabilities) self.assertIn(evdev.ecodes.EV_REL, capabilities)
# for some reason, having any EV_KEY capability is needed to
# be able to control the mouse
self.assertIn(evdev.ecodes.EV_KEY, capabilities)
self.assertEqual(len(capabilities[evdev.ecodes.EV_KEY]), 1)
def test_skip_unused_device(self): def test_skip_unused_device(self):
# skips a device because its capabilities are not used in the mapping # skips a device because its capabilities are not used in the mapping
custom_mapping.change((EV_KEY, 10), 'a') custom_mapping.change((EV_KEY, 10), 'a')
@ -282,7 +297,7 @@ class TestInjector(unittest.TestCase):
def test_injector(self): def test_injector(self):
custom_mapping.change((EV_KEY, 8), 'k(KEY_Q).k(w)') custom_mapping.change((EV_KEY, 8), 'k(KEY_Q).k(w)')
custom_mapping.change((EV_KEY, 9), 'a') custom_mapping.change((EV_ABS, ABS_HAT0X), 'a')
# one mapping that is unknown in the system_mapping on purpose # one mapping that is unknown in the system_mapping on purpose
input_b = 10 input_b = 10
custom_mapping.change((EV_KEY, input_b), 'b') custom_mapping.change((EV_KEY, input_b), 'b')
@ -301,9 +316,9 @@ class TestInjector(unittest.TestCase):
# should execute a macro # should execute a macro
Event(EV_KEY, 8, 1), Event(EV_KEY, 8, 1),
Event(EV_KEY, 8, 0), Event(EV_KEY, 8, 0),
# normal keystroke # normal keystrokes
Event(EV_KEY, 9, 1), Event(EV_ABS, ABS_HAT0X, 1),
Event(EV_KEY, 9, 0), Event(EV_ABS, ABS_HAT0X, 0),
# just pass those over without modifying # just pass those over without modifying
Event(EV_KEY, 10, 1), Event(EV_KEY, 10, 1),
Event(EV_KEY, 10, 0), Event(EV_KEY, 10, 0),

View File

@ -40,6 +40,7 @@ from keymapper.state import custom_mapping, system_mapping, \
from keymapper.paths import CONFIG, get_config_path from keymapper.paths import CONFIG, get_config_path
from keymapper.config import config from keymapper.config import config
from keymapper.dev.reader import keycode_reader from keymapper.dev.reader import keycode_reader
from keymapper.gtk.row import to_string
from tests.test import tmp, pending_events, Event, uinput_write_history_pipe, \ from tests.test import tmp, pending_events, Event, uinput_write_history_pipe, \
clear_write_history clear_write_history
@ -176,6 +177,11 @@ class TestIntegration(unittest.TestCase):
self.assertIsNotNone(self.window) self.assertIsNotNone(self.window)
self.assertTrue(self.window.window.get_visible()) self.assertTrue(self.window.window.get_visible())
def test_row_keycode_to_string(self):
# not an integration test, but I have all the row tests here already
self.assertEqual(to_string(EV_KEY, 10), '9')
self.assertEqual(to_string(EV_KEY, 39), 'SEMICOLON')
def test_row_simple(self): def test_row_simple(self):
rows = self.window.get('key_list').get_children() rows = self.window.get('key_list').get_children()
self.assertEqual(len(rows), 1) self.assertEqual(len(rows), 1)
@ -185,9 +191,15 @@ class TestIntegration(unittest.TestCase):
row.set_new_keycode(None, None) row.set_new_keycode(None, None)
self.assertIsNone(row.get_keycode()) self.assertIsNone(row.get_keycode())
self.assertEqual(len(custom_mapping), 0) self.assertEqual(len(custom_mapping), 0)
self.assertEqual(row.keycode_input.get_label(), None)
row.set_new_keycode(EV_KEY, 30) row.set_new_keycode(EV_KEY, 30)
self.assertEqual(len(custom_mapping), 0) self.assertEqual(len(custom_mapping), 0)
self.assertEqual(row.get_keycode(), (EV_KEY, 30)) self.assertEqual(row.get_keycode(), (EV_KEY, 30))
# this is KEY_A in linux/input-event-codes.h,
# but KEY_ is removed from the text
self.assertEqual(row.keycode_input.get_label(), 'A')
row.set_new_keycode(EV_KEY, 30) row.set_new_keycode(EV_KEY, 30)
self.assertEqual(len(custom_mapping), 0) self.assertEqual(len(custom_mapping), 0)
self.assertEqual(row.get_keycode(), (EV_KEY, 30)) self.assertEqual(row.get_keycode(), (EV_KEY, 30))
@ -208,8 +220,20 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(row.get_keycode(), (EV_KEY, 30)) self.assertEqual(row.get_keycode(), (EV_KEY, 30))
def change_empty_row(self, code, char, code_first=True, success=True): def change_empty_row(self, code, char, code_first=True, success=True):
"""Modify the one empty row that always exists.""" """Modify the one empty row that always exists.
# this is not a test, it's a utility function for other tests.
Utility function for other tests.
Parameters
----------
code_first : boolean
If True, the code is entered and then the character.
If False, the character is entered first.
success : boolean
If this change on the empty row is going to result in a change
in the mapping eventually. False if this change is going to
cause a duplicate.
"""
# wait for the window to create a new empty row if needed # wait for the window to create a new empty row if needed
time.sleep(0.1) time.sleep(0.1)
gtk_iteration() gtk_iteration()

View File

@ -0,0 +1,38 @@
#!/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 evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, KEY_A, ABS_X, EV_REL, REL_X
from keymapper.dev.keycode_mapper import should_map_event_as_btn
class TestKeycodeMapper(unittest.TestCase):
def test_should_map_event_as_btn(self):
self.assertTrue(should_map_event_as_btn(EV_ABS, ABS_HAT0X))
self.assertTrue(should_map_event_as_btn(EV_KEY, KEY_A))
self.assertFalse(should_map_event_as_btn(EV_ABS, ABS_X))
self.assertFalse(should_map_event_as_btn(EV_REL, REL_X))
if __name__ == "__main__":
unittest.main()

View File

@ -20,7 +20,7 @@
import unittest import unittest
from evdev.events import EV_KEY from evdev.events import EV_KEY, EV_ABS
from keymapper.mapping import Mapping from keymapper.mapping import Mapping
from keymapper.state import populate_system_mapping from keymapper.state import populate_system_mapping
@ -81,15 +81,15 @@ class TestMapping(unittest.TestCase):
self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a') self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a')
self.assertEqual(len(self.mapping), 1) self.assertEqual(len(self.mapping), 1)
# change 2 to 3 and change a to b # change KEY 2 to ABS 16 and change a to b
self.mapping.change((EV_KEY, 3), 'b', (EV_KEY, 2)) self.mapping.change((EV_ABS, 16), 'b', (EV_KEY, 2))
self.assertIsNone(self.mapping.get_character(EV_KEY, 2)) self.assertIsNone(self.mapping.get_character(EV_KEY, 2))
self.assertEqual(self.mapping.get_character(EV_KEY, 3), 'b') self.assertEqual(self.mapping.get_character(EV_ABS, 16), 'b')
self.assertEqual(len(self.mapping), 1) self.assertEqual(len(self.mapping), 1)
# add 4 # add 4
self.mapping.change((EV_KEY, 4), 'c', (None, None)) self.mapping.change((EV_KEY, 4), 'c', (None, None))
self.assertEqual(self.mapping.get_character(EV_KEY, 3), 'b') self.assertEqual(self.mapping.get_character(EV_ABS, 16), 'b')
self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'c') self.assertEqual(self.mapping.get_character(EV_KEY, 4), 'c')
self.assertEqual(len(self.mapping), 2) self.assertEqual(len(self.mapping), 2)
@ -109,10 +109,23 @@ class TestMapping(unittest.TestCase):
self.assertEqual(len(self.mapping), 2) self.assertEqual(len(self.mapping), 2)
# non-int keycodes are ignored # non-int keycodes are ignored
# TODO what about non-int types?
self.mapping.change((EV_KEY, 'b'), 'c', (EV_KEY, 'a')) self.mapping.change((EV_KEY, 'b'), 'c', (EV_KEY, 'a'))
self.mapping.change((EV_KEY, 'b'), 'c')
self.mapping.change(('foo', 1), 'c', ('foo', 2))
self.mapping.change(('foo', 1), 'c')
self.assertEqual(len(self.mapping), 2) self.assertEqual(len(self.mapping), 2)
def test_change_2(self):
self.mapping.change((EV_KEY, 2), 'a')
self.mapping.change((None, 2), 'b', (EV_KEY, 2))
self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a')
self.mapping.change((EV_KEY, None), 'c', (EV_KEY, 2))
self.assertEqual(self.mapping.get_character(EV_KEY, 2), 'a')
self.assertEqual(len(self.mapping), 1)
def test_clear(self): def test_clear(self):
# does nothing # does nothing
self.mapping.clear(EV_KEY, 40) self.mapping.clear(EV_KEY, 40)

View File

@ -21,7 +21,7 @@
import unittest import unittest
from evdev.events import EV_KEY from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X
import time import time
from keymapper.dev.reader import keycode_reader from keymapper.dev.reader import keycode_reader
@ -34,6 +34,17 @@ CODE_2 = 101
CODE_3 = 102 CODE_3 = 102
def wait(func, timeout=1.0):
"""Wait for func to return True."""
iterations = 0
sleepytime = 0.1
while not func():
time.sleep(sleepytime)
iterations += 1
if iterations * sleepytime > timeout:
break
class TestReader(unittest.TestCase): class TestReader(unittest.TestCase):
def setUp(self): def setUp(self):
# verify that tearDown properly cleared the reader # verify that tearDown properly cleared the reader
@ -45,10 +56,10 @@ class TestReader(unittest.TestCase):
for key in keys: for key in keys:
del pending_events[key] del pending_events[key]
def test_reading(self): def test_reading_1(self):
pending_events['device 1'] = [ pending_events['device 1'] = [
Event(EV_KEY, CODE_1, 1), Event(EV_KEY, CODE_1, 1),
Event(EV_KEY, CODE_2, 1), Event(EV_ABS, ABS_HAT0X, 1),
Event(EV_KEY, CODE_3, 1) Event(EV_KEY, CODE_3, 1)
] ]
keycode_reader.start_reading('device 1') keycode_reader.start_reading('device 1')
@ -56,10 +67,18 @@ class TestReader(unittest.TestCase):
# sending anything arbitrary does not stop the pipe # sending anything arbitrary does not stop the pipe
keycode_reader._pipe[0].send((EV_KEY, 1234)) keycode_reader._pipe[0].send((EV_KEY, 1234))
time.sleep(EVENT_READ_TIMEOUT * 5) wait(keycode_reader._pipe[0].poll, 0.5)
self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3)) self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3))
self.assertEqual(keycode_reader.read(), (None, None)) self.assertEqual(keycode_reader.read(), (None, None))
def test_reading_2(self):
pending_events['device 1'] = [Event(EV_ABS, ABS_HAT0X, 1)]
keycode_reader.start_reading('device 1')
wait(keycode_reader._pipe[0].poll, 0.5)
self.assertEqual(keycode_reader.read(), (EV_ABS, ABS_HAT0X))
self.assertEqual(keycode_reader.read(), (None, None))
def test_wrong_device(self): def test_wrong_device(self):
pending_events['device 1'] = [ pending_events['device 1'] = [
Event(EV_KEY, CODE_1, 1), Event(EV_KEY, CODE_1, 1),