-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
For now, this just runs pytest but I'll add more (e.g. PEP8 etc.) later.
- Loading branch information
1 parent
6681438
commit 65fb059
Showing
14 changed files
with
1,198 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
name: ci | ||
|
||
on: | ||
push: | ||
pull_request: | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-20.04 | ||
name: "python ${{ matrix.python-version }}" | ||
env: | ||
WAYLAND: 1.19.0 | ||
WAYLAND_PROTOCOLS: 1.21 | ||
WLROOTS: 0.14.0 | ||
SEATD: 0.5.0 | ||
LIBDRM: 2.4.105 | ||
strategy: | ||
matrix: | ||
# if you change one of these, be sure to update | ||
# /tox.ini:[gh-actions] as well | ||
# python-version: [pypy-3.7, 3.7, 3.8, 3.9] | ||
python-version: [3.9] | ||
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: | | ||
sudo apt update | ||
sudo apt install --no-install-recommends \ | ||
libdbus-1-dev libgirepository1.0-dev gir1.2-gtk-3.0 gir1.2-notify-0.7 gir1.2-gudev-1.0 graphviz \ | ||
imagemagick libpulse-dev lm-sensors git xserver-xephyr xterm xvfb ninja-build libegl1-mesa-dev \ | ||
libgles2-mesa-dev libgbm-dev libinput-dev libxkbcommon-dev libpixman-1-dev libpciaccess-dev \ | ||
dbus-x11 libnotify-bin | ||
sudo pip -q install tox tox-gh-actions meson PyGObject | ||
- name: Build wayland | ||
run: | | ||
wget -q --no-check-certificate https://wayland.freedesktop.org/releases/wayland-$WAYLAND.tar.xz | ||
tar -xJf wayland-$WAYLAND.tar.xz | ||
cd wayland-$WAYLAND | ||
meson build -Ddocumentation=false --prefix=/usr | ||
ninja -C build | ||
sudo ninja -C build install | ||
- name: Build wayland-protocols | ||
run: | | ||
wget -q --no-check-certificate https://wayland.freedesktop.org/releases/wayland-protocols-$WAYLAND_PROTOCOLS.tar.xz | ||
tar -xJf wayland-protocols-$WAYLAND_PROTOCOLS.tar.xz | ||
cd wayland-protocols-$WAYLAND_PROTOCOLS | ||
meson build -Dtests=false --prefix=/usr | ||
ninja -C build | ||
sudo ninja -C build install | ||
- name: Build seatd | ||
run: | | ||
wget -q --no-check-certificate https://git.sr.ht/~kennylevinsen/seatd/archive/$SEATD.tar.gz | ||
tar -xzf $SEATD.tar.gz | ||
cd seatd-$SEATD | ||
meson build --prefix=/usr | ||
ninja -C build | ||
sudo ninja -C build install | ||
- name: Build libdrm | ||
run: | | ||
wget -q --no-check-certificate https://gitlab.freedesktop.org/mesa/drm/-/archive/libdrm-$LIBDRM/drm-libdrm-$LIBDRM.tar.gz | ||
tar -xzf drm-libdrm-$LIBDRM.tar.gz | ||
cd drm-libdrm-$LIBDRM | ||
meson build --prefix=/usr | ||
ninja -C build | ||
sudo ninja -C build install | ||
- name: Build wlroots | ||
run: | | ||
wget -q --no-check-certificate https://github.com/swaywm/wlroots/archive/$WLROOTS.tar.gz | ||
tar -xzf $WLROOTS.tar.gz | ||
cd wlroots-$WLROOTS | ||
meson build -Dexamples=false --prefix=/usr | ||
ninja -C build | ||
sudo ninja -C build install | ||
# - name: Build qtile | ||
# run: | | ||
# pip install wheel | ||
# pip install 'xcffib>=0.10.1' | ||
# pip install --no-cache-dir cairocffi | ||
# git clone git://github.com/qtile/qtile.git | ||
# cd qtile | ||
# pip install . | ||
- name: run tests | ||
run: | | ||
# [ "$(grep -c -P '\t' CHANGELOG)" = "0" ] | ||
tox -e py39 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,6 +112,7 @@ venv/ | |
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
.vscode/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
3 September 2021: | ||
- First version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import contextlib | ||
import os | ||
import textwrap | ||
|
||
from libqtile.backend.wayland.core import Core | ||
from test.helpers import Backend | ||
|
||
wlr_env = { | ||
"WLR_BACKENDS": "headless", | ||
"WLR_LIBINPUT_NO_DEVICES": "1", | ||
"WLR_RENDERER_ALLOW_SOFTWARE": "1", | ||
"WLR_RENDERER": "pixman", | ||
"XDG_RUNTIME_DIR": "/tmp", | ||
} | ||
|
||
|
||
@contextlib.contextmanager | ||
def wayland_environment(outputs): | ||
"""This backend just needs some environmental variables set""" | ||
env = wlr_env.copy() | ||
env["WLR_HEADLESS_OUTPUTS"] = str(outputs) | ||
yield env | ||
|
||
|
||
class WaylandBackend(Backend): | ||
name = "wayland" | ||
|
||
def __init__(self, env, args=()): | ||
self.env = env | ||
self.args = args | ||
self.core = Core | ||
self.manager = None | ||
|
||
def create(self): | ||
"""This is used to instantiate the Core""" | ||
os.environ.update(self.env) | ||
return self.core(*self.args) | ||
|
||
def configure(self, manager): | ||
"""This backend needs to get WAYLAND_DISPLAY variable.""" | ||
success, display = manager.c.eval("self.core.display_name") | ||
assert success | ||
self.env["WAYLAND_DISPLAY"] = display | ||
|
||
def fake_click(self, x, y): | ||
"""Click at the specified coordinates""" | ||
# Currently only restacks windows, and does not trigger bindings | ||
self.manager.c.eval(textwrap.dedent(f""" | ||
self.core.warp_pointer({x}, {y}) | ||
self.core._focus_by_click() | ||
""")) | ||
|
||
def get_all_windows(self): | ||
"""Get a list of all windows in ascending order of Z position""" | ||
success, result = self.manager.c.eval(textwrap.dedent(""" | ||
[win.wid for win in self.core.mapped_windows] | ||
""")) | ||
assert success | ||
return eval(result) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import contextlib | ||
import os | ||
import subprocess | ||
|
||
import pytest | ||
import xcffib | ||
import xcffib.testing | ||
import xcffib.xproto | ||
import xcffib.xtest | ||
|
||
from libqtile.backend.x11.core import Core | ||
from libqtile.backend.x11.xcbq import Connection | ||
from test.helpers import ( | ||
HEIGHT, | ||
SECOND_HEIGHT, | ||
SECOND_WIDTH, | ||
WIDTH, | ||
Backend, | ||
BareConfig, | ||
Retry, | ||
TestManager, | ||
) | ||
|
||
|
||
@Retry(ignore_exceptions=(xcffib.ConnectionException,), return_on_fail=True) | ||
def can_connect_x11(disp=':0', *, ok=None): | ||
if ok is not None and not ok(): | ||
raise AssertionError() | ||
|
||
conn = xcffib.connect(display=disp) | ||
conn.disconnect() | ||
return True | ||
|
||
|
||
@contextlib.contextmanager | ||
def xvfb(): | ||
with xcffib.testing.XvfbTest(): | ||
display = os.environ["DISPLAY"] | ||
if not can_connect_x11(display): | ||
raise OSError("Xvfb did not come up") | ||
|
||
yield | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def display(): # noqa: F841 | ||
with xvfb(): | ||
yield os.environ["DISPLAY"] | ||
|
||
|
||
class Xephyr: | ||
"""Spawn Xephyr instance | ||
Set-up a Xephyr instance with the given parameters. The Xephyr instance | ||
must be started, and then stopped. | ||
""" | ||
def __init__(self, | ||
outputs, | ||
xoffset=None): | ||
|
||
self.outputs = outputs | ||
if xoffset is None: | ||
self.xoffset = WIDTH | ||
else: | ||
self.xoffset = xoffset | ||
|
||
self.proc = None # Handle to Xephyr instance, subprocess.Popen object | ||
self.display = None | ||
self.display_file = None | ||
|
||
def __enter__(self): | ||
try: | ||
self.start_xephyr() | ||
except: # noqa: E722 | ||
self.stop_xephyr() | ||
raise | ||
|
||
return self | ||
|
||
def __exit__(self, _exc_type, _exc_val, _exc_tb): | ||
self.stop_xephyr() | ||
|
||
def start_xephyr(self): | ||
"""Start Xephyr instance | ||
Starts the Xephyr instance and sets the `self.display` to the display | ||
which is used to setup the instance. | ||
""" | ||
# get a new display | ||
display, self.display_file = xcffib.testing.find_display() | ||
self.display = ":{}".format(display) | ||
|
||
# build up arguments | ||
args = [ | ||
"Xephyr", | ||
"-name", | ||
"qtile_test", | ||
self.display, | ||
"-ac", | ||
"-screen", | ||
"{}x{}".format(WIDTH, HEIGHT), | ||
] | ||
if self.outputs == 2: | ||
args.extend(["-origin", "%s,0" % self.xoffset, "-screen", | ||
"%sx%s" % (SECOND_WIDTH, SECOND_HEIGHT)]) | ||
args.extend(["+xinerama"]) | ||
|
||
self.proc = subprocess.Popen(args) | ||
|
||
if can_connect_x11(self.display, ok=lambda: self.proc.poll() is None): | ||
return | ||
|
||
# we weren't able to get a display up | ||
if self.proc.poll() is None: | ||
raise AssertionError("Unable to connect to running Xephyr") | ||
else: | ||
raise AssertionError( | ||
"Unable to start Xephyr, quit with return code " | ||
f"{self.proc.returncode}" | ||
) | ||
|
||
def stop_xephyr(self): | ||
"""Stop the Xephyr instance""" | ||
# Xephyr must be started first | ||
if self.proc is None: | ||
return | ||
|
||
# Kill xephyr only if it is running | ||
if self.proc.poll() is None: | ||
# We should always be able to kill xephyr nicely | ||
self.proc.terminate() | ||
self.proc.wait() | ||
|
||
self.proc = None | ||
|
||
# clean up the lock file for the display we allocated | ||
try: | ||
self.display_file.close() | ||
os.remove(xcffib.testing.lock_path(int(self.display[1:]))) | ||
except OSError: | ||
pass | ||
|
||
|
||
@contextlib.contextmanager | ||
def x11_environment(outputs, **kwargs): | ||
"""This backend needs a Xephyr instance running""" | ||
with xvfb(): | ||
with Xephyr(outputs, **kwargs) as x: | ||
yield x | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def xmanager(request, xephyr): | ||
""" | ||
This replicates the `manager` fixture except that the x11 backend is hard-coded. We | ||
cannot simply parametrize the `backend_name` fixture module-wide because it gets | ||
parametrized by `pytest_generate_tests` in test/conftest.py and only one of these | ||
parametrize calls can be used. | ||
""" | ||
config = getattr(request, "param", BareConfig) | ||
backend = XBackend({"DISPLAY": xephyr.display}, args=[xephyr.display]) | ||
|
||
with TestManager(backend, request.config.getoption("--debuglog")) as manager: | ||
manager.display = xephyr.display | ||
manager.start(config) | ||
yield manager | ||
|
||
|
||
class XBackend(Backend): | ||
name = "x11" | ||
|
||
def __init__(self, env, args=()): | ||
self.env = env | ||
self.args = args | ||
self.core = Core | ||
self.manager = None | ||
|
||
def fake_click(self, x, y): | ||
"""Click at the specified coordinates""" | ||
conn = Connection(self.env["DISPLAY"]) | ||
root = conn.default_screen.root.wid | ||
xtest = conn.conn(xcffib.xtest.key) | ||
xtest.FakeInput(6, 0, xcffib.xproto.Time.CurrentTime, root, x, y, 0) | ||
xtest.FakeInput(4, 1, xcffib.xproto.Time.CurrentTime, root, 0, 0, 0) | ||
xtest.FakeInput(5, 1, xcffib.xproto.Time.CurrentTime, root, 0, 0, 0) | ||
conn.conn.flush() | ||
self.manager.c.sync() | ||
conn.finalize() | ||
|
||
def get_all_windows(self): | ||
"""Get a list of all windows in ascending order of Z position""" | ||
conn = Connection(self.env["DISPLAY"]) | ||
root = conn.default_screen.root.wid | ||
q = conn.conn.core.QueryTree(root).reply() | ||
wins = list(q.children) | ||
conn.finalize() | ||
return wins |
Oops, something went wrong.