From b88f9d9426609bfd425953ba28f09fcd9ed0a435 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sun, 8 Nov 2020 16:27:26 +0100 Subject: [PATCH] more tests --- bin/key-mapper-gtk | 1 - keymapper/X.py | 21 +++++-- keymapper/paths.py | 4 ++ keymapper/presets.py | 18 ++---- tests/test.py | 2 + tests/testcases/config.py | 88 ++++++++++++++++++++++++++++ tests/testcases/{X.py => mapping.py} | 11 +++- 7 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 tests/testcases/config.py rename tests/testcases/{X.py => mapping.py} (87%) diff --git a/bin/key-mapper-gtk b/bin/key-mapper-gtk index 796190d6..810dfa88 100755 --- a/bin/key-mapper-gtk +++ b/bin/key-mapper-gtk @@ -44,7 +44,6 @@ window = None # TODO check for sudo rights # TODO NUM1 doesnt work anymore -# TODO write some tests? def gtk_iteration(): diff --git a/keymapper/X.py b/keymapper/X.py index c9932cab..bee28b9e 100644 --- a/keymapper/X.py +++ b/keymapper/X.py @@ -39,8 +39,7 @@ from keymapper.paths import get_home_path, get_usr_path, KEYCODES_PATH, \ CONFIG_PATH, SYMBOLS_PATH from keymapper.logger import logger from keymapper.data import get_data_path -from keymapper.presets import get_presets -from keymapper.linux import get_devices, can_grab +from keymapper.linux import get_devices class Mapping: @@ -96,7 +95,7 @@ class Mapping: character : string """ if new_keycode and character: - self._mapping[new_keycode] = character + self._mapping[new_keycode] = str(character) if new_keycode != previous_keycode: # clear previous mapping of that code, because the line # representing that one will now represent a different one. @@ -194,7 +193,7 @@ def create_setxkbmap_config(device, preset, mapping): logger.info('Writing key mappings') with open(home_preset_path, 'w') as f: - contents = generate_symbols_file_content(device, preset, mapping) + contents = generate_symbols_content(device, preset, mapping) if contents is not None: f.write(contents) @@ -260,13 +259,17 @@ def create_identity_mapping(): if not os.path.exists(KEYCODES_PATH): logger.info('Creating "%s"', KEYCODES_PATH) + os.makedirs(os.path.dirname(KEYCODES_PATH), exist_ok=True) + os.mknod(KEYCODES_PATH) with open(KEYCODES_PATH, 'w') as keycodes: keycodes.write(result) -def generate_symbols_file_content(device, preset, mapping): +def generate_symbols_content(device, preset, mapping): """Create config contents to be placed in /usr/share/X11/xkb/symbols. + This file contains the mapping of the preset as expected by X. + Parameters ---------- device : string @@ -276,15 +279,21 @@ def generate_symbols_file_content(device, preset, mapping): # TODO test this function system_default = 'us' # TODO get the system default + if len(mapping) == 0: + raise ValueError('Mapping is empty') + # If the symbols file contains key codes that are not present in # the keycodes file, THE WHOLE X SESSION WILL CRASH! if not os.path.exists(KEYCODES_PATH): - raise ValueError('Expected the keycodes file to exist.') + raise FileNotFoundError('Expected the keycodes file to exist') with open(KEYCODES_PATH, 'r') as f: keycodes = re.findall(r'<.+?>', f.read()) xkb_symbols = [] for keycode, character in mapping: + # since the side effect of a broken config is so severe, make + # sure to check the presence of the keycode once again even though + # their content is always the same (unless modified by a user). if f'<{keycode}>' not in keycodes: logger.error(f'Unknown keycode <{keycode}> for "{character}"') # continue, otherwise X would crash when loading diff --git a/keymapper/paths.py b/keymapper/paths.py index ba0da842..4de7f75e 100644 --- a/keymapper/paths.py +++ b/keymapper/paths.py @@ -28,10 +28,12 @@ Is a module so that tests can modify them. import os import subprocess + # since this needs to run as sudo, # get the home dir of the user who called sudo. who = subprocess.check_output('who').decode().split()[0] +# the path in home, is symlinked with SYMBOLS_PATH CONFIG_PATH = os.path.join('/home', who, '.config/key-mapper') # should not contain spaces @@ -57,6 +59,8 @@ def get_home_path(device, preset=None): def get_usr_path(device, preset=None): """Get the path to the config file in /usr. + This folder is a symlink and the files are in ~/.config/key-mapper + If preset is omitted, returns the folder for the device. """ device = device.strip() diff --git a/keymapper/presets.py b/keymapper/presets.py index 64977bb6..c868fc07 100644 --- a/keymapper/presets.py +++ b/keymapper/presets.py @@ -26,7 +26,8 @@ import os import time import glob -from keymapper.paths import CONFIG_PATH, get_home_path +from keymapper.paths import get_home_path, SYMBOLS_PATH, CONFIG_PATH, \ + KEYCODES_PATH from keymapper.logger import logger from keymapper.linux import get_devices @@ -53,17 +54,6 @@ def get_presets(device): return [preset.replace('_', ' ') for preset in presets] -def get_mappings(device, preset): - """Get all configured buttons of the preset. - - Parameters - ---------- - device : string - preset : string - """ - pass - - def get_any_preset(): """Return the first found tuple of (device, preset).""" devices = get_devices().keys() @@ -99,7 +89,7 @@ def find_newest_preset(device=None): ) if len(paths) == 0: - logger.debug('No presets found.') + logger.debug('No presets found') return get_any_preset() online_devices = [ @@ -118,7 +108,7 @@ def find_newest_preset(device=None): break if newest_path is None: - logger.debug('None of the configured devices is currently online.') + logger.debug('None of the configured devices is currently online') return get_any_preset() # ui: no underscores, filesystem: no whitespaces diff --git a/tests/test.py b/tests/test.py index af0adcae..d3de51f1 100644 --- a/tests/test.py +++ b/tests/test.py @@ -30,6 +30,8 @@ import unittest from keymapper import paths paths.SYMBOLS_PATH = '/tmp/key-mapper-test/symbols' paths.CONFIG_PATH = '/tmp/key-mapper-test/.config' +paths.KEYCODES_PATH = '/tmp/key-mapper-test/keycodes' + from keymapper import linux linux._devices = { diff --git a/tests/testcases/config.py b/tests/testcases/config.py new file mode 100644 index 00000000..8bfc1f5d --- /dev/null +++ b/tests/testcases/config.py @@ -0,0 +1,88 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# key-mapper - GUI for device specific keyboard mappings +# Copyright (C) 2020 sezanzeb +# +# 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 . + + +import os +import unittest +import shutil + +from keymapper.X import Mapping, generate_symbols_content, \ + create_identity_mapping, create_setxkbmap_config, get_home_path +from keymapper.paths import KEYCODES_PATH, SYMBOLS_PATH, CONFIG_PATH + +from test import tmp + + +class TestConfig(unittest.TestCase): + def setUp(self): + self.mapping = Mapping() + self.mapping.change(None, 10, 'a') + self.mapping.change(None, 11, 'NUM1') + self.mapping.change(None, 12, 3) + if os.path.exists(tmp): + shutil.rmtree(tmp) + + def test_create_setxkbmap_config(self): + create_setxkbmap_config('device a', 'preset b', self.mapping) + + self.assertTrue(os.path.exists(os.path.join( + CONFIG_PATH, + 'device_a', + 'preset_b' + ))) + + self.assertTrue(os.path.exists(os.path.join( + SYMBOLS_PATH, + 'device_a', + 'preset_b' + ))) + + self.assertTrue(os.path.exists(KEYCODES_PATH)) + + with open(get_home_path('device_a', 'preset_b'), 'r') as f: + content = f.read() + self.assertIn('key <10> { [ a ] };', content) + self.assertIn('key <11> { [ NUM1 ] };', content) + self.assertIn('key <12> { [ 3 ] };', content) + + def test_generate_content(self): + self.assertRaises( + FileNotFoundError, + generate_symbols_content, + 'device', 'preset', self.mapping + ) + + # create the identity mapping, because it is required for + # generate_symbols_content + create_identity_mapping() + self.assertTrue(os.path.exists(KEYCODES_PATH)) + with open(KEYCODES_PATH, 'r') as f: + keycodes = f.read() + self.assertIn('<8> = 8;', keycodes) + self.assertIn('<255> = 255;', keycodes) + + content = generate_symbols_content('device', 'preset', self.mapping) + self.assertIn('key <10> { [ a ] };', content) + self.assertIn('key <11> { [ NUM1 ] };', content) + self.assertIn('key <12> { [ 3 ] };', content) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/testcases/X.py b/tests/testcases/mapping.py similarity index 87% rename from tests/testcases/X.py rename to tests/testcases/mapping.py index f9ddc688..308074d0 100644 --- a/tests/testcases/X.py +++ b/tests/testcases/mapping.py @@ -29,7 +29,7 @@ class TestMapping(unittest.TestCase): self.mapping = Mapping() def test_change(self): - # 1 is not assigned yet, just add it + # 1 is not assigned yet, ignore it self.mapping.change(1, 2, 'a') self.assertIsNone(self.mapping.get(1)) self.assertEqual(self.mapping.get(2), 'a') @@ -66,6 +66,15 @@ class TestMapping(unittest.TestCase): self.assertIsNone(self.mapping.get(20)) self.assertEqual(self.mapping.get(30), 'NUM3') + def test_iterate_and_convert(self): + self.mapping.change(None, 10, 1) + self.mapping.change(None, 20, 2) + self.mapping.change(None, 30, 3) + self.assertListEqual( + list(self.mapping), + [(10, '1'), (20, '2'), (30, '3')] + ) + if __name__ == "__main__": unittest.main()