diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..33c2be8c --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +name: Lint + +on: [push, pull_request] + +jobs: + black: + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository + strategy: + matrix: + python-version: ["3.10"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + scripts/ci-install-deps.sh + pip install black + - name: Analysing the code with black --check --diff + run: | + black --check --diff ./inputremapper ./tests + diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml new file mode 100644 index 00000000..6e1369c9 --- /dev/null +++ b/.github/workflows/reviewdog.yml @@ -0,0 +1,46 @@ +--- +name: reviewdog +# run reviewdog for PR only because "github-check" option is failing :( +# https://github.com/reviewdog/reviewdog/issues/924 +on: [pull_request] + +jobs: + reviewdog_python: + name: reviewdog - Python lint + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10"] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: reviewdog/action-setup@master + with: + reviewdog_version: latest + - name: Install dependencies + shell: bash + run: | + scripts/ci-install-deps.sh + pip install flake8 pylint mypy black + - name: Set env for PR + if: github.event_name == 'pull_request' + shell: bash + run: echo "REWIEVDOG_REPORTER=github-pr-review" >> $GITHUB_ENV + + - name: Set env for push + if: github.event_name != 'pull_request' + shell: bash + run: echo "REWIEVDOG_REPORTER=github-check" >> $GITHUB_ENV + + - name: Run reviewdog + shell: bash + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + reviewdog -list + reviewdog -tee -runners=mypy,black -reporter=${{ env.REWIEVDOG_REPORTER }} -fail-on-error=false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..1c73fd75 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: Test + +on: [push, pull_request] + +jobs: + build: + continue-on-error: true + runs-on: ubuntu-latest + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository + strategy: + matrix: + python-version: ["3.7", "3.10"] # min and max supported versions? + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + # Install deps as root since we will run tests as root + sudo scripts/ci-install-deps.sh + sudo pip install . + - name: Run tests + run: | + # FIXME: Had some permissions issues, currently worked around by running tests as root + mkdir test_tmp + TMPDIR="$(realpath test_tmp)" sudo python tests/test.py --start-dir unit diff --git a/.reviewdog.yml b/.reviewdog.yml new file mode 100644 index 00000000..849929f9 --- /dev/null +++ b/.reviewdog.yml @@ -0,0 +1,21 @@ +--- +runner: + mypy: + name: mypy + cmd: mypy --show-column-numbers inputremapper tests --ignore-missing-imports + errorformat: + - "%f:%l:%c: %m" + + pylint: + name: pylint + cmd: pylint inputremapper tests --extension-pkg-whitelist=evdev + errorformat: + - "%f:%l:%c: %t%n: %m" + + flake8: + cmd: flake8 inputremapper tests + format: flake8 + + black: + cmd: black --diff --quiet --check ./inputremapper ./tests + format: black diff --git a/inputremapper/injection/consumers/joystick_to_mouse.py b/inputremapper/injection/consumers/joystick_to_mouse.py index 5fdf7454..a9521f71 100644 --- a/inputremapper/injection/consumers/joystick_to_mouse.py +++ b/inputremapper/injection/consumers/joystick_to_mouse.py @@ -213,7 +213,7 @@ class JoystickToMouse(Consumer): non_linearity = preset.get("gamepad.joystick.non_linearity") x_scroll_speed = preset.get("gamepad.joystick.x_scroll_speed") y_scroll_speed = preset.get("gamepad.joystick.y_scroll_speed") - max_speed = 2 ** 0.5 # for normalized abs event values + max_speed = 2**0.5 # for normalized abs event values if abs_range is not None: logger.info( diff --git a/inputremapper/input_event.py b/inputremapper/input_event.py index 66f4e9df..3acdbd6c 100644 --- a/inputremapper/input_event.py +++ b/inputremapper/input_event.py @@ -27,7 +27,9 @@ from typing import Tuple from inputremapper.exceptions import InputEventCreationError -@dataclass(frozen=True) # Todo: add slots=True as soon as python 3.10 is in common distros +@dataclass( + frozen=True +) # Todo: add slots=True as soon as python 3.10 is in common distros class InputEvent: """ the evnet used by inputremapper diff --git a/scripts/ci-install-deps.sh b/scripts/ci-install-deps.sh new file mode 100755 index 00000000..e89efa76 --- /dev/null +++ b/scripts/ci-install-deps.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Called from multiple CI pipelines in .github/workflows +set -xeuo pipefail + +# native deps +# gettext required to generate translations, others are python deps +sudo apt-get install -y gettext python3-evdev python3-pydbus python3-pydantic + +# ensure pip and setuptools/wheel up to date so can install all pip modules +python -m pip install --upgrade pip +pip install wheel setuptools + +# install test deps which aren't in setup.py +pip install psutil diff --git a/setup.py b/setup.py index 623e8c64..ce5292a2 100644 --- a/setup.py +++ b/setup.py @@ -129,13 +129,7 @@ setup( ("/usr/bin/", ["bin/key-mapper-service"]), ("/usr/bin/", ["bin/key-mapper-control"]), ], - install_requires=[ - "setuptools", - "evdev", - "pydbus", - "pygobject", - "pydantic" - ], + install_requires=["setuptools", "evdev", "pydbus", "pygobject", "pydantic"], cmdclass={ "install": Install, }, diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..4d669e26 --- /dev/null +++ b/shell.nix @@ -0,0 +1,48 @@ +# shell.nix - used with nix-shell to get a development environment with necessary dependencies +# Should be enough to run unit tests, integration tests and the service won't work +# If you don't use nix, don't worry about/use this file +let + pkgs = import { }; + python = pkgs.python310; +in +pkgs.mkShell { + nativeBuildInputs = [ + pkgs.pkg-config + pkgs.wrapGAppsHook + ]; + buildInputs = [ + pkgs.gobject-introspection + pkgs.gtk3 + pkgs.bashInteractive + pkgs.gobject-introspection + pkgs.xlibs.xmodmap + pkgs.gtksourceview4 + (python.withPackages ( + python-packages: with python-packages; [ + pip + wheel + setuptools # for pkg_resources + types-setuptools + + evdev + pydbus + pygobject3 + pydantic + + psutil # only used in tests + ] + )) + ]; + # https://nixos.wiki/wiki/Python#Emulating_virtualenv_with_nix-shell + shellHook = '' + # Tells pip to put packages into $PIP_PREFIX instead of the usual locations. + # See https://pip.pypa.io/en/stable/user_guide/#environment-variables. + export PIP_PREFIX=$(pwd)/venv + export PYTHONPATH="$PIP_PREFIX/${python.sitePackages}:$PYTHONPATH" + export PATH="$PIP_PREFIX/bin:$PATH" + unset SOURCE_DATE_EPOCH + + python setup.py egg_info + pip install `grep -v '^\[' *.egg-info/requires.txt` || true + ''; +} diff --git a/tests/integration/test_gui.py b/tests/integration/test_gui.py index 541bbff7..72307b8c 100644 --- a/tests/integration/test_gui.py +++ b/tests/integration/test_gui.py @@ -699,12 +699,18 @@ class TestGui(GuiTestBase): def test_editor_keycode_to_string(self): # not an integration test, but I have all the selection_label tests here already - self.assertEqual(EventCombination((EV_KEY, evdev.ecodes.KEY_A, 1)).beautify(), "a") + self.assertEqual( + EventCombination((EV_KEY, evdev.ecodes.KEY_A, 1)).beautify(), "a" + ) self.assertEqual( EventCombination([EV_KEY, evdev.ecodes.KEY_A, 1]).beautify(), "a" ) - self.assertEqual(EventCombination((EV_ABS, evdev.ecodes.ABS_HAT0Y, -1)).beautify(), "DPad Up") - self.assertEqual(EventCombination((EV_KEY, evdev.ecodes.BTN_A, 1)).beautify(), "Button A") + self.assertEqual( + EventCombination((EV_ABS, evdev.ecodes.ABS_HAT0Y, -1)).beautify(), "DPad Up" + ) + self.assertEqual( + EventCombination((EV_KEY, evdev.ecodes.BTN_A, 1)).beautify(), "Button A" + ) self.assertEqual(EventCombination((EV_KEY, 1234, 1)).beautify(), "1234") self.assertEqual( EventCombination([EV_ABS, evdev.ecodes.ABS_HAT0X, -1]).beautify(), @@ -1736,7 +1742,7 @@ class TestGui(GuiTestBase): gtk_iteration() speed = active_preset.get("gamepad.joystick.pointer_speed") active_preset.set("gamepad.joystick.non_linearity", 1) - self.assertEqual(speed, 2 ** 6) + self.assertEqual(speed, 2**6) # don't consume the events in the reader, they are used to test # the injection diff --git a/tests/test.py b/tests/test.py index 89f6f49b..6e52ee65 100644 --- a/tests/test.py +++ b/tests/test.py @@ -20,6 +20,7 @@ """Sets up inputremapper for the tests and runs them.""" +import argparse import os import sys import tempfile @@ -663,7 +664,15 @@ cleanup() def main(): - modules = sys.argv[1:] + # https://docs.python.org/3/library/argparse.html + parser = argparse.ArgumentParser(description=__doc__) + # repeated argument 0 or more times with modules + parser.add_argument("modules", type=str, nargs="*") + # start-dir value if not using modules, allows eg python tests/test.py --start-dir unit + parser.add_argument("--start-dir", type=str, default=".") + parsed_args = parser.parse_args() # takes from sys.argv by default + modules = parsed_args.modules + # discoverer is really convenient, but it can't find a specific test # in all of the available tests like unittest.main() does..., # so provide both options. @@ -674,7 +683,9 @@ def main(): testsuite = unittest.defaultTestLoader.loadTestsFromNames(modules) else: # run all tests by default - testsuite = unittest.defaultTestLoader.discover(".", pattern="test_*.py") + testsuite = unittest.defaultTestLoader.discover( + parsed_args.start_dir, pattern="test_*.py" + ) # add a newline to each "qux (foo.bar)..." output before each test, # because the first log will be on the same line otherwise diff --git a/tests/unit/test_daemon.py b/tests/unit/test_daemon.py index da218063..628eecc2 100644 --- a/tests/unit/test_daemon.py +++ b/tests/unit/test_daemon.py @@ -27,7 +27,6 @@ import json import evdev from evdev.ecodes import EV_KEY, EV_ABS, KEY_B, KEY_A -from gi.repository import Gtk from pydbus import SystemBus from inputremapper.configs.system_mapping import system_mapping @@ -51,12 +50,6 @@ from tests.test import ( ) -def gtk_iteration(): - """Iterate while events are pending.""" - while Gtk.events_pending(): - Gtk.main_iteration() - - check_output = subprocess.check_output os_system = os.system dbus_get = type(SystemBus()).get diff --git a/tests/unit/test_input_event.py b/tests/unit/test_input_event.py index 304b0cad..917658b9 100644 --- a/tests/unit/test_input_event.py +++ b/tests/unit/test_input_event.py @@ -91,10 +91,14 @@ class TestInputEvent(unittest.TestCase): ) self.assertEqual(e1.type_and_code, (evdev.ecodes.EV_KEY, evdev.ecodes.BTN_LEFT)) - with self.assertRaises(FrozenInstanceError): # would be TypeError on a slotted class + with self.assertRaises( + FrozenInstanceError + ): # would be TypeError on a slotted class e1.event_tuple = (1, 2, 3) - with self.assertRaises(FrozenInstanceError): # would be TypeError on a slotted class + with self.assertRaises( + FrozenInstanceError + ): # would be TypeError on a slotted class e1.type_and_code = (1, 2) with self.assertRaises(FrozenInstanceError):