From d92310acd6ac9a1dfdfe46c2694996c4e7ad679d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 20 Apr 2021 13:01:20 +1000 Subject: test: add a test case for ISO codes Some languages need to be special-cased, pycountry doesn't list them. Signed-off-by: Peter Hutterer --- .gitlab-ci.yml | 4 +- tests/test_rules_xml.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 528c702..4614bd0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,7 +26,7 @@ variables: # Changing the tag will rebuild the container images. The value is just a # string, but we use the date for human benefits. - FDO_DISTRIBUTION_TAG: '2021-12-17.1' + FDO_DISTRIBUTION_TAG: '2022-01-20.0' stages: @@ -46,7 +46,7 @@ container-prep: # minimal set of packages required to build xkeyboard-config. BASE_PACKAGES: 'xorg-util-macros gettext pkg-config gcc grep m4 python meson ninja git diffutils' # extra packages we need for various tests - EXTRA_PACKAGES: 'tree libxml2 bison xorg-xkbcomp python-pytest python-libevdev python-yaml yq libxkbcommon libxslt' + EXTRA_PACKAGES: 'tree libxml2 bison xorg-xkbcomp python-pytest python-libevdev python-yaml yq libxkbcommon libxslt python-pycountry' FDO_DISTRIBUTION_PACKAGES: $BASE_PACKAGES $EXTRA_PACKAGES diff --git a/tests/test_rules_xml.py b/tests/test_rules_xml.py index b833a08..85ad717 100644 --- a/tests/test_rules_xml.py +++ b/tests/test_rules_xml.py @@ -23,14 +23,72 @@ def xkb_config_root(): return _xkb_config_root() +def iterate_layouts_variants(rules_xml): + ''' + Return an iterator of type (layout, variant) for each element in the XML + file. + ''' + tree = ET.parse(rules_xml) + root = tree.getroot() + for layout in root.iter('layout'): + yield layout, None + + for variant in layout.iter('variant'): + yield layout, variant + + 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) + # for any test_foo function with an argument named layout, + # make it a Layout wrapper class for all layout(variant) combinations + elif 'layout' in metafunc.fixturenames: + rules_xml = list(_xkb_config_root().glob('rules/*.xml')) + assert rules_xml + layouts = [] + for f in rules_xml: + for l, v in iterate_layouts_variants(f): + layouts.append(Layout(f, l, v)) + metafunc.parametrize('layout', layouts) + + + +class Layout: + ''' + Wrapper class for layout/variants - both ConfigItems are available but + the properties automatically pick the variant (if it exists) or the + layout otherwise. + ''' + def __init__(self, rulesfile, layout, variant=None): + self.rulesfile = rulesfile + self.layout = ConfigItem.from_elem(layout) + self.variant = ConfigItem.from_elem(variant) if variant else None + if variant: + self.name = f"{self.layout.name}({self.variant.name})" + else: + self.name = f"{self.layout.name}" + + def _fetch(self, name): + parent = self.variant or self.layout + elements = parent.findall(name) + if elements is None: + return None + elif len(elements) > 1: + return elements + else: + return elements[0] + + @property + def iso3166(self): + return (self.variant or self.layout).iso3166 + + @property + def iso639(self): + return (self.variant or self.layout).iso639 def prettyxml(element): @@ -42,6 +100,8 @@ class ConfigItem: self.name = name self.shortDescription = shortDescription self.description = description + self.iso639 = [] + self.iso3166 = [] @classmethod def _fetch_subelement(cls, parent, name): @@ -51,6 +111,11 @@ class ConfigItem: else: return None + @classmethod + def _fetch_subelement_text(cls, parent, name): + sub_element = parent.findall(name) + return [e.text for e in sub_element] + @classmethod def _fetch_text(cls, parent, name): sub_element = cls._fetch_subelement(parent, name) @@ -67,7 +132,21 @@ class ConfigItem: # shortDescription and description are optional sdesc = cls._fetch_text(ci_element, 'shortDescription') desc = cls._fetch_text(ci_element, 'description') - return ConfigItem(name, sdesc, desc) + ci = ConfigItem(name, sdesc, desc) + + langlist = cls._fetch_subelement(ci_element, 'languageList') + if langlist: + ci.iso639 = cls._fetch_subelement_text(langlist, 'iso639Id') + + langlist = cls._fetch_subelement(ci_element, 'languageList') + if langlist: + ci.iso639 = cls._fetch_subelement_text(langlist, 'iso639Id') + + countrylist = cls._fetch_subelement(ci_element, 'countryList') + if countrylist: + ci.iso3166 = cls._fetch_subelement_text(countrylist, 'iso3166') + + return ci except AssertionError as e: endl = "\n" # f{} cannot contain backslashes e.args = (f'\nFor element {prettyxml(elem)}\n{endl.join(e.args)}',) @@ -99,3 +178,28 @@ def test_duplicate_models(rules_xml): ci = ConfigItem.from_elem(model) assert ci.name not in models, f'Duplicate model {ci.name}' models[ci.name] = True + + +def test_iso3166(layout): + pycountry = pytest.importorskip('pycountry') + country_codes = [c.alpha_2 for c in pycountry.countries] + for code in layout.iso3166: + assert code in country_codes, \ + f'{layout.rulesfile}: unknown country code "{code}" in {layout.name}' + + +def test_iso639(layout): + pycountry = pytest.importorskip('pycountry') + + # A list of languages not in pycountry, so we need to special-case them + special_langs = [ + 'ber', # Berber languages (collective), https://iso639-3.sil.org/code/ber + 'btb', # Beti (Cameroon), https://iso639-3.sil.org/code/btb + 'fox', # Formosan languages (collective), https://iso639-3.sil.org/code/fox + 'phi', # Philippine languages (collective), https://iso639-3.sil.org/code/phi + 'ovd', # Elfdalian, https://iso639-3.sil.org/code/ovd + ] + language_codes = [c.alpha_3 for c in pycountry.languages] + special_langs + for code in layout.iso639: + assert code in language_codes, \ + f'{layout.rulesfile}: unknown language code "{code}" in {layout.name}' -- cgit v1.2.1