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