X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=test%2Fxkeyboard-config-test.py.in;h=c33e7077156867da814cad6f21ddff06f4a4caf8;hb=243b0c8703e4339454d7b8e6758ffe6daddbb263;hp=c271f5af2320f2f7866c340ad53e54224ca5bb8a;hpb=cd5a24aa38894f5e38f265a6ea121e3aa7b67d1a;p=platform%2Fupstream%2Flibxkbcommon.git diff --git a/test/xkeyboard-config-test.py.in b/test/xkeyboard-config-test.py.in index c271f5a..c33e707 100755 --- a/test/xkeyboard-config-test.py.in +++ b/test/xkeyboard-config-test.py.in @@ -1,192 +1,339 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import argparse +import multiprocessing import sys import subprocess import os -import io import xml.etree.ElementTree as ET -from multiprocessing import Pool +from pathlib import Path -verbose = True +verbose = False -DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml' +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')]) +EXTRA_PATH = "@MESON_BUILD_ROOT@" +os.environ["PATH"] = ":".join([EXTRA_PATH, os.getenv("PATH")]) + + +def escape(s): + return s.replace('"', '\\"') # The function generating the progress bar (if any). -progress_bar = lambda x, desc: x -if os.isatty(sys.stdout.fileno()): - try: - from tqdm import tqdm - progress_bar = tqdm +def create_progress_bar(verbose): + def noop_progress_bar(x, total, file=None): + return x - verbose = False - except ImportError: - pass + progress_bar = noop_progress_bar + if not verbose and os.isatty(sys.stdout.fileno()): + try: + from tqdm import tqdm + + progress_bar = tqdm + except ImportError: + pass + + return progress_bar + + +class Invocation: + def __init__(self, r, m, l, v, o): + self.command = "" + self.rules = r + self.model = m + self.layout = l + self.variant = v + self.option = o + self.exitstatus = 77 # default to skipped + self.error = None + self.keymap = None # The fully compiled keymap + + @property + def rmlvo(self): + return self.rules, self.model, self.layout, self.variant, self.option + + def __str__(self): + s = [] + rmlvo = [x or "" for x in self.rmlvo] + rmlvo = ", ".join([f'"{x}"' for x in rmlvo]) + s.append(f"- rmlvo: [{rmlvo}]") + s.append(f' cmd: "{escape(self.command)}"') + s.append(f" status: {self.exitstatus}") + if self.error: + s.append(f' error: "{escape(self.error.strip())}"') + return "\n".join(s) + + def run(self): + raise NotImplementedError + + +class XkbCompInvocation(Invocation): + def run(self): + r, m, l, v, o = self.rmlvo + 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)) + + xkbcomp_args = ["xkbcomp", "-xkb", "-", "-"] + + self.command = " ".join(args + ["|"] + xkbcomp_args) + + setxkbmap = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) + stdout, stderr = setxkbmap.communicate() + if "Cannot open display" in stderr: + self.error = stderr + self.exitstatus = 90 + else: + xkbcomp = subprocess.Popen( + xkbcomp_args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) + stdout, stderr = xkbcomp.communicate(stdout) + if xkbcomp.returncode != 0: + self.error = "failed to compile keymap" + self.exitstatus = xkbcomp.returncode + else: + self.keymap = stdout + self.exitstatus = 0 -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) +class XkbcommonInvocation(Invocation): + UNRECOGNIZED_KEYSYM_ERROR = "XKB-107" + + def run(self): + r, m, l, v, o = self.rmlvo args = [ - 'rmlvo-to-keymap', - '--rules', r, - '--model', m, - '--layout', l, + "xkbcli-compile-keymap", # this is run in the builddir + "--verbose", + "--rules", + r, + "--model", + m, + "--layout", + l, ] if v is not None: - args += ['--variant', v] + args += ["--variant", v] if o is not None: - args += ['--options', o] - - success = True - out = io.StringIO() - if verbose: - print(':: {}'.format(' '.join(args)), file=out) + args += ["--options", o] + self.command = " ".join(args) try: - output = subprocess.check_output(args, stderr=subprocess.STDOUT) - if verbose: - print(output.decode('utf-8'), file=out) + output = subprocess.check_output( + args, stderr=subprocess.STDOUT, universal_newlines=True + ) + if self.UNRECOGNIZED_KEYSYM_ERROR in output: + for line in output.split("\n"): + if self.UNRECOGNIZED_KEYSYM_ERROR in line: + self.error = line + self.exitstatus = 99 # tool doesn't generate this one + else: + self.exitstatus = 0 + self.keymap = output except subprocess.CalledProcessError as err: - print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out) - print(err.output.decode('utf-8'), file=out) - success = False + self.error = "failed to compile keymap" + self.exitstatus = err.returncode + - return success, out.getvalue() +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) + tool = XkbcommonInvocation(r, m, l, v, o) + tool.run() + return tool 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 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) - 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.decode('utf-8'), file=out) - print(stderr.decode('utf-8'), file=out) - - # This catches setxkbmap errors. - except subprocess.CalledProcessError as err: - print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out) - print(err.output.decode('utf-8'), file=out) - success = False - - return success, out.getvalue() + 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) + tool = XkbCompInvocation(r, m, l, v, o) + tool.run() + return tool except KeyboardInterrupt: pass def parse(path): root = ET.fromstring(open(path).read()) - layouts = root.findall('layoutList/layout') + layouts = root.findall("layoutList/layout") - options = [ - e.text - for e in root.findall('optionList/group/option/configItem/name') - ] + 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}) + layout = l.find("configItem/name").text + combos.append({"l": layout}) - variants = l.findall('variantList/variant') + variants = l.findall("variantList/variant") for v in variants: - variant = v.find('configItem/name').text + variant = v.find("configItem/name").text - combos.append({'l': layout, 'v': variant}) + combos.append({"l": layout, "v": variant}) for option in options: - combos.append({'l': layout, 'v': variant, 'o': option}) + combos.append({"l": layout, "v": variant, "o": option}) return combos -def run(combos, tool, njobs): +def run(combos, tool, njobs, keymap_output_dir): + if keymap_output_dir: + keymap_output_dir = Path(keymap_output_dir) + try: + keymap_output_dir.mkdir() + except FileExistsError as e: + print(e, file=sys.stderr) + return False + + keymap_file = None + keymap_file_fd = None + failed = False - with Pool(njobs) as p: + with multiprocessing.Pool(njobs) as p: results = p.imap_unordered(tool, combos) - for r in progress_bar(results, 'testing'): - success, output = r - if not success: + for invocation in progress_bar(results, total=len(combos), file=sys.stdout): + if invocation.exitstatus != 0: failed = True - if output: - print(output, file=sys.stdout if success else sys.stderr) + target = sys.stderr + else: + target = sys.stdout if verbose else None + + if target: + print(invocation, file=target) + + if keymap_output_dir: + # we're running through the layouts in a somewhat sorted manner, + # so let's keep the fd open until we switch layouts + layout = invocation.layout + if invocation.variant: + layout += f"({invocation.variant})" + fname = keymap_output_dir / layout + if fname != keymap_file: + keymap_file = fname + if keymap_file_fd: + keymap_file_fd.close() + keymap_file_fd = open(keymap_file, "a") + + rmlvo = ", ".join([x or "" for x in invocation.rmlvo]) + print(f"// {rmlvo}", file=keymap_file_fd) + print(invocation.keymap, file=keymap_file_fd) + keymap_file_fd.flush() return failed def main(args): + global progress_bar + global verbose + tools = { - 'libxkbcommon': xkbcommontool, - 'xkbcomp': xkbcomp, + "libxkbcommon": xkbcommontool, + "xkbcomp": xkbcomp, } parser = argparse.ArgumentParser( - description='Tool to test all layout/variant/option combinations.' + description=""" + This tool compiles a keymap for each layout, variant and + options combination in the given rules XML file. The output + of this tool is YAML, use your favorite YAML parser to + extract error messages. Errors are printed to stderr. + """ + ) + 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", + ) + parser.add_argument("--verbose", "-v", default=False, action="store_true") + parser.add_argument( + "--keymap-output-dir", + default=None, + type=str, + help="Directory to print compiled keymaps to", + ) + parser.add_argument( + "--layout", default=None, type=str, help="Only test the given layout" ) - 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') + parser.add_argument( + "--variant", default=None, type=str, help="Only test the given variant" + ) + parser.add_argument( + "--option", default=None, type=str, help="Only test the given option" + ) + args = parser.parse_args() + verbose = args.verbose + keymapdir = args.keymap_output_dir + progress_bar = create_progress_bar(verbose) + tool = tools[args.tool] - combos = parse(args.path) - failed = run(combos, tool, args.jobs) + if any([args.layout, args.variant, args.option]): + combos = [ + { + "l": args.layout, + "v": args.variant, + "o": args.option, + } + ] + else: + combos = parse(args.path) + failed = run(combos, tool, args.jobs, keymapdir) sys.exit(failed) -if __name__ == '__main__': +if __name__ == "__main__": try: main(sys.argv) except KeyboardInterrupt: - print('Exiting after Ctrl+C') + print("# Exiting after Ctrl+C")