spiegel-keyman/linux/keyman-config/tests/uninstall_kmp_tests.py
Eberhard Beilharz b7d5e89577
fix(linux): fix installing keyboard in shared area
Installing into the shared area (`/usr/local/share`) requires `root`
permissions, so in order to install into the shared area the user has
to call `km-package-install` with `sudo`. But we also want to add the
keyboard to the user's list of available keyboards so that he can
switch to it. That needs to be done in the user context. However,
when `sudo` ran the command in the user context, the environment
variables to connect to the user's session dbus were not set.

This change fixes this by passing `DBUS_SESSION_BUS_ADDRESS` along
with the `sudo -u <user>` call. Since `root` doesn't have this variable
set (or at least not pointing to the user's dbus), we construct the
dbus address based on the user's uid.

On newer Ubuntu versions (>=25.04) we also have to set `XDG_RUNTIME_DIR`,
at least when running the autopkgtests.

Fixes: #14911
2025-10-14 17:54:36 +02:00

349 lines
16 KiB
Python

#!/usr/bin/env python3
import unittest
from unittest.mock import Mock, patch
from keyman_config.get_kmp import InstallLocation
from keyman_config.uninstall_kmp import _uninstall_keyboards_from_gnome, _uninstall_keyboards_from_ibus, _uninstall_kmp_common, uninstall_kmp
class UninstallKmpGnomeTests(unittest.TestCase):
def setUp(self):
patcher = patch('keyman_config.uninstall_kmp.GnomeKeyboardsUtil')
self.mockGnomeKeyboardsUtilClass = patcher.start()
self.addCleanup(patcher.stop)
def test_UninstallKeyboardsFromGnome_RemoveOneKeyboardFromEmpty(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = []
# Execute
_uninstall_keyboards_from_gnome([{'id': 'foo1'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with([])
def test_UninstallKeyboardsFromGnome_RemoveNonExistingKeyboard(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('ibus', 'fooDir/foo2.kmx')]
# Execute
_uninstall_keyboards_from_gnome([{'id': 'foo1'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with([
('ibus', 'fooDir/foo2.kmx')])
def test_UninstallKeyboardsFromGnome_RemoveOneKeyboard(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx')]
# Execute
_uninstall_keyboards_from_gnome([{'id': 'foo1'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveMultipleKeyboards(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')]
# Execute
_uninstall_keyboards_from_gnome([{'id': 'foo1'}, {'id': 'foo2'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveAllKeyboards(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('ibus', 'fooDir/foo1.kmx'), ('ibus', 'fooDir/foo2.kmx')]
# Execute
_uninstall_keyboards_from_gnome([{'id': 'foo1'}, {'id': 'foo2'}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with([])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_SingleLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')]
# Execute
_uninstall_keyboards_from_gnome([{'id': 'foo1', 'languages': [{'id': 'en'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_MultipleLanguages(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'fr:fooDir/foo1.kmx')]
# Execute
_uninstall_keyboards_from_gnome(
[{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_OneNotMatchingLanguage(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx')]
# Execute
_uninstall_keyboards_from_gnome(
[{'id': 'foo1', 'languages': [{'id': 'fr'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
def test_UninstallKeyboardsFromGnome_RemoveKeyboard_RemovesAllMatching(self):
# Setup
mockGnomeKeyboardsUtilInstance = self.mockGnomeKeyboardsUtilClass.return_value
mockGnomeKeyboardsUtilInstance.read_input_sources.return_value = [
('xkb', 'en'), ('ibus', 'en:fooDir/foo1.kmx'), ('ibus', 'fr:fooDir/foo1.kmx')]
# Execute
_uninstall_keyboards_from_gnome(
[{'id': 'foo1', 'languages': [{'id': 'fr'}]}], 'fooDir')
# Verify
mockGnomeKeyboardsUtilInstance.write_input_sources.assert_called_once_with(
[('xkb', 'en')])
class UninstallKmpIbusTests(unittest.TestCase):
def setUp(self):
self.mockIsDir = self._setupMock('os.path.isdir')
self.mockIbusUtilClass = self._setupMock('keyman_config.uninstall_kmp.IbusUtil')
self.mockRestartIbus = self._setupMock('keyman_config.uninstall_kmp.restart_ibus')
self.mockGetIbusBus = self._setupMock('keyman_config.uninstall_kmp.get_ibus_bus')
self.mockGetIbusBus.return_value = None
def _setupMock(self, arg0):
patcher = patch(arg0)
result = patcher.start()
self.addCleanup(patcher.stop)
return result
def test_UninstallKeyboardsFromIbus_RemoveOneKeyboardFromEmpty(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = []
# Execute
_uninstall_keyboards_from_ibus([{'id': 'foo1'}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_not_called()
def test_UninstallKeyboardsFromIbus_RemoveNonExistingKeyboard(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'fr:fooDir/foo2.kmx']
# Execute
_uninstall_keyboards_from_ibus([{'id': 'foo1'}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(None, [
'fr:fooDir/foo2.kmx'])
def test_UninstallKeyboardsFromIbus_RemoveOneKeyboard(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'xkb:us::eng', 'km:fooDir/foo1.kmx']
# Execute
_uninstall_keyboards_from_ibus([{'id': 'foo1'}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(
None,
['xkb:us::eng'])
def test_UninstallKeyboardsFromIbus_RemoveMultipleKeyboards(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'xkb:us::eng', 'km:fooDir/foo1.kmx', 'fr:fooDir/foo2.kmx']
# Execute
_uninstall_keyboards_from_ibus([{'id': 'foo1'}, {'id': 'foo2'}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(
None,
['xkb:us::eng'])
def test_UninstallKeyboardsFromIbus_RemoveAllKeyboards(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'km:fooDir/foo1.kmx', 'fr:fooDir/foo2.kmx']
# Execute
_uninstall_keyboards_from_ibus([{'id': 'foo1'}, {'id': 'foo2'}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(None, [])
def test_UninstallKeyboardsFromIbus_RemoveKeyboard_SingleLanguage(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'xkb:us::eng', 'en:fooDir/foo1.kmx']
# Execute
_uninstall_keyboards_from_ibus([{'id': 'foo1', 'languages': [{'id': 'en'}]}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(
None,
['xkb:us::eng'])
def test_UninstallKeyboardsFromIbus_RemoveKeyboard_MultipleLanguages(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'xkb:us::eng', 'fr:fooDir/foo1.kmx']
# Execute
_uninstall_keyboards_from_ibus(
[{'id': 'foo1', 'languages': [{'id': 'en'}, {'id': 'fr'}]}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(
None,
['xkb:us::eng'])
def test_UninstallKeyboardsFromIbus_RemoveKeyboard_OneNotMatchingLanguage(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'xkb:us::eng', 'en:fooDir/foo1.kmx']
# Execute
_uninstall_keyboards_from_ibus(
[{'id': 'foo1', 'languages': [{'id': 'fr'}]}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(
None,
['xkb:us::eng'])
def test_UninstallKeyboardsFromIbus_RemoveKeyboard_RemovesAllMatching(self):
# Setup
mockIbusUtilInstance = self.mockIbusUtilClass.return_value
mockIbusUtilInstance.read_preload_engines.return_value = [
'xkb:us::eng', 'en:fooDir/foo1.kmx', 'fr:fooDir/foo1.kmx', 'km:fooDir/foo2.kmx']
# Execute
_uninstall_keyboards_from_ibus(
[{'id': 'foo1', 'languages': [{'id': 'fr'}]}], 'fooDir')
# Verify
mockIbusUtilInstance.write_preload_engines.assert_called_once_with(
None,
['xkb:us::eng', 'km:fooDir/foo2.kmx'])
class UninstallKmpCommonTests(unittest.TestCase):
def setUp(self):
self.mockIsDir = self._setupMock('os.path.isdir')
self.mockGetKeyboardDir = self._setupMock('keyman_config.uninstall_kmp.get_keyboard_dir')
self.mockGetKeymanDocDir = self._setupMock('keyman_config.uninstall_kmp.get_keyman_doc_dir')
self.mockGetKeymanFontDir = self._setupMock('keyman_config.uninstall_kmp.get_keyman_font_dir')
self.mockIsGnomeShell = self._setupMock('keyman_config.uninstall_kmp.is_gnome_desktop')
self.mockIsGnomeShell.return_value = False
self.mockIsFcitxRunning = self._setupMock('keyman_config.uninstall_kmp.is_fcitx_running')
self.mockIsFcitxRunning.return_value = False
self.mockUninstallKeyboardsFromIbus = self._setupMock('keyman_config.uninstall_kmp._uninstall_keyboards_from_ibus')
self.mockUninstallDir = self._setupMock('keyman_config.uninstall_kmp._uninstall_dir')
self.mockGetMetaData = self._setupMock('keyman_config.uninstall_kmp.get_metadata')
self.mockCustomKeyboardsClass = self._setupMock('keyman_config.uninstall_kmp.CustomKeyboards')
def _setupMock(self, arg0):
patcher = patch(arg0)
result = patcher.start()
self.addCleanup(patcher.stop)
return result
def test_UninstallKmpCommon_NoRemoveLanguage(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
self.mockGetKeyboardDir.return_value = '/tmp/foo'
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
self.mockGetMetaData.return_value = (None, None, None, keyboards, None)
# Execute
_uninstall_kmp_common(InstallLocation.User, 'foo1', False)
# Verify
self.mockUninstallKeyboardsFromIbus.assert_not_called()
self.mockUninstallDir.assert_called()
mockCustomKeyboardsInstance.remove.assert_not_called()
def test_UninstallKmpCommon_RemoveLanguage(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
self.mockGetKeyboardDir.return_value = '/tmp/foo'
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}]
self.mockGetMetaData.return_value = (None, None, None, keyboards, None)
# Execute
_uninstall_kmp_common(InstallLocation.User, 'foo1', True)
# Verify
self.mockUninstallKeyboardsFromIbus.assert_called_once_with(keyboards, '/tmp/foo')
self.mockUninstallDir.assert_called()
mockCustomKeyboardsInstance.remove.assert_called_once_with('/tmp/foo/foo1.kmx')
def test_UninstallKmpCommon_RemoveLanguage_NoKeyboards(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
self.mockGetKeyboardDir.return_value = '/tmp/foo'
keyboards = []
self.mockGetMetaData.return_value = (None, None, None, keyboards, None)
# Execute
_uninstall_kmp_common(InstallLocation.User, 'foo1', True)
# Verify
self.mockUninstallKeyboardsFromIbus.assert_not_called()
self.mockUninstallDir.assert_called()
mockCustomKeyboardsInstance.remove.assert_not_called()
def test_UninstallKmpCommon_RemoveLanguage_MultipleKeyboards(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
self.mockGetKeyboardDir.return_value = '/tmp/foo'
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}, {'id': 'bar2', 'languages': [{'id': 'fr'}]}]
self.mockGetMetaData.return_value = (None, None, None, keyboards, None)
# Execute
_uninstall_kmp_common(InstallLocation.User, 'foo1', True)
# Verify
self.mockUninstallKeyboardsFromIbus.assert_called_once_with(keyboards, '/tmp/foo')
self.mockUninstallDir.assert_called()
self.assertEqual(mockCustomKeyboardsInstance.remove.call_count, 2)
class UninstallKmpTests(unittest.TestCase):
def setUp(self):
self.mockIsDir = self._setupMock('os.path.isdir')
self.mockGetKeyboardDir = self._setupMock('keyman_config.uninstall_kmp.get_keyboard_dir')
self.mockGetKeyboardDir.return_value = '/tmp/foo'
self.mockGetKeymanDocDir = self._setupMock('keyman_config.uninstall_kmp.get_keyman_doc_dir')
self.mockGetKeymanFontDir = self._setupMock('keyman_config.uninstall_kmp.get_keyman_font_dir')
self.mockIsGnomeShell = self._setupMock('keyman_config.uninstall_kmp.is_gnome_desktop')
self.mockIsGnomeShell.return_value = False
self.mockIsFcitxRunning = self._setupMock('keyman_config.uninstall_kmp.is_fcitx_running')
self.mockIsFcitxRunning.return_value = False
self.mockUninstallKeyboardsFromIbus = self._setupMock(
'keyman_config.uninstall_kmp._uninstall_keyboards_from_ibus')
self.mockUninstallDir = self._setupMock('keyman_config.uninstall_kmp._uninstall_dir')
self.mockGetMetaData = self._setupMock('keyman_config.uninstall_kmp.get_metadata')
self.mockCustomKeyboardsClass = self._setupMock('keyman_config.uninstall_kmp.CustomKeyboards')
self.mockKeyboardListChanged = self._setupMock('keyman_config.uninstall_kmp.keyboard_list_changed')
def _setupMock(self, arg0):
patcher = patch(arg0)
result = patcher.start()
self.addCleanup(patcher.stop)
return result
def test_UninstallKmp_HappyPath(self):
# Setup
mockCustomKeyboardsInstance = self.mockCustomKeyboardsClass.return_value
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}, {'id': 'bar2', 'languages': [{'id': 'fr'}]}]
self.mockGetMetaData.return_value = (None, None, None, keyboards, None)
# Execute
uninstall_kmp('foo', False, True)
# Verify
self.mockUninstallKeyboardsFromIbus.assert_called_once_with(keyboards, '/tmp/foo')
self.mockUninstallDir.assert_called()
self.assertEqual(mockCustomKeyboardsInstance.remove.call_count, 2)
self.mockKeyboardListChanged.assert_called_once()