test: rework the output for the xkeyboard-config layout tester
authorPeter Hutterer <peter.hutterer@who-t.net>
Thu, 15 Apr 2021 00:39:05 +0000 (10:39 +1000)
committerRan Benita <ran@unusedvar.com>
Tue, 20 Apr 2021 07:30:17 +0000 (10:30 +0300)
The previous output is largely unusable. The result in the CI test runs is a 6GB
file with every compiled keymap in it and while we can grep for ERROR, it's not
particularly useful.

Let's change this and print out YAML instead - that can be machine-processed.
This patch adds a new parent class that prints itself in YAML format,
the tool invocations are child classes of that class. The result looks like this:

Example output:
- rmlvo: ["evdev", "pc105", "us", "haw", "grp:rwin_switch"]
  cmd: "xkbcli-compile-keymap --verbose --rules evdev --model pc105 --layout us --variant haw --options grp:rwin_switch"
  status: 0
- rmlvo: ["evdev", "pc105", "us", "foo", ""]
  cmd: "xkbcli-compile-keymap --verbose --rules evdev --model pc105 --layout us --variant foo"
  status: 1
  error: "failed to compile keymap"

Special status codes are: 99 for "unrecognized keysym" and 90 for "Cannot open
display" in the setxkbmap case.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
test/xkeyboard-config-test.py.in

index 8c02125..cc53da4 100755 (executable)
@@ -1,11 +1,10 @@
 #!/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
 
 
 verbose = False
@@ -37,13 +36,83 @@ def create_progress_bar(verbose):
     return progress_bar
 
 
-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 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
+
+
+class XkbcommonInvocation(Invocation):
+    def run(self):
+        r, m, l, v, o = self.rmlvo
         args = [
             'xkbcli-compile-keymap',  # this is run in the builddir
             '--verbose',
@@ -56,28 +125,33 @@ def xkbcommontool(rmlvo):
         if o is not None:
             args += ['--options', o]
 
-        success = True
-        out = io.StringIO()
-        if verbose:
-            print(':: {}'.format(' '.join(args)), file=out)
-
+        self.command = " ".join(args)
         try:
             output = subprocess.check_output(args, stderr=subprocess.STDOUT,
                                              universal_newlines=True)
-            if verbose:
-                print(output, file=out)
-
             if "unrecognized keysym" in output:
                 for line in output.split('\n'):
                     if "unrecognized keysym" in line:
-                        print('ERROR: {}'.format(line))
-                success = False
+                        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, 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
 
@@ -89,51 +163,9 @@ def xkbcomp(rmlvo):
         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()
+        tool = XkbCompInvocation(r, m, l, v, o)
+        tool.run()
+        return tool
     except KeyboardInterrupt:
         pass
 
@@ -165,13 +197,18 @@ def parse(path):
 
 def run(combos, tool, njobs):
     failed = False
-    with Pool(njobs) as p:
+    with multiprocessing.Pool(njobs) as p:
         results = p.imap_unordered(tool, combos)
-        for success, output in progress_bar(results, total=len(combos)):
-            if not success:
+        for invocation in progress_bar(results, total=len(combos)):
+            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)
+
     return failed
 
 
@@ -214,4 +251,4 @@ if __name__ == '__main__':
     try:
         main(sys.argv)
     except KeyboardInterrupt:
-        print('Exiting after Ctrl+C')
+        print('Exiting after Ctrl+C')