3 # Copyright © 2020 Red Hat, Inc.
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:
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
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.
35 top_builddir = os.environ["top_builddir"]
36 top_srcdir = os.environ["top_srcdir"]
39 "Required environment variables not found: top_srcdir/top_builddir",
42 from pathlib import Path
46 top_builddir = next(Path(".").glob("**/meson-logs/")).parent
50 'Using srcdir "{}", builddir "{}"'.format(top_srcdir, top_builddir),
55 logging.basicConfig(level=logging.DEBUG)
56 logger = logging.getLogger("test")
57 logger.setLevel(logging.DEBUG)
59 # Permutation of RMLVO that we use in multiple tests
62 for x in itertools.permutations(
63 ["--rules=evdev", "--model=pc104", "--layout=ch", "--options=eurosign:5"]
68 def _disable_coredump():
69 resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
72 def run_command(args):
73 logger.debug("run command: {}".format(" ".join(args)))
78 preexec_fn=_disable_coredump,
83 return p.returncode, p.stdout, p.stderr
84 except subprocess.TimeoutExpired as e:
85 return 0, e.stdout, e.stderr
89 xkbcli_tool = "xkbcli"
92 def __init__(self, subtool=None, skipIf=(), skipError=()):
93 self.tool_path = top_builddir
94 self.subtool = subtool
96 self.skipError = skipError
98 def run_command(self, args):
99 for condition, reason in self.skipIf:
101 raise unittest.SkipTest(reason)
102 if self.subtool is not None:
103 tool = "{}-{}".format(self.xkbcli_tool, self.subtool)
105 tool = self.xkbcli_tool
106 args = [os.path.join(self.tool_path, tool)] + args
108 return run_command(args)
110 def run_command_success(self, args):
111 rc, stdout, stderr = self.run_command(args)
113 for testfunc, reason in self.skipError:
114 if testfunc(rc, stdout, stderr):
115 raise unittest.SkipTest(reason)
116 assert rc == 0, (rc, stdout, stderr)
117 return stdout, stderr
119 def run_command_invalid(self, args):
120 rc, stdout, stderr = self.run_command(args)
121 assert rc == 2, (rc, stdout, stderr)
122 return rc, stdout, stderr
124 def run_command_unrecognized_option(self, args):
125 rc, stdout, stderr = self.run_command(args)
126 assert rc == 2, (rc, stdout, stderr)
127 assert stdout.startswith("Usage") or stdout == ""
128 assert "unrecognized option" in stderr
130 def run_command_missing_arg(self, args):
131 rc, stdout, stderr = self.run_command(args)
132 assert rc == 2, (rc, stdout, stderr)
133 assert stdout.startswith("Usage") or stdout == ""
134 assert "requires an argument" in stderr
137 return str(self.subtool)
140 class TestXkbcli(unittest.TestCase):
143 cls.xkbcli = XkbcliTool()
144 cls.xkbcli_list = XkbcliTool(
148 not int(os.getenv("HAVE_XKBCLI_LIST", "1")),
149 "xkbregistory not enabled",
153 cls.xkbcli_how_to_type = XkbcliTool("how-to-type")
154 cls.xkbcli_compile_keymap = XkbcliTool("compile-keymap")
155 cls.xkbcli_interactive_evdev = XkbcliTool(
159 not int(os.getenv("HAVE_XKBCLI_INTERACTIVE_EVDEV", "1")),
162 (not os.path.exists("/dev/input/event0"), "event node required"),
164 not os.access("/dev/input/event0", os.R_OK),
165 "insufficient permissions",
170 lambda rc, stdout, stderr: "Couldn't find any keyboards" in stderr,
171 "No keyboards available",
175 cls.xkbcli_interactive_x11 = XkbcliTool(
179 not int(os.getenv("HAVE_XKBCLI_INTERACTIVE_X11", "1")),
182 (not os.getenv("DISPLAY"), "DISPLAY not set"),
185 cls.xkbcli_interactive_wayland = XkbcliTool(
186 "interactive-wayland",
189 not int(os.getenv("HAVE_XKBCLI_INTERACTIVE_WAYLAND", "1")),
190 "wayland not enabled",
192 (not os.getenv("WAYLAND_DISPLAY"), "WAYLAND_DISPLAY not set"),
198 cls.xkbcli_how_to_type,
199 cls.xkbcli_compile_keymap,
200 cls.xkbcli_interactive_evdev,
201 cls.xkbcli_interactive_x11,
202 cls.xkbcli_interactive_wayland,
206 # --help is supported by all tools
207 for tool in self.all_tools:
208 with self.subTest(tool=tool):
209 stdout, stderr = tool.run_command_success(["--help"])
210 assert stdout.startswith("Usage:")
213 def test_invalid_option(self):
214 # --foobar generates "Usage:" for all tools
215 for tool in self.all_tools:
216 with self.subTest(tool=tool):
217 tool.run_command_unrecognized_option(["--foobar"])
219 def test_xkbcli_version(self):
221 stdout, stderr = self.xkbcli.run_command_success(["--version"])
222 assert stdout.startswith("1")
225 def test_xkbcli_too_many_args(self):
226 self.xkbcli.run_command_invalid(["a"] * 64)
228 def test_compile_keymap_args(self):
233 ["--verbose", "--rmlvo"],
234 # ['--verbose', '--kccgst'],
236 with self.subTest(args=args):
237 self.xkbcli_compile_keymap.run_command_success(args)
239 def test_compile_keymap_rmlvo(self):
241 with self.subTest(rmlvo=rmlvo):
242 self.xkbcli_compile_keymap.run_command_success(rmlvo)
244 def test_compile_keymap_include(self):
246 ["--include", ".", "--include-defaults"],
247 ["--include", "/tmp", "--include-defaults"],
249 with self.subTest(args=args):
250 # Succeeds thanks to include-defaults
251 self.xkbcli_compile_keymap.run_command_success(args)
253 def test_compile_keymap_include_invalid(self):
254 # A non-directory is rejected by default
255 args = ["--include", "/proc/version"]
256 rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
257 assert rc == 1, (stdout, stderr)
258 assert "There are no include paths to search" in stderr
260 # A non-existing directory is rejected by default
261 args = ["--include", "/tmp/does/not/exist"]
262 rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
263 assert rc == 1, (stdout, stderr)
264 assert "There are no include paths to search" in stderr
266 # Valid dir, but missing files
267 args = ["--include", "/tmp"]
268 rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
269 assert rc == 1, (stdout, stderr)
270 assert "Couldn't look up rules" in stderr
272 def test_how_to_type(self):
273 # Unicode codepoint conversions, we support whatever strtol does
274 for args in (["123"], ["0x123"], ["0123"]):
275 with self.subTest(args=args):
276 self.xkbcli_how_to_type.run_command_success(args)
278 def test_how_to_type_rmlvo(self):
280 with self.subTest(rmlvo=rmlvo):
281 args = rmlvo + ["0x1234"]
282 self.xkbcli_how_to_type.run_command_success(args)
284 def test_list_rmlvo(self):
288 ["--verbose", "--load-exotic"],
293 with self.subTest(args=args):
294 self.xkbcli_list.run_command_success(args)
296 def test_list_rmlvo_includes(self):
298 self.xkbcli_list.run_command_success(args)
300 def test_list_rmlvo_includes_invalid(self):
301 args = ["/proc/version"]
302 rc, stdout, stderr = self.xkbcli_list.run_command(args)
304 assert "Failed to append include path" in stderr
306 def test_list_rmlvo_includes_no_defaults(self):
307 args = ["--skip-default-paths", "/tmp"]
308 rc, stdout, stderr = self.xkbcli_list.run_command(args)
310 assert "Failed to parse XKB description" in stderr
312 def test_interactive_evdev_rmlvo(self):
314 with self.subTest(rmlvo=rmlvo):
315 self.xkbcli_interactive_evdev.run_command_success(rmlvo)
317 def test_interactive_evdev(self):
318 # Note: --enable-compose fails if $prefix doesn't have the compose tables
321 ["--report-state-changes"],
322 ["--enable-compose"],
323 ["--consumed-mode=xkb"],
324 ["--consumed-mode=gtk"],
325 ["--without-x11-offset"],
327 with self.subTest(args=args):
328 self.xkbcli_interactive_evdev.run_command_success(args)
330 def test_interactive_x11(self):
331 # To be filled in if we handle something other than --help
334 def test_interactive_wayland(self):
335 # To be filled in if we handle something other than --help
339 if __name__ == "__main__":
340 with tempfile.TemporaryDirectory() as tmpdir:
341 # Use our own test xkeyboard-config copy.
342 os.environ["XKB_CONFIG_ROOT"] = top_srcdir + "/test/data"
343 # Use our own X11 locale copy.
344 os.environ["XLOCALEDIR"] = top_srcdir + "/test/data/locale"
345 # Use our own locale.
346 os.environ["LC_CTYPE"] = "en_US.UTF-8"
347 # libxkbcommon has fallbacks when XDG_CONFIG_HOME isn't set so we need
348 # to override it with a known (empty) directory. Otherwise our test
349 # behavior depends on the system the test is run on.
350 os.environ["XDG_CONFIG_HOME"] = tmpdir
351 # Prevent the legacy $HOME/.xkb from kicking in.
352 del os.environ["HOME"]
353 # This needs to be separated if we do specific extra path testing
354 os.environ["XKB_CONFIG_EXTRA_PATH"] = tmpdir