test/data: update host.xkb to match keymap-dump style
[platform/upstream/libxkbcommon.git] / test / tool-option-parsing.py
1 #!/usr/bin/env python3
2 #
3 # Copyright © 2020 Red Hat, Inc.
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the "Software"),
7 # to deal in the Software without restriction, including without limitation
8 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 # and/or sell copies of the Software, and to permit persons to whom the
10 # Software is furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice (including the next
13 # paragraph) shall be included in all copies or substantial portions of the
14 # Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 # DEALINGS IN THE SOFTWARE.
23
24 import itertools
25 import os
26 import resource
27 import sys
28 import subprocess
29 import logging
30 import tempfile
31 import unittest
32
33
34 top_builddir = os.environ['top_builddir']
35 top_srcdir = os.environ['top_srcdir']
36
37 logging.basicConfig(level=logging.DEBUG)
38 logger = logging.getLogger('test')
39 logger.setLevel(logging.DEBUG)
40
41 # Permutation of RMLVO that we use in multiple tests
42 rmlvos = [list(x) for x in itertools.permutations(
43     ['--rules=evdev', '--model=pc104',
44      '--layout=ch', '--options=eurosign:5']
45 )]
46
47
48 def _disable_coredump():
49     resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
50
51
52 def run_command(args):
53     logger.debug('run command: {}'.format(' '.join(args)))
54
55     try:
56         p = subprocess.run(args, preexec_fn=_disable_coredump,
57                            capture_output=True, text=True,
58                            timeout=0.7)
59         return p.returncode, p.stdout, p.stderr
60     except subprocess.TimeoutExpired as e:
61         return 0, e.stdout, e.stderr
62
63
64 class XkbcliTool:
65     xkbcli_tool = 'xkbcli'
66     subtool = None
67
68     def __init__(self, subtool=None, skipIf=()):
69         self.tool_path = top_builddir
70         self.subtool = subtool
71         self.skipIf = skipIf
72
73     def run_command(self, args):
74         for condition, reason in self.skipIf:
75             if condition:
76                 raise unittest.SkipTest(reason)
77         if self.subtool is not None:
78             tool = '{}-{}'.format(self.xkbcli_tool, self.subtool)
79         else:
80             tool = self.xkbcli_tool
81         args = [os.path.join(self.tool_path, tool)] + args
82
83         return run_command(args)
84
85     def run_command_success(self, args):
86         rc, stdout, stderr = self.run_command(args)
87         assert rc == 0, (stdout, stderr)
88         return stdout, stderr
89
90     def run_command_invalid(self, args):
91         rc, stdout, stderr = self.run_command(args)
92         assert rc == 2, (rc, stdout, stderr)
93         return rc, stdout, stderr
94
95     def run_command_unrecognized_option(self, args):
96         rc, stdout, stderr = self.run_command(args)
97         assert rc == 2, (rc, stdout, stderr)
98         assert stdout.startswith('Usage') or stdout == ''
99         assert 'unrecognized option' in stderr
100
101     def run_command_missing_arg(self, args):
102         rc, stdout, stderr = self.run_command(args)
103         assert rc == 2, (rc, stdout, stderr)
104         assert stdout.startswith('Usage') or stdout == ''
105         assert 'requires an argument' in stderr
106
107     def __str__(self):
108         return str(self.subtool)
109
110
111 class TestXkbcli(unittest.TestCase):
112     @classmethod
113     def setUpClass(cls):
114         cls.xkbcli = XkbcliTool()
115         cls.xkbcli_list = XkbcliTool('list', skipIf=(
116             (not int(os.getenv('HAVE_XKBCLI_LIST', '1')), 'xkbregistory not enabled'),
117         ))
118         cls.xkbcli_how_to_type = XkbcliTool('how-to-type')
119         cls.xkbcli_compile_keymap = XkbcliTool('compile-keymap')
120         cls.xkbcli_interactive_evdev = XkbcliTool('interactive-evdev', skipIf=(
121             (not int(os.getenv('HAVE_XKBCLI_INTERACTIVE_EVDEV', '1')), 'evdev not enabled'),
122             (not os.path.exists('/dev/input/event0'), 'event node required'),
123             (not os.access('/dev/input/event0', os.R_OK), 'insufficient permissions'),
124         ))
125         cls.xkbcli_interactive_x11 = XkbcliTool('interactive-x11', skipIf=(
126             (not int(os.getenv('HAVE_XKBCLI_INTERACTIVE_X11', '1')), 'x11 not enabled'),
127             (not os.getenv('DISPLAY'), 'DISPLAY not set'),
128         ))
129         cls.xkbcli_interactive_wayland = XkbcliTool('interactive-wayland', skipIf=(
130             (not int(os.getenv('HAVE_XKBCLI_INTERACTIVE_WAYLAND', '1')), 'wayland not enabled'),
131             (not os.getenv('WAYLAND_DISPLAY'), 'WAYLAND_DISPLAY not set'),
132         ))
133         cls.all_tools = [
134             cls.xkbcli,
135             cls.xkbcli_list,
136             cls.xkbcli_how_to_type,
137             cls.xkbcli_compile_keymap,
138             cls.xkbcli_interactive_evdev,
139             cls.xkbcli_interactive_x11,
140             cls.xkbcli_interactive_wayland,
141         ]
142
143     def test_help(self):
144         # --help is supported by all tools
145         for tool in self.all_tools:
146             with self.subTest(tool=tool):
147                 stdout, stderr = tool.run_command_success(['--help'])
148                 assert stdout.startswith('Usage:')
149                 assert stderr == ''
150
151     def test_invalid_option(self):
152         # --foobar generates "Usage:" for all tools
153         for tool in self.all_tools:
154             with self.subTest(tool=tool):
155                 tool.run_command_unrecognized_option(['--foobar'])
156
157     def test_xkbcli_version(self):
158         # xkbcli --version
159         stdout, stderr = self.xkbcli.run_command_success(['--version'])
160         assert stdout.startswith('1')
161         assert stderr == ''
162
163     def test_xkbcli_too_many_args(self):
164         self.xkbcli.run_command_invalid(['a'] * 64)
165
166     def test_compile_keymap_args(self):
167         for args in (
168             ['--verbose'],
169             ['--rmlvo'],
170             # ['--kccgst'],
171             ['--verbose', '--rmlvo'],
172             # ['--verbose', '--kccgst'],
173         ):
174             with self.subTest(args=args):
175                 self.xkbcli_compile_keymap.run_command_success(args)
176
177     def test_compile_keymap_rmlvo(self):
178         for rmlvo in rmlvos:
179             with self.subTest(rmlvo=rmlvo):
180                 self.xkbcli_compile_keymap.run_command_success(rmlvo)
181
182     def test_compile_keymap_include(self):
183         for args in (
184             ['--include', '.', '--include-defaults'],
185             ['--include', '/tmp', '--include-defaults'],
186         ):
187             with self.subTest(args=args):
188                 # Succeeds thanks to include-defaults
189                 self.xkbcli_compile_keymap.run_command_success(args)
190
191     def test_compile_keymap_include_invalid(self):
192         # A non-directory is rejected by default
193         args = ['--include', '/proc/version']
194         rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
195         assert rc == 1, (stdout, stderr)
196         assert "There are no include paths to search" in stderr
197
198         # A non-existing directory is rejected by default
199         args = ['--include', '/tmp/does/not/exist']
200         rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
201         assert rc == 1, (stdout, stderr)
202         assert "There are no include paths to search" in stderr
203
204         # Valid dir, but missing files
205         args = ['--include', '/tmp']
206         rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
207         assert rc == 1, (stdout, stderr)
208         assert "Couldn't look up rules" in stderr
209
210     def test_how_to_type(self):
211         # Unicode codepoint conversions, we support whatever strtol does
212         for args in (['123'], ['0x123'], ['0123']):
213             with self.subTest(args=args):
214                 self.xkbcli_how_to_type.run_command_success(args)
215
216     def test_how_to_type_rmlvo(self):
217         for rmlvo in rmlvos:
218             with self.subTest(rmlvo=rmlvo):
219                 args = rmlvo + ['0x1234']
220                 self.xkbcli_how_to_type.run_command_success(args)
221
222     def test_list_rmlvo(self):
223         for args in (
224             ['--verbose'],
225             ['-v'],
226             ['--verbose', '--load-exotic'],
227             ['--load-exotic'],
228             ['--ruleset=evdev'],
229             ['--ruleset=base'],
230         ):
231             with self.subTest(args=args):
232                 self.xkbcli_list.run_command_success(args)
233
234     def test_list_rmlvo_includes(self):
235         args = ['/tmp/']
236         self.xkbcli_list.run_command_success(args)
237
238     def test_list_rmlvo_includes_invalid(self):
239         args = ['/proc/version']
240         rc, stdout, stderr = self.xkbcli_list.run_command(args)
241         assert rc == 1
242         assert "Failed to append include path" in stderr
243
244     def test_list_rmlvo_includes_no_defaults(self):
245         args = ['--skip-default-paths', '/tmp']
246         rc, stdout, stderr = self.xkbcli_list.run_command(args)
247         assert rc == 1
248         assert "Failed to parse XKB description" in stderr
249
250     def test_interactive_evdev_rmlvo(self):
251         for rmlvo in rmlvos:
252             with self.subTest(rmlvo=rmlvo):
253                 self.xkbcli_interactive_evdev.run_command_success(rmlvos)
254
255     def test_interactive_evdev(self):
256         # Note: --enable-compose fails if $prefix doesn't have the compose tables
257         # installed
258         for args in (
259             ['--report-state-changes'],
260             ['--enable-compose'],
261             ['--consumed-mode=xkb'],
262             ['--consumed-mode=gtk'],
263             ['--without-x11-offset'],
264         ):
265             with self.subTest(args=args):
266                 self.xkbcli_interactive_evdev.run_command_success(args)
267
268     def test_interactive_x11(self):
269         # To be filled in if we handle something other than --help
270         pass
271
272     def test_interactive_wayland(self):
273         # To be filled in if we handle something other than --help
274         pass
275
276
277 if __name__ == '__main__':
278     with tempfile.TemporaryDirectory() as tmpdir:
279         # Use our own test xkeyboard-config copy.
280         os.environ['XKB_CONFIG_ROOT'] = top_srcdir + '/test/data'
281         # libxkbcommon has fallbacks when XDG_CONFIG_HOME isn't set so we need
282         # to override it with a known (empty) directory. Otherwise our test
283         # behavior depends on the system the test is run on.
284         os.environ['XDG_CONFIG_HOME'] = tmpdir
285         # Prevent the legacy $HOME/.xkb from kicking in.
286         del os.environ['HOME']
287         # This needs to be separated if we do specific extra path testing
288         os.environ['XKB_CONFIG_EXTRA_PATH'] = tmpdir
289
290         unittest.main()