aboutsummaryrefslogtreecommitdiff
path: root/test/xkeyboard-config-test.py.in
diff options
context:
space:
mode:
Diffstat (limited to 'test/xkeyboard-config-test.py.in')
-rwxr-xr-xtest/xkeyboard-config-test.py.in199
1 files changed, 199 insertions, 0 deletions
diff --git a/test/xkeyboard-config-test.py.in b/test/xkeyboard-config-test.py.in
new file mode 100755
index 0000000..ed37d7e
--- /dev/null
+++ b/test/xkeyboard-config-test.py.in
@@ -0,0 +1,199 @@
+#!/usr/bin/env python3
+import argparse
+import sys
+import subprocess
+import os
+import io
+import xml.etree.ElementTree as ET
+from multiprocessing import Pool
+
+
+verbose = True
+
+DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
+
+# Meson needs to fill this in so we can call the tool in the buildir.
+EXTRA_PATH = '@MESON_BUILD_ROOT@'
+os.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
+
+
+def noop_progress_bar(x, total):
+ return x
+
+
+# The function generating the progress bar (if any).
+progress_bar = noop_progress_bar
+if os.isatty(sys.stdout.fileno()):
+ try:
+ from tqdm import tqdm
+ progress_bar = tqdm
+
+ verbose = False
+ except ImportError:
+ pass
+
+
+def xkbcommontool(rmlvo):
+ try:
+ r = rmlvo.get('r', 'evdev')
+ m = rmlvo.get('m', 'pc105')
+ l = rmlvo.get('l', 'us')
+ v = rmlvo.get('v', None)
+ o = rmlvo.get('o', None)
+ args = [
+ 'xkbcli-compile-keymap', # this is run in the builddir
+ '--rules', r,
+ '--model', m,
+ '--layout', l,
+ ]
+ if v is not None:
+ args += ['--variant', v]
+ if o is not None:
+ args += ['--options', o]
+
+ success = True
+ out = io.StringIO()
+ if verbose:
+ print(':: {}'.format(' '.join(args)), file=out)
+
+ try:
+ output = subprocess.check_output(args, stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ if verbose:
+ print(output, file=out)
+ except subprocess.CalledProcessError as err:
+ print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
+ print(err.output, file=out)
+ success = False
+
+ return success, out.getvalue()
+ except KeyboardInterrupt:
+ pass
+
+
+def xkbcomp(rmlvo):
+ try:
+ r = rmlvo.get('r', 'evdev')
+ m = rmlvo.get('m', 'pc105')
+ l = rmlvo.get('l', 'us')
+ v = rmlvo.get('v', None)
+ o = rmlvo.get('o', None)
+ args = ['setxkbmap', '-print']
+ if r is not None:
+ args.append('-rules')
+ args.append('{}'.format(r))
+ if m is not None:
+ args.append('-model')
+ args.append('{}'.format(m))
+ if l is not None:
+ args.append('-layout')
+ args.append('{}'.format(l))
+ if v is not None:
+ args.append('-variant')
+ args.append('{}'.format(v))
+ if o is not None:
+ args.append('-option')
+ args.append('{}'.format(o))
+
+ success = True
+ out = io.StringIO()
+ if verbose:
+ print(':: {}'.format(' '.join(args)), file=out)
+
+ try:
+ xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
+
+ setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
+ xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True)
+ setxkbmap.stdout.close()
+ stdout, stderr = xkbcomp.communicate()
+ if xkbcomp.returncode != 0:
+ print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
+ success = False
+ if xkbcomp.returncode != 0 or verbose:
+ print(stdout, file=out)
+ print(stderr, file=out)
+
+ # This catches setxkbmap errors.
+ except subprocess.CalledProcessError as err:
+ print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
+ print(err.output, file=out)
+ success = False
+
+ return success, out.getvalue()
+ except KeyboardInterrupt:
+ pass
+
+
+def parse(path):
+ root = ET.fromstring(open(path).read())
+ layouts = root.findall('layoutList/layout')
+
+ options = [
+ e.text
+ for e in root.findall('optionList/group/option/configItem/name')
+ ]
+
+ combos = []
+ for l in layouts:
+ layout = l.find('configItem/name').text
+ combos.append({'l': layout})
+
+ variants = l.findall('variantList/variant')
+ for v in variants:
+ variant = v.find('configItem/name').text
+
+ combos.append({'l': layout, 'v': variant})
+ for option in options:
+ combos.append({'l': layout, 'v': variant, 'o': option})
+
+ return combos
+
+
+def run(combos, tool, njobs):
+ failed = False
+ with Pool(njobs) as p:
+ results = p.imap_unordered(tool, combos)
+ for success, output in progress_bar(results, total=len(combos)):
+ if not success:
+ failed = True
+ if output:
+ print(output, file=sys.stdout if success else sys.stderr)
+ return failed
+
+
+def main(args):
+ tools = {
+ 'libxkbcommon': xkbcommontool,
+ 'xkbcomp': xkbcomp,
+ }
+
+ parser = argparse.ArgumentParser(
+ description='Tool to test all layout/variant/option combinations.'
+ )
+ parser.add_argument('path', metavar='/path/to/evdev.xml',
+ nargs='?', type=str,
+ default=DEFAULT_RULES_XML,
+ help='Path to xkeyboard-config\'s evdev.xml')
+ parser.add_argument('--tool', choices=tools.keys(),
+ type=str, default='libxkbcommon',
+ help='parsing tool to use')
+ parser.add_argument('--jobs', '-j', type=int,
+ default=os.cpu_count() * 4,
+ help='number of processes to use')
+ args = parser.parse_args()
+
+ tool = tools[args.tool]
+
+ combos = parse(args.path)
+ failed = run(combos, tool, args.jobs)
+ sys.exit(failed)
+
+
+if __name__ == '__main__':
+ try:
+ main(sys.argv)
+ except KeyboardInterrupt:
+ print('Exiting after Ctrl+C')