test: add proper --verbose handling to the xkeyboard-config tester
[platform/upstream/libxkbcommon.git] / test / xkeyboard-config-test.py.in
1 #!/usr/bin/env python3
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 = False
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 def escape(s):
21     return s.replace('"', '\\"')
22
23
24 # The function generating the progress bar (if any).
25 def create_progress_bar(verbose):
26     def noop_progress_bar(x, total):
27         return x
28
29     progress_bar = noop_progress_bar
30     if not verbose and os.isatty(sys.stdout.fileno()):
31         try:
32             from tqdm import tqdm
33             progress_bar = tqdm
34         except ImportError:
35             pass
36
37     return progress_bar
38
39
40 def xkbcommontool(rmlvo):
41     try:
42         r = rmlvo.get('r', 'evdev')
43         m = rmlvo.get('m', 'pc105')
44         l = rmlvo.get('l', 'us')
45         v = rmlvo.get('v', None)
46         o = rmlvo.get('o', None)
47         args = [
48             'xkbcli-compile-keymap',  # this is run in the builddir
49             '--verbose',
50             '--rules', r,
51             '--model', m,
52             '--layout', l,
53         ]
54         if v is not None:
55             args += ['--variant', v]
56         if o is not None:
57             args += ['--options', o]
58
59         success = True
60         out = io.StringIO()
61         if verbose:
62             print(':: {}'.format(' '.join(args)), file=out)
63
64         try:
65             output = subprocess.check_output(args, stderr=subprocess.STDOUT,
66                                              universal_newlines=True)
67             if verbose:
68                 print(output, file=out)
69
70             if "unrecognized keysym" in output:
71                 for line in output.split('\n'):
72                     if "unrecognized keysym" in line:
73                         print('ERROR: {}'.format(line))
74                 success = False
75         except subprocess.CalledProcessError as err:
76             print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
77             print(err.output, file=out)
78             success = False
79
80         return success, out.getvalue()
81     except KeyboardInterrupt:
82         pass
83
84
85 def xkbcomp(rmlvo):
86     try:
87         r = rmlvo.get('r', 'evdev')
88         m = rmlvo.get('m', 'pc105')
89         l = rmlvo.get('l', 'us')
90         v = rmlvo.get('v', None)
91         o = rmlvo.get('o', None)
92         args = ['setxkbmap', '-print']
93         if r is not None:
94             args.append('-rules')
95             args.append('{}'.format(r))
96         if m is not None:
97             args.append('-model')
98             args.append('{}'.format(m))
99         if l is not None:
100             args.append('-layout')
101             args.append('{}'.format(l))
102         if v is not None:
103             args.append('-variant')
104             args.append('{}'.format(v))
105         if o is not None:
106             args.append('-option')
107             args.append('{}'.format(o))
108
109         success = True
110         out = io.StringIO()
111         if verbose:
112             print(':: {}'.format(' '.join(args)), file=out)
113
114         try:
115             xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
116
117             setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
118             xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
119                                        stdout=subprocess.PIPE, stderr=subprocess.PIPE,
120                                        universal_newlines=True)
121             setxkbmap.stdout.close()
122             stdout, stderr = xkbcomp.communicate()
123             if xkbcomp.returncode != 0:
124                 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
125                 success = False
126             if xkbcomp.returncode != 0 or verbose:
127                 print(stdout, file=out)
128                 print(stderr, file=out)
129
130         # This catches setxkbmap errors.
131         except subprocess.CalledProcessError as err:
132             print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
133             print(err.output, file=out)
134             success = False
135
136         return success, out.getvalue()
137     except KeyboardInterrupt:
138         pass
139
140
141 def parse(path):
142     root = ET.fromstring(open(path).read())
143     layouts = root.findall('layoutList/layout')
144
145     options = [
146         e.text
147         for e in root.findall('optionList/group/option/configItem/name')
148     ]
149
150     combos = []
151     for l in layouts:
152         layout = l.find('configItem/name').text
153         combos.append({'l': layout})
154
155         variants = l.findall('variantList/variant')
156         for v in variants:
157             variant = v.find('configItem/name').text
158
159             combos.append({'l': layout, 'v': variant})
160             for option in options:
161                 combos.append({'l': layout, 'v': variant, 'o': option})
162
163     return combos
164
165
166 def run(combos, tool, njobs):
167     failed = False
168     with Pool(njobs) as p:
169         results = p.imap_unordered(tool, combos)
170         for success, output in progress_bar(results, total=len(combos)):
171             if not success:
172                 failed = True
173             if output:
174                 print(output, file=sys.stdout if success else sys.stderr)
175     return failed
176
177
178 def main(args):
179     global progress_bar
180     global verbose
181
182     tools = {
183         'libxkbcommon': xkbcommontool,
184         'xkbcomp': xkbcomp,
185     }
186
187     parser = argparse.ArgumentParser(
188         description='Tool to test all layout/variant/option combinations.'
189     )
190     parser.add_argument('path', metavar='/path/to/evdev.xml',
191                         nargs='?', type=str,
192                         default=DEFAULT_RULES_XML,
193                         help='Path to xkeyboard-config\'s evdev.xml')
194     parser.add_argument('--tool', choices=tools.keys(),
195                         type=str, default='libxkbcommon',
196                         help='parsing tool to use')
197     parser.add_argument('--jobs', '-j', type=int,
198                         default=os.cpu_count() * 4,
199                         help='number of processes to use')
200     parser.add_argument('--verbose', '-v', default=False, action="store_true")
201     args = parser.parse_args()
202
203     verbose = args.verbose
204     progress_bar = create_progress_bar(verbose)
205
206     tool = tools[args.tool]
207
208     combos = parse(args.path)
209     failed = run(combos, tool, args.jobs)
210     sys.exit(failed)
211
212
213 if __name__ == '__main__':
214     try:
215         main(sys.argv)
216     except KeyboardInterrupt:
217         print('Exiting after Ctrl+C')