1#!/usr/bin/env python3 2# vim: set expandtab shiftwidth=4: 3# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ 4# 5# Copyright © 2018 Red Hat, Inc. 6# 7# Permission is hereby granted, free of charge, to any person obtaining a 8# copy of this software and associated documentation files (the "Software"), 9# to deal in the Software without restriction, including without limitation 10# the rights to use, copy, modify, merge, publish, distribute, sublicense, 11# and/or sell copies of the Software, and to permit persons to whom the 12# Software is furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice (including the next 15# paragraph) shall be included in all copies or substantial portions of the 16# Software. 17# 18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24# DEALINGS IN THE SOFTWARE. 25 26import os 27import resource 28import sys 29import subprocess 30import logging 31 32try: 33 import pytest 34except ImportError: 35 print("Failed to import pytest. Skipping.", file=sys.stderr) 36 sys.exit(77) 37 38 39logger = logging.getLogger("test") 40logger.setLevel(logging.DEBUG) 41 42if "@DISABLE_WARNING@" != "yes": 43 print("This is the source file, run the one in the meson builddir instead") 44 sys.exit(1) 45 46 47def _disable_coredump(): 48 resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) 49 50 51def run_command(args): 52 logger.debug("run command: {}".format(" ".join(args))) 53 with subprocess.Popen( 54 args, 55 preexec_fn=_disable_coredump, 56 stdout=subprocess.PIPE, 57 stderr=subprocess.PIPE, 58 ) as p: 59 try: 60 p.wait(0.7) 61 except subprocess.TimeoutExpired: 62 p.send_signal(3) # SIGQUIT 63 stdout, stderr = p.communicate(timeout=5) 64 if p.returncode == -3: 65 p.returncode = 0 66 return p.returncode, stdout.decode("UTF-8"), stderr.decode("UTF-8") 67 68 69class LibinputTool(object): 70 libinput_tool = "libinput" 71 subtool = None 72 73 def __init__(self, subtool=None): 74 self.libinput_tool = "@TOOL_PATH@" 75 self.subtool = subtool 76 77 def run_command(self, args): 78 args = [self.libinput_tool] + args 79 if self.subtool is not None: 80 args.insert(1, self.subtool) 81 82 return run_command(args) 83 84 def run_command_success(self, args): 85 rc, stdout, stderr = self.run_command(args) 86 # if we're running as user, we might fail the command but we should 87 # never get rc 2 (invalid usage) 88 assert rc in [0, 1], (stdout, stderr) 89 return stdout, stderr 90 91 def run_command_invalid(self, args): 92 rc, stdout, stderr = self.run_command(args) 93 assert rc == 2, (rc, stdout, stderr) 94 return rc, stdout, stderr 95 96 def run_command_unrecognized_option(self, args): 97 rc, stdout, stderr = self.run_command(args) 98 assert rc == 2, (rc, stdout, stderr) 99 assert stdout.startswith("Usage") or stdout == "" 100 assert "unrecognized option" in stderr 101 102 def run_command_missing_arg(self, args): 103 rc, stdout, stderr = self.run_command(args) 104 assert rc == 2, (rc, stdout, stderr) 105 assert stdout.startswith("Usage") or stdout == "" 106 assert "requires an argument" in stderr 107 108 def run_command_unrecognized_tool(self, args): 109 rc, stdout, stderr = self.run_command(args) 110 assert rc == 2, (rc, stdout, stderr) 111 assert stdout.startswith("Usage") or stdout == "" 112 assert "is not installed" in stderr 113 114 115class LibinputDebugGui(LibinputTool): 116 def __init__(self, subtool="debug-gui"): 117 assert subtool == "debug-gui" 118 super().__init__(subtool) 119 120 debug_gui_enabled = "@MESON_ENABLED_DEBUG_GUI@" == "True" 121 if not debug_gui_enabled: 122 pytest.skip() 123 124 if not os.getenv("DISPLAY") and not os.getenv("WAYLAND_DISPLAY"): 125 pytest.skip() 126 127 # 77 means gtk_init() failed, which is probably because you can't 128 # connect to the display server. 129 rc, _, _ = self.run_command(["--help"]) 130 if rc == 77: 131 pytest.skip() 132 133 134def get_tool(subtool=None): 135 if subtool == "debug-gui": 136 return LibinputDebugGui() 137 else: 138 return LibinputTool(subtool) 139 140 141@pytest.fixture 142def libinput(): 143 return get_tool() 144 145 146@pytest.fixture(params=["debug-events", "debug-gui"]) 147def libinput_debug_tool(request): 148 yield get_tool(request.param) 149 150 151@pytest.fixture 152def libinput_debug_events(): 153 return get_tool("debug-events") 154 155 156@pytest.fixture 157def libinput_debug_gui(): 158 return get_tool("debug-gui") 159 160 161@pytest.fixture 162def libinput_record(): 163 return get_tool("record") 164 165 166def test_help(libinput): 167 stdout, stderr = libinput.run_command_success(["--help"]) 168 assert stdout.startswith("Usage:") 169 assert stderr == "" 170 171 172def test_version(libinput): 173 stdout, stderr = libinput.run_command_success(["--version"]) 174 assert stdout.startswith("1") 175 assert stderr == "" 176 177 178@pytest.mark.parametrize("argument", ["--banana", "--foo", "--quiet", "--verbose"]) 179def test_invalid_arguments(libinput, argument): 180 libinput.run_command_unrecognized_option([argument]) 181 182 183@pytest.mark.parametrize("tool", [["foo"], ["debug"], ["foo", "--quiet"]]) 184def test_invalid_tool(libinput, tool): 185 libinput.run_command_unrecognized_tool(tool) 186 187 188def test_udev_seat(libinput_debug_tool): 189 libinput_debug_tool.run_command_missing_arg(["--udev"]) 190 libinput_debug_tool.run_command_success(["--udev", "seat0"]) 191 libinput_debug_tool.run_command_success(["--udev", "seat1"]) 192 193 194@pytest.mark.skipif(os.environ.get("UDEV_NOT_AVAILABLE"), reason="udev required") 195def test_device_arg(libinput_debug_tool): 196 libinput_debug_tool.run_command_missing_arg(["--device"]) 197 libinput_debug_tool.run_command_success(["--device", "/dev/input/event0"]) 198 libinput_debug_tool.run_command_success(["--device", "/dev/input/event1"]) 199 libinput_debug_tool.run_command_success(["/dev/input/event0"]) 200 201 202options = { 203 "pattern": ["sendevents"], 204 # enable/disable options 205 "enable-disable": [ 206 "tap", 207 "drag", 208 "drag-lock", 209 "middlebutton", 210 "natural-scrolling", 211 "left-handed", 212 "dwt", 213 ], 214 # options with distinct values 215 "enums": { 216 "set-click-method": ["none", "clickfinger", "buttonareas"], 217 "set-scroll-method": ["none", "twofinger", "edge", "button"], 218 "set-profile": ["adaptive", "flat"], 219 "set-tap-map": ["lrm", "lmr"], 220 }, 221 # options with a range 222 "ranges": { 223 "set-speed": (float, -1.0, +1.0), 224 }, 225} 226 227 228# Options that allow for glob patterns 229@pytest.mark.parametrize("option", options["pattern"]) 230def test_options_pattern(libinput_debug_tool, option): 231 libinput_debug_tool.run_command_success(["--disable-{}".format(option), "*"]) 232 libinput_debug_tool.run_command_success(["--disable-{}".format(option), "abc*"]) 233 234 235@pytest.mark.parametrize("option", options["enable-disable"]) 236def test_options_enable_disable(libinput_debug_tool, option): 237 libinput_debug_tool.run_command_success(["--enable-{}".format(option)]) 238 libinput_debug_tool.run_command_success(["--disable-{}".format(option)]) 239 240 241@pytest.mark.parametrize("option", options["enums"].items()) 242def test_options_enums(libinput_debug_tool, option): 243 name, values = option 244 for v in values: 245 libinput_debug_tool.run_command_success(["--{}".format(name), v]) 246 libinput_debug_tool.run_command_success(["--{}={}".format(name, v)]) 247 248 249@pytest.mark.parametrize("option", options["ranges"].items()) 250def test_options_ranges(libinput_debug_tool, option): 251 name, values = option 252 range_type, minimum, maximum = values 253 assert range_type == float 254 step = (maximum - minimum) / 10.0 255 value = minimum 256 while value < maximum: 257 libinput_debug_tool.run_command_success(["--{}".format(name), str(value)]) 258 libinput_debug_tool.run_command_success(["--{}={}".format(name, value)]) 259 value += step 260 libinput_debug_tool.run_command_success(["--{}".format(name), str(maximum)]) 261 libinput_debug_tool.run_command_success(["--{}={}".format(name, maximum)]) 262 263 264def test_apply_to(libinput_debug_tool): 265 libinput_debug_tool.run_command_missing_arg(["--apply-to"]) 266 libinput_debug_tool.run_command_success(["--apply-to", "*foo*"]) 267 libinput_debug_tool.run_command_success(["--apply-to", "foobar"]) 268 libinput_debug_tool.run_command_success(["--apply-to", "any"]) 269 270 271@pytest.mark.parametrize( 272 "args", 273 [["--verbose"], ["--quiet"], ["--verbose", "--quiet"], ["--quiet", "--verbose"]], 274) 275def test_debug_events_verbose_quiet(libinput_debug_events, args): 276 libinput_debug_events.run_command_success(args) 277 278 279@pytest.mark.parametrize("arg", ["--banana", "--foo", "--version"]) 280def test_invalid_args(libinput_debug_tool, arg): 281 libinput_debug_tool.run_command_unrecognized_option([arg]) 282 283 284def test_libinput_debug_events_multiple_devices(libinput_debug_events): 285 libinput_debug_events.run_command_success( 286 ["--device", "/dev/input/event0", "/dev/input/event1"] 287 ) 288 # same event path multiple times? meh, your problem 289 libinput_debug_events.run_command_success( 290 ["--device", "/dev/input/event0", "/dev/input/event0"] 291 ) 292 libinput_debug_events.run_command_success( 293 ["/dev/input/event0", "/dev/input/event1"] 294 ) 295 296 297def test_libinput_debug_events_too_many_devices(libinput_debug_events): 298 # Too many arguments just bails with the usage message 299 rc, stdout, stderr = libinput_debug_events.run_command(["/dev/input/event0"] * 61) 300 assert rc == 2, (stdout, stderr) 301 302 303@pytest.mark.parametrize("arg", ["--quiet"]) 304def test_libinput_debug_gui_invalid_arg(libinput_debug_gui, arg): 305 libinput_debug_gui.run_command_unrecognized_option([arg]) 306 307 308def test_libinput_debug_gui_verbose(libinput_debug_gui): 309 libinput_debug_gui.run_command_success(["--verbose"]) 310 311 312@pytest.mark.parametrize( 313 "arg", ["--help", "--show-keycodes", "--with-libinput", "--with-hidraw"] 314) 315def test_libinput_record_args(libinput_record, arg): 316 libinput_record.run_command_success([arg]) 317 318 319def test_libinput_record_multiple_arg(libinput_record): 320 # this arg is deprecated and a noop 321 libinput_record.run_command_success(["--multiple"]) 322 323 324@pytest.fixture 325def recording(tmp_path): 326 return str((tmp_path / "record.out").resolve()) 327 328 329def test_libinput_record_all(libinput_record, recording): 330 libinput_record.run_command_success(["--all", "-o", recording]) 331 libinput_record.run_command_success(["--all", recording]) 332 333 334def test_libinput_record_outfile(libinput_record, recording): 335 libinput_record.run_command_success(["-o", recording]) 336 libinput_record.run_command_success(["--output-file", recording]) 337 libinput_record.run_command_success(["--output-file={}".format(recording)]) 338 339 340def test_libinput_record_single(libinput_record, recording): 341 libinput_record.run_command_success(["/dev/input/event0"]) 342 libinput_record.run_command_success(["-o", recording, "/dev/input/event0"]) 343 libinput_record.run_command_success(["/dev/input/event0", recording]) 344 libinput_record.run_command_success([recording, "/dev/input/event0"]) 345 346 347def test_libinput_record_multiple(libinput_record, recording): 348 libinput_record.run_command_success( 349 ["-o", recording, "/dev/input/event0", "/dev/input/event1"] 350 ) 351 libinput_record.run_command_success( 352 [recording, "/dev/input/event0", "/dev/input/event1"] 353 ) 354 libinput_record.run_command_success( 355 ["/dev/input/event0", "/dev/input/event1", recording] 356 ) 357 358 359def test_libinput_record_autorestart(libinput_record, recording): 360 libinput_record.run_command_invalid(["--autorestart"]) 361 libinput_record.run_command_invalid(["--autorestart=2"]) 362 libinput_record.run_command_success(["-o", recording, "--autorestart=2"]) 363 364 365def main(): 366 args = ["-m", "pytest"] 367 try: 368 import xdist # noqa 369 370 args += ["-n", "auto"] 371 except ImportError: 372 logger.info("python-xdist missing, this test will be slow") 373 pass 374 375 args += ["@MESON_BUILD_ROOT@"] 376 377 os.environ["LIBINPUT_RUNNING_TEST_SUITE"] = "1" 378 379 return subprocess.run([sys.executable] + args).returncode 380 381 382if __name__ == "__main__": 383 raise SystemExit(main()) 384