spiegel-keyman/linux/keyman-config/tests/install_kmp_tests.py
Eberhard Beilharz 951793ea84
fix(linux): normalize language tags from keyboard
This fixes a bug where a keyboard didn't show up in the language dropdown
if the first language it specified wasn't a normalized language tag.
Previously in that case we added a custom keyboard with whatever language
tag the keyboard specified. With this change we now continue to use
the first language of the first keyboard but normalize it. Since the
selection of the language now happens a little earlier it is slightly
possible that there will be unexpected side effects that didn't show up
in my testing.

Fixes: #14465
2025-08-18 19:49:38 +02:00

340 lines
15 KiB
Python

#!/usr/bin/env python3
import os
import tempfile
import unittest
from unittest.mock import patch, ANY
from keyman_config import __version__
from keyman_config.get_kmp import InstallLocation
from keyman_config.install_kmp import InstallError, InstallKmp
class InstallKmpBase(unittest.TestCase):
def setUp(self):
patcher1 = patch('keyman_config.install_kmp.install_to_ibus')
self.mockInstallToIbus = patcher1.start()
self.addCleanup(patcher1.stop)
patcher2 = patch('keyman_config.install_kmp.restart_ibus')
self.mockRestartIbus = patcher2.start()
self.addCleanup(patcher2.stop)
patcher3 = patch('keyman_config.install_kmp.get_ibus_bus')
self.mockGetIbusBus = patcher3.start()
self.addCleanup(patcher3.stop)
patcher4 = patch('keyman_config.install_kmp.GnomeKeyboardsUtil')
self.mockGnomeKeyboardsUtilClass = patcher4.start()
self.addCleanup(patcher4.stop)
patcher5 = patch('keyman_config.install_kmp.get_keyboard_dir')
self.mockGetKeyboardDir = patcher5.start()
self.addCleanup(patcher5.stop)
patcher6 = patch('keyman_config.install_kmp.extract_kmp')
self.mockExtractKmp = patcher6.start()
self.addCleanup(patcher6.stop)
patcher7 = patch('keyman_config.install_kmp.extractico')
self.mockExtractIco = patcher7.start()
self.addCleanup(patcher7.stop)
patcher8 = patch('keyman_config.install_kmp.is_gnome_desktop')
self.mockIsGnomeShell = patcher8.start()
self.addCleanup(patcher8.stop)
self.mockIsGnomeShell.return_value = False
patcher9 = patch('keyman_config.install_kmp.is_fcitx_running')
self.mockIsFcitxRunning = patcher9.start()
self.addCleanup(patcher9.stop)
self.mockIsFcitxRunning.return_value = False
patcher10 = patch('keyman_config.install_kmp.CustomKeyboards')
self.mockCustomKeyboardsClass = patcher10.start()
self.addCleanup(patcher10.stop)
class InstallKeyboardsToIbusTests(InstallKmpBase):
def test_InstallKeyboardsToIbus_NoIbus(self):
# Setup
self.mockGetIbusBus.return_value = None
# Execute
InstallKmp()._install_keyboards_to_ibus([], None)
# Verify
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToIbus_SingleKbNoLanguages(self):
# Setup
bus = self.mockGetIbusBus.return_value
keyboards = [{'id': 'foo1'}]
# Execute
InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
# Verify
self.mockInstallToIbus.assert_called_once_with(ANY, 'fooDir/foo1.kmx')
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
def test_InstallKeyboardsToIbus_MultipleKbsNoLanguages(self):
# Setup
bus = self.mockGetIbusBus.return_value
keyboards = [{'id': 'foo1'}, {'id': 'foo2'}]
# Execute
InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
# Verify
self.mockInstallToIbus.assert_any_call(ANY, 'fooDir/foo1.kmx')
self.mockInstallToIbus.assert_any_call(ANY, 'fooDir/foo2.kmx')
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
def test_InstallKeyboardsToIbus_SingleKbSingleLanguage(self):
# Setup
bus = self.mockGetIbusBus.return_value
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
# Execute
InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
# Verify
self.mockInstallToIbus.assert_called_once_with(ANY, 'en:fooDir/foo1.kmx')
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
def test_InstallKeyboardsToIbus_SingleKbMultipleLanguages(self):
# Setup
bus = self.mockGetIbusBus.return_value
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir')
# Verify
self.mockInstallToIbus.assert_called_once()
self.mockInstallToIbus.assert_called_with(ANY, 'en:fooDir/foo1.kmx')
# self.mockInstallToIbus.assert_not_called_with(ANY, 'fr:fooDir/foo1.kmx')
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
def test_InstallKeyboardsToIbus_SingleKbMultipleLanguages_GivenLanguage(self):
# Setup
bus = self.mockGetIbusBus.return_value
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir', 'fr')
# Verify
self.mockInstallToIbus.assert_called_once()
self.mockInstallToIbus.assert_called_with(ANY, 'fr:fooDir/foo1.kmx')
# self.mockInstallToIbus.assert_not_called_with(ANY, 'en:fooDir/foo1.kmx')
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
def test_InstallKeyboardsToIbus_SingleKbMultipleLanguages_OtherLanguage(self):
# Setup
bus = self.mockGetIbusBus.return_value
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards_to_ibus(keyboards, 'fooDir', 'de')
# Verify
self.mockInstallToIbus.assert_called_once()
self.mockInstallToIbus.assert_called_with(ANY, 'de:fooDir/foo1.kmx')
# self.mockInstallToIbus.assert_not_called_with(ANY, 'en:fooDir/foo1.kmx')
self.mockRestartIbus.assert_called_once()
bus.destroy.assert_called_once()
class InstallKeyboardsToGnomeTests(InstallKmpBase):
def test_InstallKeyboardsToGnome_SingleKbNoLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1'}]
# Execute
InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_MultipleKbsNoLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1'}, {'id': 'foo2'}]
# Execute
InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_SingleKbSingleLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
# Execute
InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages_GivenLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir', 'fr')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'fr:fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
def test_InstallKeyboardsToGnome_SingleKbMultipleLanguages_OtherLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [('xkb', 'en')]
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards_to_gnome(keyboards, 'fooDir', 'de')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en'), ('ibus', 'de:fooDir/foo1.kmx')])
self.mockRestartIbus.assert_not_called()
class InstallKmpTests(InstallKmpBase):
def _createEmptyKmp(self, workdir):
kmpfile = os.path.join(workdir, 'foo.kmp')
with open(kmpfile, 'w') as file:
file.write('')
return kmpfile
def _createKmpJson(self, packagedir, fileVersionString):
with open(os.path.join(packagedir, 'kmp.json'), 'w') as file:
file.write('''{
"system": {
"keymanDeveloperVersion": "15.0"
''' + fileVersionString + '''
},
"files": [ {
"name": "khmer_angkor.kmx",
"description": "Keyboard Khmer Angkor"
}, {
"name": "kmp.json",
"description": "Package information (JSON)"
} ],
"keyboards": [ {
"name": "Khmer Angkor",
"id": "khmer_angkor",
"version": "1.1",
"languages": [ {
"name": "Central Khmer (Khmer, Cambodia)",
"id": "km"
} ]}
]}''')
def test_InstallKmp_FutureKeymanVersion(self):
# Setup
workdir = tempfile.TemporaryDirectory()
packagedir = tempfile.TemporaryDirectory()
self.mockGetKeyboardDir.return_value = packagedir.name
kmpfile = self._createEmptyKmp(workdir.name)
self._createKmpJson(packagedir.name, ', "fileVersion": "99.0"')
# Execute
with self.assertRaises(InstallError) as context:
InstallKmp()._install_kmp(kmpfile, 'km', InstallLocation.User)
# Verify
self.assertTrue('foo.kmp requires Keyman 99.0 or higher' in context.exception.message)
self.mockInstallToIbus.assert_not_called()
# Teardown
workdir.cleanup()
packagedir.cleanup()
def test_InstallKmp(self):
for testcase in [
{'name': 'PreviousKeymanVersion', 'fileVersion': ', "fileVersion": "7.0"'},
{'name': 'SameKeymanVersion', 'fileVersion': f', "fileVersion": "{__version__}"'},
{'name': 'NoFileVersion', 'fileVersion': ''}
]:
with self.subTest(msg=testcase['name'], data=testcase['fileVersion']):
# Setup
self.mockInstallToIbus.reset_mock()
workdir = tempfile.TemporaryDirectory()
packagedir = tempfile.TemporaryDirectory()
self.mockGetKeyboardDir.return_value = packagedir.name
kmpfile = self._createEmptyKmp(workdir.name)
self._createKmpJson(packagedir.name, testcase['fileVersion'])
with patch('keyman_config.install_kmp.process_keyboard_data') as mock:
method = mock.return_value
method.return_value = None
# Execute
InstallKmp()._install_kmp(kmpfile, 'km', InstallLocation.User)
# Verify
self.mockInstallToIbus.assert_called_once()
# Teardown
workdir.cleanup()
packagedir.cleanup()
class InstallKeyboardsTests(InstallKmpBase):
def test_InstallKeyboards_NoLang(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
mockCustomKeyboardsInstance.get.return_value = None
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards(keyboards, 'fooDir', None)
# Verify
self.mockInstallToIbus.assert_called_once_with(ANY, 'en:fooDir/foo1.kmx')
mockCustomKeyboardsInstance.add.assert_not_called()
def test_InstallKeyboards_LangInKeyboard(self):
# Setup
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
mockCustomKeyboardsInstance.get.return_value = None
# Execute
InstallKmp()._install_keyboards(keyboards, 'fooDir', 'fr')
# Verify
self.mockInstallToIbus.assert_called_once_with(ANY, 'fr:fooDir/foo1.kmx')
mockCustomKeyboardsInstance.add.assert_not_called()
def test_InstallKeyboards_CustomLang(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
mockCustomKeyboardsInstance.get.return_value = None
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}]
# Execute
InstallKmp()._install_keyboards(keyboards, 'fooDir', 'mul')
# Verify
self.mockInstallToIbus.assert_called_once_with(ANY, 'mul:fooDir/foo1.kmx')
mockCustomKeyboardsInstance.add.assert_called_once_with('mul:fooDir/foo1.kmx')
class AddCustomKeyboardTests(InstallKmpBase):
def test_AddCustomKeyboard_NoLang(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
keyboard = {'id': 'foo1'}
# Execute
language = InstallKmp()._add_custom_keyboard(keyboard, 'fooDir', None)
# Verify
self.assertEqual(language, None)
mockCustomKeyboardsInstance.add.assert_not_called()
def test_AddCustomKeyboard_Add(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
keyboard = {'id': 'foo1'}
# Execute
language = InstallKmp()._add_custom_keyboard(keyboard, 'fooDir', 'mul')
# Verify
self.assertEqual(language, 'mul')
mockCustomKeyboardsInstance.add.assert_called_once_with('mul:fooDir/foo1.kmx')