summaryrefslogtreecommitdiff
path: root/tests/test_rules_xml.py
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2020-08-28 09:22:15 +1000
committerSergey Udaltsov <sergey.udaltsov@gmail.com>2020-09-23 15:30:43 +0000
commit387e0dda53dba2d964df479896d915dd4518bad4 (patch)
treeb10fa1c27df3184a71dba8018a246c010199daef /tests/test_rules_xml.py
parent50cab6799a1a0eb801cad107b81b6b6550864ada (diff)
test: add a test for duplicate entries in the rules.xml files
Related to https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/issues/222 Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Diffstat (limited to 'tests/test_rules_xml.py')
-rw-r--r--tests/test_rules_xml.py91
1 files changed, 91 insertions, 0 deletions
diff --git a/tests/test_rules_xml.py b/tests/test_rules_xml.py
new file mode 100644
index 00000000..0e27a56c
--- /dev/null
+++ b/tests/test_rules_xml.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Call with pytest. Requires XKB_CONFIG_ROOT to be set
+
+import os
+import pytest
+from pathlib import Path
+import xml.etree.ElementTree as ET
+
+
+def _xkb_config_root():
+ path = os.getenv('XKB_CONFIG_ROOT')
+ assert path is not None, 'Environment variable XKB_CONFIG_ROOT must be set'
+ print(f'Using {path}')
+
+ xkbpath = Path(path)
+ assert (xkbpath / 'rules').exists(), f'{path} is not an XKB installation'
+ return xkbpath
+
+
+@pytest.fixture
+def xkb_config_root():
+ return _xkb_config_root()
+
+
+def pytest_generate_tests(metafunc):
+ # for any test_foo function with an argument named rules_xml,
+ # make it the list of XKB_CONFIG_ROOT/rules/*.xml files.
+ if 'rules_xml' in metafunc.fixturenames:
+
+ rules_xml = list(_xkb_config_root().glob('rules/*.xml'))
+ assert rules_xml
+ metafunc.parametrize('rules_xml', rules_xml)
+
+
+def prettyxml(element):
+ return ET.tostring(element).decode('utf-8')
+
+
+class ConfigItem:
+ def __init__(self, name, shortDescription=None, description=None):
+ self.name = name
+ self.shortDescription = shortDescription
+ self.description = description
+
+ @classmethod
+ def _fetch_subelement(cls, parent, name):
+ sub_element = parent.findall(name)
+ if sub_element is not None and len(sub_element) == 1:
+ return sub_element[0]
+ else:
+ return None
+
+ @classmethod
+ def _fetch_text(cls, parent, name):
+ sub_element = cls._fetch_subelement(parent, name)
+ if sub_element is None:
+ return None
+ return sub_element.text
+
+ @classmethod
+ def from_elem(cls, elem):
+ try:
+ ci_element = cls._fetch_subelement(elem, 'configItem')
+ name = cls._fetch_text(ci_element, 'name')
+ assert name is not None
+ # shortDescription and description are optional
+ sdesc = cls._fetch_text(ci_element, 'shortDescription')
+ desc = cls._fetch_text(ci_element, 'description')
+ return ConfigItem(name, sdesc, desc)
+ except AssertionError as e:
+ endl = "\n" # f{} cannot contain backslashes
+ e.args = (f'\nFor element {prettyxml(elem)}\n{endl.join(e.args)}',)
+ raise
+
+
+def test_duplicate_layouts(rules_xml):
+ tree = ET.parse(rules_xml)
+ root = tree.getroot()
+ layouts = {}
+ for layout in root.iter('layout'):
+ ci = ConfigItem.from_elem(layout)
+ assert ci.name not in layouts, f'Duplicate layout {ci.name}'
+ layouts[ci.name] = True
+
+ variants = {}
+ for variant in layout.iter('variant'):
+ vci = ConfigItem.from_elem(variant)
+ assert vci.name not in variants, \
+ f'{rules_xml}: duplicate variant {ci.name}({vci.name}):\n{prettyxml(variant)}'
+ variants[vci.name] = True