| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- #!/usr/bin/env python3
- # vim: set expandtab shiftwidth=4:
- # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
- #
- # Copyright © 2018 Red Hat, Inc.
- #
- # Permission is hereby granted, free of charge, to any person obtaining a
- # copy of this software and associated documentation files (the "Software"),
- # to deal in the Software without restriction, including without limitation
- # the rights to use, copy, modify, merge, publish, distribute, sublicense,
- # and/or sell copies of the Software, and to permit persons to whom the
- # Software is furnished to do so, subject to the following conditions:
- #
- # The above copyright notice and this permission notice (including the next
- # paragraph) shall be included in all copies or substantial portions of the
- # Software.
- #
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- # DEALINGS IN THE SOFTWARE.
- import logging
- import os
- import resource
- import subprocess
- import sys
- try:
- import pytest
- except ImportError:
- print("Failed to import pytest. Skipping.", file=sys.stderr)
- sys.exit(77)
- logger = logging.getLogger("test")
- logger.setLevel(logging.DEBUG)
- if "@DISABLE_WARNING@" != "yes": # noqa: PLR0133
- print("This is the source file, run the one in the meson builddir instead")
- sys.exit(1)
- def _disable_coredump():
- resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
- def run_command(args):
- logger.debug(f"run command: {' '.join(args)}")
- with subprocess.Popen(
- args,
- preexec_fn=_disable_coredump, # noqa: PLW1509
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- ) as p:
- try:
- p.wait(0.7)
- except subprocess.TimeoutExpired:
- p.send_signal(3) # SIGQUIT
- stdout, stderr = p.communicate(timeout=5)
- if p.returncode == -3:
- p.returncode = 0
- return p.returncode, stdout.decode("UTF-8"), stderr.decode("UTF-8")
- class LibinputTool:
- libinput_tool = "libinput"
- subtool = None
- def __init__(self, subtool=None):
- self.libinput_tool = "@TOOL_PATH@"
- self.subtool = subtool
- def run_command(self, args):
- args = [self.libinput_tool] + args
- if self.subtool is not None:
- args.insert(1, self.subtool)
- return run_command(args)
- def run_command_success(self, args):
- rc, stdout, stderr = self.run_command(args)
- # if we're running as user, we might fail the command but we should
- # never get rc 2 (invalid usage)
- assert rc in [0, 1], (stdout, stderr)
- return stdout, stderr
- def run_command_invalid(self, args):
- rc, stdout, stderr = self.run_command(args)
- assert rc == 2, (rc, stdout, stderr)
- return rc, stdout, stderr
- def run_command_unrecognized_option(self, args):
- rc, stdout, stderr = self.run_command(args)
- assert rc == 2, (rc, stdout, stderr)
- assert stdout.startswith("Usage") or stdout == ""
- assert "unrecognized option" in stderr
- def run_command_missing_arg(self, args):
- rc, stdout, stderr = self.run_command(args)
- assert rc == 2, (rc, stdout, stderr)
- assert stdout.startswith("Usage") or stdout == ""
- assert "requires an argument" in stderr
- def run_command_unrecognized_tool(self, args):
- rc, stdout, stderr = self.run_command(args)
- assert rc == 2, (rc, stdout, stderr)
- assert stdout.startswith("Usage") or stdout == ""
- assert "is not installed" in stderr
- class LibinputDebugGui(LibinputTool):
- def __init__(self, subtool="debug-gui"):
- assert subtool == "debug-gui"
- super().__init__(subtool)
- debug_gui_enabled = "@MESON_ENABLED_DEBUG_GUI@".lower() == "true"
- if not debug_gui_enabled:
- pytest.skip()
- if not os.getenv("DISPLAY") and not os.getenv("WAYLAND_DISPLAY"):
- pytest.skip()
- # 77 means gtk_init() failed, which is probably because you can't
- # connect to the display server.
- rc, _, _ = self.run_command(["--help"])
- if rc == 77:
- pytest.skip()
- def get_tool(subtool=None):
- if subtool == "debug-gui":
- return LibinputDebugGui()
- else:
- return LibinputTool(subtool)
- @pytest.fixture
- def libinput():
- return get_tool()
- @pytest.fixture(params=["debug-events", "debug-gui"])
- def libinput_debug_tool(request):
- yield get_tool(request.param)
- @pytest.fixture
- def libinput_debug_events():
- return get_tool("debug-events")
- @pytest.fixture
- def libinput_debug_gui():
- return get_tool("debug-gui")
- @pytest.fixture
- def libinput_record():
- return get_tool("record")
- def test_help(libinput):
- stdout, stderr = libinput.run_command_success(["--help"])
- assert stdout.startswith("Usage:")
- assert stderr == ""
- def test_version(libinput):
- stdout, stderr = libinput.run_command_success(["--version"])
- assert stdout.startswith("1")
- assert stderr == ""
- @pytest.mark.parametrize("argument", ["--banana", "--foo", "--quiet", "--verbose"])
- def test_invalid_arguments(libinput, argument):
- libinput.run_command_unrecognized_option([argument])
- @pytest.mark.parametrize("tool", [["foo"], ["debug"], ["foo", "--quiet"]])
- def test_invalid_tool(libinput, tool):
- libinput.run_command_unrecognized_tool(tool)
- def test_udev_seat(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--udev"])
- libinput_debug_tool.run_command_success(["--udev", "seat0"])
- libinput_debug_tool.run_command_success(["--udev", "seat1"])
- @pytest.mark.skipif(os.environ.get("UDEV_NOT_AVAILABLE"), reason="udev required")
- def test_device_arg(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--device"])
- libinput_debug_tool.run_command_success(["--device", "/dev/input/event0"])
- libinput_debug_tool.run_command_success(["--device", "/dev/input/event1"])
- libinput_debug_tool.run_command_success(["/dev/input/event0"])
- options = {
- "pattern": ["sendevents"],
- # enable/disable options
- "enable-disable": [
- "tap",
- "drag",
- "drag-lock",
- "middlebutton",
- "natural-scrolling",
- "left-handed",
- "dwt",
- "dwtp",
- "scroll-button-lock",
- "plugins",
- ],
- # options with distinct values
- "enums": {
- "set-click-method": ["none", "clickfinger", "buttonareas"],
- "set-scroll-method": ["none", "twofinger", "edge", "button"],
- "set-profile": ["adaptive", "flat", "custom"],
- "set-tap-map": ["lrm", "lmr"],
- "set-clickfinger-map": ["lrm", "lmr"],
- "enable-drag-lock": ["sticky", "timeout"],
- "set-sendevents": ["disabled", "enabled", "disabled-on-external-mouse"],
- "enable-3fg-drag": ["3fg", "4fg", "disabled"],
- "set-eraser-button-mode": ["default", "button"],
- "set-eraser-button-button": ["BTN_STYLUS", "BTN_STYLUS2", "BTN_STYLUS3"],
- "set-custom-type": ["fallback", "motion", "scroll"],
- },
- # options with a range (and increment)
- "ranges": {
- "set-speed": (-1.0, +1.0, 0.1),
- "set-rotation": (0, 360, 10),
- },
- }
- # Options that allow for glob patterns
- @pytest.mark.parametrize("option", options["pattern"])
- def test_options_pattern(libinput_debug_tool, option):
- libinput_debug_tool.run_command_success([f"--disable-{option}", "*"])
- libinput_debug_tool.run_command_success([f"--disable-{option}", "abc*"])
- @pytest.mark.parametrize("option", options["enable-disable"])
- def test_options_enable_disable(libinput_debug_tool, option):
- libinput_debug_tool.run_command_success([f"--enable-{option}"])
- libinput_debug_tool.run_command_success([f"--disable-{option}"])
- @pytest.mark.parametrize("option", options["enums"].items())
- def test_options_enums(libinput_debug_tool, option):
- name, values = option
- for v in values:
- libinput_debug_tool.run_command_success([f"--{name}", v])
- libinput_debug_tool.run_command_success([f"--{name}={v}"])
- @pytest.mark.parametrize("option", options["ranges"].items())
- def test_options_ranges(libinput_debug_tool, option):
- name, values = option
- minimum, maximum, step = values
- value = minimum
- while value < maximum:
- libinput_debug_tool.run_command_success([f"--{name}", str(value)])
- libinput_debug_tool.run_command_success([f"--{name}={value}"])
- value += step
- libinput_debug_tool.run_command_success([f"--{name}", str(maximum)])
- libinput_debug_tool.run_command_success([f"--{name}={maximum}"])
- def test_apply_to(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--apply-to"])
- libinput_debug_tool.run_command_success(["--apply-to", "*foo*"])
- libinput_debug_tool.run_command_success(["--apply-to", "foobar"])
- libinput_debug_tool.run_command_success(["--apply-to", "any"])
- def test_set_scroll_button(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-scroll-button"])
- libinput_debug_tool.run_command_success(["--set-scroll-button", "BTN_LEFT"])
- libinput_debug_tool.run_command_success(["--set-scroll-button=BTN_RIGHT"])
- def test_set_custom_points(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-custom-points"])
- libinput_debug_tool.run_command_success(["--set-custom-points", "0.0;1.0"])
- libinput_debug_tool.run_command_success(["--set-custom-points=0.0;0.5;1.0"])
- def test_set_custom_step(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-custom-step"])
- libinput_debug_tool.run_command_success(["--set-custom-step", "0.5"])
- libinput_debug_tool.run_command_success(["--set-custom-step=1.0"])
- def test_set_pressure_range(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-pressure-range"])
- libinput_debug_tool.run_command_success(["--set-pressure-range", "0.1:0.9"])
- libinput_debug_tool.run_command_success(["--set-pressure-range=0.2:0.8"])
- def test_set_calibration(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-calibration"])
- libinput_debug_tool.run_command_success(
- ["--set-calibration", "1.0 0.0 0.0 0.0 1.0 0.0"]
- )
- def test_set_area(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-area"])
- libinput_debug_tool.run_command_success(["--set-area", "0.0/0.0 1.0/1.0"])
- def test_set_plugin_path(libinput_debug_tool):
- libinput_debug_tool.run_command_missing_arg(["--set-plugin-path"])
- libinput_debug_tool.run_command_success(["--set-plugin-path", "/usr/lib/libinput"])
- libinput_debug_tool.run_command_success(
- ["--set-plugin-path=/usr/lib/libinput:/usr/local/lib/libinput"]
- )
- @pytest.mark.parametrize(
- "args",
- [["--verbose"], ["--quiet"], ["--verbose", "--quiet"], ["--quiet", "--verbose"]],
- )
- def test_debug_events_verbose_quiet(libinput_debug_events, args):
- libinput_debug_events.run_command_success(args)
- def test_debug_events_compress_motion_events(libinput_debug_events):
- libinput_debug_events.run_command_success(["--compress-motion-events"])
- def test_debug_events_grab(libinput_debug_events):
- libinput_debug_events.run_command_success(["--grab"])
- def test_debug_gui_grab(libinput_debug_gui):
- libinput_debug_gui.run_command_success(["--grab"])
- @pytest.mark.parametrize("arg", ["--banana", "--foo", "--version"])
- def test_invalid_args(libinput_debug_tool, arg):
- libinput_debug_tool.run_command_unrecognized_option([arg])
- def test_libinput_debug_events_multiple_devices(libinput_debug_events):
- libinput_debug_events.run_command_success(
- ["--device", "/dev/input/event0", "/dev/input/event1"]
- )
- # same event path multiple times? meh, your problem
- libinput_debug_events.run_command_success(
- ["--device", "/dev/input/event0", "/dev/input/event0"]
- )
- libinput_debug_events.run_command_success(
- ["/dev/input/event0", "/dev/input/event1"]
- )
- def test_libinput_debug_events_too_many_devices(libinput_debug_events):
- # Too many arguments just bails with the usage message
- rc, stdout, stderr = libinput_debug_events.run_command(["/dev/input/event0"] * 61)
- assert rc == 2, (stdout, stderr)
- @pytest.mark.parametrize("arg", ["--quiet"])
- def test_libinput_debug_gui_invalid_arg(libinput_debug_gui, arg):
- libinput_debug_gui.run_command_unrecognized_option([arg])
- def test_libinput_debug_gui_verbose(libinput_debug_gui):
- libinput_debug_gui.run_command_success(["--verbose"])
- @pytest.mark.parametrize(
- "arg",
- [
- "--help",
- "--show-keycodes",
- "--with-libinput",
- "--with-hidraw",
- "--grab",
- "--no-events",
- ],
- )
- def test_libinput_record_args(libinput_record, arg):
- libinput_record.run_command_success([arg])
- def test_libinput_record_multiple_arg(libinput_record):
- # this arg is deprecated and a noop
- libinput_record.run_command_success(["--multiple"])
- @pytest.fixture
- def recording(tmp_path):
- return str((tmp_path / "record.out").resolve())
- def test_libinput_record_all(libinput_record, recording):
- libinput_record.run_command_success(["--all", "-o", recording])
- libinput_record.run_command_success(["--all", recording])
- def test_libinput_record_outfile(libinput_record, recording):
- libinput_record.run_command_success(["-o", recording])
- libinput_record.run_command_success(["--output-file", recording])
- libinput_record.run_command_success([f"--output-file={recording}"])
- def test_libinput_record_single(libinput_record, recording):
- libinput_record.run_command_success(["/dev/input/event0"])
- libinput_record.run_command_success(["-o", recording, "/dev/input/event0"])
- libinput_record.run_command_success(["/dev/input/event0", recording])
- libinput_record.run_command_success([recording, "/dev/input/event0"])
- def test_libinput_record_multiple(libinput_record, recording):
- libinput_record.run_command_success(
- ["-o", recording, "/dev/input/event0", "/dev/input/event1"]
- )
- libinput_record.run_command_success(
- [recording, "/dev/input/event0", "/dev/input/event1"]
- )
- libinput_record.run_command_success(
- ["/dev/input/event0", "/dev/input/event1", recording]
- )
- def test_libinput_record_autorestart(libinput_record, recording):
- libinput_record.run_command_invalid(["--autorestart"])
- libinput_record.run_command_success(["--autorestart=2"])
- libinput_record.run_command_success(["-o", recording, "--autorestart=2"])
- def test_libinput_record_no_events_autorestart(libinput_record):
- libinput_record.run_command_invalid(["--no-events", "--autorestart=2"])
- def main():
- args = ["-m", "pytest"]
- try:
- import xdist # noqa
- ncores = os.environ.get("FDO_CI_CONCURRENT", "auto")
- args += ["-n", ncores]
- except ImportError:
- logger.info("python-xdist missing, this test will be slow")
- args += ["@MESON_BUILD_ROOT@"]
- os.environ["LIBINPUT_RUNNING_TEST_SUITE"] = "1"
- return subprocess.run([sys.executable] + args, check=False).returncode
- if __name__ == "__main__":
- raise SystemExit(main())
|