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