test: xkeyboard-config: print to stderr on failure, stdout otherwise
[platform/upstream/libxkbcommon.git] / test / xkeyboard-config-test.py.in
1 #!/usr/bin/env python
2 import argparse
3 import sys
4 import subprocess
5 import os
6 import io
7 import xml.etree.ElementTree as ET
8 from multiprocessing import Pool
9
10
11 verbose = True
12
13 DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
14
15 # Meson needs to fill this in so we can call the tool in the buildir.
16 EXTRA_PATH='@MESON_BUILD_ROOT@'
17 os.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
18
19
20 # The function generating the progress bar (if any).
21 progress_bar = lambda x, desc: x
22 if os.isatty(sys.stdout.fileno()):
23     try:
24         from tqdm import tqdm
25         progress_bar = tqdm
26
27         verbose = False
28     except ImportError:
29         pass
30
31
32 def xkbcommontool(rmlvo):
33     r = rmlvo.get('r', 'evdev')
34     m = rmlvo.get('m', 'pc105')
35     l = rmlvo.get('l', 'us')
36     v = rmlvo.get('v', None)
37     o = rmlvo.get('o', None)
38     args = [
39         'rmlvo-to-keymap',
40         '--rules', r,
41         '--model', m,
42         '--layout', l,
43     ]
44     if v is not None:
45         args += ['--variant', v]
46     if o is not None:
47         args += ['--options', o]
48
49     success = True
50     out = io.StringIO()
51     if verbose:
52         print(':: {}'.format(' '.join(args)), file=out)
53
54     try:
55         output = subprocess.check_output(args, stderr=subprocess.STDOUT)
56         if verbose:
57             print(output.decode('utf-8'), file=out)
58     except subprocess.CalledProcessError as err:
59         print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
60         print(err.output.decode('utf-8'), file=out)
61         success = False
62
63     return success, out.getvalue()
64
65
66 def xkbcomp(rmlvo):
67     r = rmlvo.get('r', 'evdev')
68     m = rmlvo.get('m', 'pc105')
69     l = rmlvo.get('l', 'us')
70     v = rmlvo.get('v', None)
71     o = rmlvo.get('o', None)
72     args = ['setxkbmap', '-print']
73     if r is not None:
74         args.append('-rules')
75         args.append('{}'.format(r))
76     if m is not None:
77         args.append('-model')
78         args.append('{}'.format(m))
79     if l is not None:
80         args.append('-layout')
81         args.append('{}'.format(l))
82     if o is not None:
83         args.append('-option')
84         args.append('{}'.format(o))
85
86     success = True
87     out = io.StringIO()
88     if verbose:
89         print(':: {}'.format(' '.join(args)), file=out)
90
91     try:
92         xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
93
94         setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
95         xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
96                                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97         setxkbmap.stdout.close()
98         stdout, stderr = xkbcomp.communicate()
99         if xkbcomp.returncode != 0:
100             print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
101             success = False
102         if xkbcomp.returncode != 0 or verbose:
103             print(stdout.decode('utf-8'), file=out)
104             print(stderr.decode('utf-8'), file=out)
105
106     # This catches setxkbmap errors.
107     except subprocess.CalledProcessError as err:
108         print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
109         print(err.output.decode('utf-8'), file=out)
110         success = False
111
112     return success, out.getvalue()
113
114
115 def parse(path):
116     root = ET.fromstring(open(path).read())
117     layouts = root.findall('layoutList/layout')
118
119     options = [
120         e.text
121         for e in root.findall('optionList/group/option/configItem/name')
122     ]
123
124     combos = []
125     for l in layouts:
126         layout = l.find('configItem/name').text
127         combos.append({'l': layout})
128
129         variants = l.findall('variantList/variant')
130         for v in variants:
131             variant = v.find('configItem/name').text
132
133             combos.append({'l': layout, 'v': variant})
134             for option in options:
135                 combos.append({'l': layout, 'v': variant, 'o': option})
136
137     return combos
138
139
140 def run(combos, tool, njobs):
141     failed = False
142     with Pool(njobs) as p:
143         results = p.imap_unordered(tool, combos)
144         for r in progress_bar(results, 'testing'):
145             success, output = r
146             if not success:
147                 failed = True
148             if output:
149                 print(output, file=sys.stdout if success else sys.stderr)
150
151     return failed
152
153
154 def main(args):
155     tools = {
156         'libxkbcommon': xkbcommontool,
157         'xkbcomp': xkbcomp,
158     }
159
160     parser = argparse.ArgumentParser(
161         description='Tool to test all layout/variant/option combinations.'
162     )
163     parser.add_argument('path', metavar='/path/to/evdev.xml',
164                         nargs='?', type=str,
165                         default=DEFAULT_RULES_XML,
166                         help='Path to xkeyboard-config\'s evdev.xml')
167     parser.add_argument('--tool', choices=tools.keys(),
168                         type=str, default='libxkbcommon',
169                         help='parsing tool to use')
170     parser.add_argument('--jobs', '-j', type=int,
171                         default=os.cpu_count() * 4,
172                         help='number of processes to use')
173     args = parser.parse_args()
174
175     tool = tools[args.tool]
176
177     combos = parse(args.path)
178     failed = run(combos, tool, args.jobs)
179     sys.exit(failed)
180
181
182 if __name__ == '__main__':
183     main(sys.argv)