test: parallel-safe suite (xdist), shared globals session, KICK_DUMP_DIR
Make ./test.sh -n4 work (~47s vs ~156s serial): - kick.c: KICK_DUMP_DIR env override for frame-dump .ppm paths (default /tmp); per-boot dump_seq counters no longer collide across concurrent compositors. - tests: every spawn gets a unique log file and a per-session dump directory owned by the Session (close removes the tree instead of globbing /tmp, which raced parallel workers' live dumps). - 14 protocol-globals tests share one module-scoped keeper session via short-lived --globals-out clients instead of 14 boots. - Two presence-waits converted from fixed sleeps to event waits. - Harden the fullscreen round-trip: wait for seat keyboard focus ([diag] kbd-enter, new debug line in window_enter) before tapping F -- map alone does not guarantee enter under load and the tap was silently dropped. Serial remains the deterministic gate (documented in test.sh and README): a few pre-existing timing-sensitive tests can still flake under parallel load; they pass alone and serially.
This commit is contained in:
parent
a593d83b37
commit
7e2bb18a85
4 changed files with 107 additions and 71 deletions
|
|
@ -347,6 +347,13 @@ Known-flaky tests are marked `@pytest.mark.flaky`. `pytest.ini` defaults to
|
||||||
`-m "not flaky"`, so they are excluded from every plain run (`./test.sh` or
|
`-m "not flaky"`, so they are excluded from every plain run (`./test.sh` or
|
||||||
direct `pytest`). Run them on demand with `./test.sh -m flaky`.
|
direct `pytest`). Run them on demand with `./test.sh -m flaky`.
|
||||||
|
|
||||||
|
Parallel runs: `./test.sh -n4` splits the suite across workers
|
||||||
|
(pytest-xdist), cutting wall time ~3x (156s to ~47s here). Every spawn
|
||||||
|
uses unique log paths and per-session dump directories (`KICK_DUMP_DIR`),
|
||||||
|
and sockets are pid-unique, so workers stay isolated. The serial run
|
||||||
|
remains the deterministic gate: a few timing-sensitive tests can flake
|
||||||
|
under parallel load (they pass alone and serially).
|
||||||
|
|
||||||
## Cursor shapes (`cursor-shape-v1`)
|
## Cursor shapes (`cursor-shape-v1`)
|
||||||
|
|
||||||
Kick advertises the `cursor-shape-v1` protocol (as `wp_cursor_shape_manager_v1`,
|
Kick advertises the `cursor-shape-v1` protocol (as `wp_cursor_shape_manager_v1`,
|
||||||
|
|
|
||||||
40
kick.c
40
kick.c
|
|
@ -894,6 +894,14 @@ static void bridge_attach_seat(void);
|
||||||
static volatile sig_atomic_t dump_scene_req, dump_host_req;
|
static volatile sig_atomic_t dump_scene_req, dump_host_req;
|
||||||
static int dump_seq;
|
static int dump_seq;
|
||||||
static bool client_dumped = false;
|
static bool client_dumped = false;
|
||||||
|
/* Directory for dump .ppm files (KICK_DUMP_DIR, default /tmp): parallel
|
||||||
|
test workers point each compositor at its own directory so per-boot
|
||||||
|
dump_seq counters cannot collide on filenames. */
|
||||||
|
static char dump_dir[512] = "/tmp";
|
||||||
|
|
||||||
|
static void dump_path(char *buf, size_t sz, const char *kind, int seq) {
|
||||||
|
snprintf(buf, sz, "%s/%s_%d.ppm", dump_dir, kind, seq);
|
||||||
|
}
|
||||||
|
|
||||||
/* Set by sigchld_handler whenever a child exits; the main loop clears it and
|
/* Set by sigchld_handler whenever a child exits; the main loop clears it and
|
||||||
runs sweep_dead_startup_groups() to drop black tabs whose command exited
|
runs sweep_dead_startup_groups() to drop black tabs whose command exited
|
||||||
|
|
@ -3000,6 +3008,12 @@ static void window_enter(struct kick_window *w) {
|
||||||
if (!kbd) kbd = &server.keyboard;
|
if (!kbd) kbd = &server.keyboard;
|
||||||
wlr_seat_keyboard_notify_enter(server.seat, surface,
|
wlr_seat_keyboard_notify_enter(server.seat, surface,
|
||||||
kbd->keycodes, kbd->num_keycodes, &kbd->modifiers);
|
kbd->keycodes, kbd->num_keycodes, &kbd->modifiers);
|
||||||
|
/* Seat keyboard focus is what routes keys to the client; map alone
|
||||||
|
does not guarantee it has arrived (enter can lag map under
|
||||||
|
load), so tests tapping keys wait on this line, not the map. */
|
||||||
|
if (kick_log_level() >= KICK_LOG_DEBUG)
|
||||||
|
kick_log(KICK_LOG_DEBUG, "[diag] kbd-enter app=%d\n",
|
||||||
|
tab_index(tab));
|
||||||
}
|
}
|
||||||
show_window(w->tree ? w : NULL);
|
show_window(w->tree ? w : NULL);
|
||||||
kick_request_redraw();
|
kick_request_redraw();
|
||||||
|
|
@ -3500,9 +3514,8 @@ static void handle_toplevel_commit(struct wl_listener *listener, void *data) {
|
||||||
"nonblack=%lu/%lu mean=%lu\n", fmt, nonblack,
|
"nonblack=%lu/%lu mean=%lu\n", fmt, nonblack,
|
||||||
(unsigned long)bw * bh,
|
(unsigned long)bw * bh,
|
||||||
lum / ((unsigned long)bw * bh + 1));
|
lum / ((unsigned long)bw * bh + 1));
|
||||||
char cpath[64];
|
char cpath[512];
|
||||||
snprintf(cpath, sizeof(cpath), "/tmp/client_%d.ppm",
|
dump_path(cpath, sizeof(cpath), "client", dump_seq);
|
||||||
dump_seq);
|
|
||||||
FILE *cf = fopen(cpath, "wb");
|
FILE *cf = fopen(cpath, "wb");
|
||||||
if (cf) {
|
if (cf) {
|
||||||
fprintf(cf, "P6\n%d %d\n255\n", bw, bh);
|
fprintf(cf, "P6\n%d %d\n255\n", bw, bh);
|
||||||
|
|
@ -8081,8 +8094,8 @@ static void dump_scene_buffer(struct wlr_buffer *buf) {
|
||||||
kick_log(KICK_LOG_INFO, "[dump] scene %dx%d fmt=0x%x "
|
kick_log(KICK_LOG_INFO, "[dump] scene %dx%d fmt=0x%x "
|
||||||
"nonblack=%lu/%lu mean=%lu\n", w, h, format, nonblack,
|
"nonblack=%lu/%lu mean=%lu\n", w, h, format, nonblack,
|
||||||
(unsigned long)w * h, lum / ((unsigned long)w * h + 1));
|
(unsigned long)w * h, lum / ((unsigned long)w * h + 1));
|
||||||
char path[64];
|
char path[512];
|
||||||
snprintf(path, sizeof(path), "/tmp/scene_%d.ppm", dump_seq);
|
dump_path(path, sizeof(path), "scene", dump_seq);
|
||||||
FILE *f = fopen(path, "wb");
|
FILE *f = fopen(path, "wb");
|
||||||
if (f) {
|
if (f) {
|
||||||
int w = buf->width, h = buf->height;
|
int w = buf->width, h = buf->height;
|
||||||
|
|
@ -8124,8 +8137,8 @@ static void dump_host_back(void) {
|
||||||
kick_log(KICK_LOG_INFO, "[dump] host %dx%d nonblack=%lu/%lu mean=%lu\n",
|
kick_log(KICK_LOG_INFO, "[dump] host %dx%d nonblack=%lu/%lu mean=%lu\n",
|
||||||
w, h, nonblack, (unsigned long)w * h,
|
w, h, nonblack, (unsigned long)w * h,
|
||||||
lum / ((unsigned long)w * h + 1));
|
lum / ((unsigned long)w * h + 1));
|
||||||
char path[64];
|
char path[512];
|
||||||
snprintf(path, sizeof(path), "/tmp/host_%d.ppm", dump_seq);
|
dump_path(path, sizeof(path), "host", dump_seq);
|
||||||
FILE *f = fopen(path, "wb");
|
FILE *f = fopen(path, "wb");
|
||||||
if (f) {
|
if (f) {
|
||||||
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
||||||
|
|
@ -8195,8 +8208,8 @@ static void dump_standalone_buffer(struct wlr_buffer *buf) {
|
||||||
kick_log(KICK_LOG_INFO, "[dump] host %dx%d nonblack=%lu/%lu mean=%lu\n",
|
kick_log(KICK_LOG_INFO, "[dump] host %dx%d nonblack=%lu/%lu mean=%lu\n",
|
||||||
w, h, nonblack, (unsigned long)w * h,
|
w, h, nonblack, (unsigned long)w * h,
|
||||||
lum / ((unsigned long)w * h + 1));
|
lum / ((unsigned long)w * h + 1));
|
||||||
char path[64];
|
char path[512];
|
||||||
snprintf(path, sizeof(path), "/tmp/host_%d.ppm", dump_seq);
|
dump_path(path, sizeof(path), "host", dump_seq);
|
||||||
FILE *f = fopen(path, "wb");
|
FILE *f = fopen(path, "wb");
|
||||||
if (f) {
|
if (f) {
|
||||||
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
||||||
|
|
@ -11174,6 +11187,15 @@ int main(int argc, char *argv[]) {
|
||||||
level without touching non-async-signal-safe functions. */
|
level without touching non-async-signal-safe functions. */
|
||||||
kick_log_level_init();
|
kick_log_level_init();
|
||||||
|
|
||||||
|
/* KICK_DUMP_DIR: where frame-dump .ppm files land (default /tmp).
|
||||||
|
Test workers point each compositor at its own directory so the
|
||||||
|
per-boot dump_seq counters cannot collide on filenames. */
|
||||||
|
{
|
||||||
|
const char *d = getenv("KICK_DUMP_DIR");
|
||||||
|
if (d && d[0])
|
||||||
|
snprintf(dump_dir, sizeof(dump_dir), "%s", d);
|
||||||
|
}
|
||||||
|
|
||||||
/* KICK_SYNTH_INPUT: keep real host keyboard events out of the test
|
/* KICK_SYNTH_INPUT: keep real host keyboard events out of the test
|
||||||
channel so the stdin feed is the only source of key/modifier input. */
|
channel so the stdin feed is the only source of key/modifier input. */
|
||||||
{
|
{
|
||||||
|
|
|
||||||
6
test.sh
6
test.sh
|
|
@ -1,4 +1,10 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# Test runner: ./test.sh [pytest args].
|
||||||
|
# Serial (`./test.sh`) is the deterministic gate. For iteration speed,
|
||||||
|
# `./test.sh -n4` runs workers in parallel (~3x faster here); workers are
|
||||||
|
# isolated (unique logs, per-session KICK_DUMP_DIR, pid-unique sockets),
|
||||||
|
# but a few timing-sensitive tests may flake under load -- re-run serially
|
||||||
|
# to confirm.
|
||||||
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ Known-flaky tests are marked @pytest.mark.flaky; pytest.ini's default
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import glob
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
|
@ -59,6 +58,15 @@ def _fresh_log():
|
||||||
legacy default path (used when a caller passes log= explicitly)."""
|
legacy default path (used when a caller passes log= explicitly)."""
|
||||||
_log_seq[0] += 1
|
_log_seq[0] += 1
|
||||||
return "/tmp/tabtest-p%d-%d.log" % (os.getpid(), _log_seq[0])
|
return "/tmp/tabtest-p%d-%d.log" % (os.getpid(), _log_seq[0])
|
||||||
|
|
||||||
|
|
||||||
|
def _mk_dump_dir():
|
||||||
|
"""A fresh per-spawn dump directory (KICK_DUMP_DIR).
|
||||||
|
|
||||||
|
Parallel workers' compositors must not share /tmp dump filenames:
|
||||||
|
every boot restarts the dump_seq counter, so host_0.ppm etc. would
|
||||||
|
collide. The owning Session removes the tree in close()."""
|
||||||
|
return tempfile.mkdtemp(prefix="kick-dump-")
|
||||||
RED = "#ff0000"
|
RED = "#ff0000"
|
||||||
BLUE = "#0000ff"
|
BLUE = "#0000ff"
|
||||||
GREEN = "#00ff00"
|
GREEN = "#00ff00"
|
||||||
|
|
@ -191,9 +199,10 @@ def _preexec_death(expected_ppid):
|
||||||
class Session:
|
class Session:
|
||||||
"""A running kick instance plus helpers."""
|
"""A running kick instance plus helpers."""
|
||||||
|
|
||||||
def __init__(self, proc, log=None):
|
def __init__(self, proc, log=None, dump_dir=None):
|
||||||
self.proc = proc
|
self.proc = proc
|
||||||
self._log = log or _fresh_log()
|
self._log = log or _fresh_log()
|
||||||
|
self._dump_dir = dump_dir
|
||||||
self._socket = None
|
self._socket = None
|
||||||
|
|
||||||
# -- log watching ----------------------------------------------------
|
# -- log watching ----------------------------------------------------
|
||||||
|
|
@ -560,8 +569,12 @@ class Session:
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
self.proc.kill()
|
self.proc.kill()
|
||||||
self.proc.wait()
|
self.proc.wait()
|
||||||
for ppm in glob.glob("/tmp/host_*.ppm") + glob.glob("/tmp/scene_*.ppm"):
|
# Each session owns its dump directory (see _mk_dump_dir): remove
|
||||||
os.unlink(ppm)
|
# the whole tree instead of globbing /tmp, which would race
|
||||||
|
# parallel workers' live dumps.
|
||||||
|
if self._dump_dir:
|
||||||
|
shutil.rmtree(self._dump_dir, ignore_errors=True)
|
||||||
|
self._dump_dir = None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
|
@ -572,15 +585,17 @@ def session():
|
||||||
if not os.access(path, os.X_OK):
|
if not os.access(path, os.X_OK):
|
||||||
pytest.skip(f"build first: {path}")
|
pytest.skip(f"build first: {path}")
|
||||||
log = _fresh_log()
|
log = _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
|
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1", "stdbuf", "-oL",
|
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1",
|
||||||
|
"KICK_DUMP_DIR=" + dump_dir, "stdbuf", "-oL",
|
||||||
BIN, f"{WL} '{RED}'", f"{WL} '{BLUE}'"],
|
BIN, f"{WL} '{RED}'", f"{WL} '{BLUE}'"],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()),
|
preexec_fn=_preexec_death(os.getpid()),
|
||||||
)
|
)
|
||||||
s = Session(proc, log)
|
s = Session(proc, log, dump_dir)
|
||||||
try:
|
try:
|
||||||
yield s
|
yield s
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -600,6 +615,7 @@ def standalone_session():
|
||||||
if not os.access(path, os.X_OK):
|
if not os.access(path, os.X_OK):
|
||||||
pytest.skip(f"build first: {path}")
|
pytest.skip(f"build first: {path}")
|
||||||
log = _fresh_log()
|
log = _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
|
|
||||||
# Clear any leftover XDG/compositor interaction: standalone must not
|
# Clear any leftover XDG/compositor interaction: standalone must not
|
||||||
# connect to the host compositor (it would pick the wayland backend and
|
# connect to the host compositor (it would pick the wayland backend and
|
||||||
|
|
@ -622,13 +638,14 @@ def standalone_session():
|
||||||
"WLR_BACKENDS=headless",
|
"WLR_BACKENDS=headless",
|
||||||
"KICK_FONT=" + (env.get("KICK_FONT") or _pick_font() or ""),
|
"KICK_FONT=" + (env.get("KICK_FONT") or _pick_font() or ""),
|
||||||
"XCURSOR_THEME=Adwaita",
|
"XCURSOR_THEME=Adwaita",
|
||||||
|
"KICK_DUMP_DIR=" + dump_dir,
|
||||||
"stdbuf", "-oL",
|
"stdbuf", "-oL",
|
||||||
BIN, f"{WL} '{RED}'", f"{WL} '{BLUE}'"],
|
BIN, f"{WL} '{RED}'", f"{WL} '{BLUE}'"],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()),
|
preexec_fn=_preexec_death(os.getpid()),
|
||||||
)
|
)
|
||||||
s = Session(proc, log)
|
s = Session(proc, log, dump_dir)
|
||||||
try:
|
try:
|
||||||
yield s
|
yield s
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -1116,6 +1133,7 @@ def _spawn_standalone(extra_env, *apps, log=None):
|
||||||
interleaving their logs; the default is a fresh per-spawn file (see
|
interleaving their logs; the default is a fresh per-spawn file (see
|
||||||
_fresh_log), so parallel workers never share a log."""
|
_fresh_log), so parallel workers never share a log."""
|
||||||
log = log or _fresh_log()
|
log = log or _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env["KICK_LOG"] = "2"
|
env["KICK_LOG"] = "2"
|
||||||
env["KICK_MODE"] = "standalone"
|
env["KICK_MODE"] = "standalone"
|
||||||
|
|
@ -1124,6 +1142,7 @@ def _spawn_standalone(extra_env, *apps, log=None):
|
||||||
env.pop("XCURSOR_THEME", None)
|
env.pop("XCURSOR_THEME", None)
|
||||||
env.pop("XCURSOR_PATH", None)
|
env.pop("XCURSOR_PATH", None)
|
||||||
env.update(extra_env or {})
|
env.update(extra_env or {})
|
||||||
|
env["KICK_DUMP_DIR"] = dump_dir
|
||||||
cmd = ["env", "-i",
|
cmd = ["env", "-i",
|
||||||
"PATH=" + env["PATH"],
|
"PATH=" + env["PATH"],
|
||||||
"HOME=" + env.get("HOME", "/"),
|
"HOME=" + env.get("HOME", "/"),
|
||||||
|
|
@ -1143,7 +1162,7 @@ def _spawn_standalone(extra_env, *apps, log=None):
|
||||||
cmd, stdin=subprocess.PIPE,
|
cmd, stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()))
|
preexec_fn=_preexec_death(os.getpid()))
|
||||||
return Session(proc, log)
|
return Session(proc, log, dump_dir)
|
||||||
|
|
||||||
|
|
||||||
def _spawn_nested_in(outer, *apps, log=None):
|
def _spawn_nested_in(outer, *apps, log=None):
|
||||||
|
|
@ -1152,9 +1171,11 @@ def _spawn_nested_in(outer, *apps, log=None):
|
||||||
backend, so data-control/primary bridge paths are testable without a
|
backend, so data-control/primary bridge paths are testable without a
|
||||||
labwc/sway host. The inner session gets its own log file."""
|
labwc/sway host. The inner session gets its own log file."""
|
||||||
log = log or _fresh_log()
|
log = log or _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env["KICK_LOG"] = "2"
|
env["KICK_LOG"] = "2"
|
||||||
env["KICK_SYNTH_INPUT"] = "1"
|
env["KICK_SYNTH_INPUT"] = "1"
|
||||||
|
env["KICK_DUMP_DIR"] = dump_dir
|
||||||
env["WAYLAND_DISPLAY"] = outer.socket
|
env["WAYLAND_DISPLAY"] = outer.socket
|
||||||
# The outer kick advertises no server decorations; take the direct
|
# The outer kick advertises no server decorations; take the direct
|
||||||
# xdg-shell path instead of libdecor (which also needs KICK_LIBDECOR
|
# xdg-shell path instead of libdecor (which also needs KICK_LIBDECOR
|
||||||
|
|
@ -1167,7 +1188,7 @@ def _spawn_nested_in(outer, *apps, log=None):
|
||||||
env=env, stdin=subprocess.PIPE,
|
env=env, stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()))
|
preexec_fn=_preexec_death(os.getpid()))
|
||||||
return Session(proc, log)
|
return Session(proc, log, dump_dir)
|
||||||
|
|
||||||
|
|
||||||
def _wait_file(path, timeout=15.0):
|
def _wait_file(path, timeout=15.0):
|
||||||
|
|
@ -2528,6 +2549,9 @@ def test_client_fullscreen_round_trip(session):
|
||||||
watch both directions of the mirror."""
|
watch both directions of the mirror."""
|
||||||
idx = session.wait_any_active()
|
idx = session.wait_any_active()
|
||||||
session.wait_surface_mapped(idx)
|
session.wait_surface_mapped(idx)
|
||||||
|
# Tapping F before seat keyboard focus reaches the surface drops the
|
||||||
|
# key silently; map alone does not guarantee enter under load.
|
||||||
|
session.wait_for(r"\[diag\] kbd-enter app=%d\b" % idx)
|
||||||
session.tap(F)
|
session.tap(F)
|
||||||
session.wait_for("host fullscreen=1")
|
session.wait_for("host fullscreen=1")
|
||||||
session.wait_for("clients fullscreen=1")
|
session.wait_for("clients fullscreen=1")
|
||||||
|
|
@ -2773,14 +2797,16 @@ def _nested_session(*tabs, extra_args=()):
|
||||||
if not os.access(path, os.X_OK):
|
if not os.access(path, os.X_OK):
|
||||||
pytest.skip(f"build first: {path}")
|
pytest.skip(f"build first: {path}")
|
||||||
log = _fresh_log()
|
log = _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1", "stdbuf", "-oL",
|
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1",
|
||||||
|
"KICK_DUMP_DIR=" + dump_dir, "stdbuf", "-oL",
|
||||||
BIN, *extra_args] + list(tabs),
|
BIN, *extra_args] + list(tabs),
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()),
|
preexec_fn=_preexec_death(os.getpid()),
|
||||||
)
|
)
|
||||||
return Session(proc, log)
|
return Session(proc, log, dump_dir)
|
||||||
|
|
||||||
|
|
||||||
def test_alt_shift_t_becomes_active(session):
|
def test_alt_shift_t_becomes_active(session):
|
||||||
|
|
@ -3485,17 +3511,19 @@ def test_no_args_opens_terminal():
|
||||||
if not os.access(path, os.X_OK):
|
if not os.access(path, os.X_OK):
|
||||||
pytest.skip(f"build first: {path}")
|
pytest.skip(f"build first: {path}")
|
||||||
log = _fresh_log()
|
log = _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
|
|
||||||
# The one tab is the terminal named by TERMINAL (a wltest, so no real
|
# The one tab is the terminal named by TERMINAL (a wltest, so no real
|
||||||
# terminal emulator has to be installed on the test host).
|
# terminal emulator has to be installed on the test host).
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1",
|
["env", "KICK_LOG=2", "KICK_SYNTH_INPUT=1",
|
||||||
f"TERMINAL={WL} '{RED}'", "stdbuf", "-oL", BIN],
|
f"TERMINAL={WL} '{RED}'", "KICK_DUMP_DIR=" + dump_dir,
|
||||||
|
"stdbuf", "-oL", BIN],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()),
|
preexec_fn=_preexec_death(os.getpid()),
|
||||||
)
|
)
|
||||||
s = Session(proc, log)
|
s = Session(proc, log, dump_dir)
|
||||||
try:
|
try:
|
||||||
s.wait_for(r"\[diag\] map app=0")
|
s.wait_for(r"\[diag\] map app=0")
|
||||||
finally:
|
finally:
|
||||||
|
|
@ -3664,6 +3692,7 @@ def _spawn_kick_standalone(commands):
|
||||||
"""
|
"""
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
log = _fresh_log()
|
log = _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["env", "-i",
|
["env", "-i",
|
||||||
"PATH=" + env["PATH"],
|
"PATH=" + env["PATH"],
|
||||||
|
|
@ -3676,13 +3705,14 @@ def _spawn_kick_standalone(commands):
|
||||||
"WLR_BACKENDS=headless",
|
"WLR_BACKENDS=headless",
|
||||||
"KICK_FONT=" + (env.get("KICK_FONT") or _pick_font() or ""),
|
"KICK_FONT=" + (env.get("KICK_FONT") or _pick_font() or ""),
|
||||||
"XCURSOR_THEME=Adwaita",
|
"XCURSOR_THEME=Adwaita",
|
||||||
|
"KICK_DUMP_DIR=" + dump_dir,
|
||||||
"stdbuf", "-oL",
|
"stdbuf", "-oL",
|
||||||
BIN] + list(commands),
|
BIN] + list(commands),
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()),
|
preexec_fn=_preexec_death(os.getpid()),
|
||||||
)
|
)
|
||||||
return Session(proc, log)
|
return Session(proc, log, dump_dir)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
|
@ -3997,17 +4027,19 @@ def test_mode_autodetect_stays_nested_without_console():
|
||||||
terminal-emulator launches keep the old default."""
|
terminal-emulator launches keep the old default."""
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
log = _fresh_log()
|
log = _fresh_log()
|
||||||
|
dump_dir = _mk_dump_dir()
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["env", "-i",
|
["env", "-i",
|
||||||
"PATH=" + env["PATH"],
|
"PATH=" + env["PATH"],
|
||||||
"HOME=" + env.get("HOME", "/"),
|
"HOME=" + env.get("HOME", "/"),
|
||||||
"XDG_RUNTIME_DIR=" + env.get("XDG_RUNTIME_DIR", "/tmp"),
|
"XDG_RUNTIME_DIR=" + env.get("XDG_RUNTIME_DIR", "/tmp"),
|
||||||
"KICK_LOG=1",
|
"KICK_LOG=1",
|
||||||
|
"KICK_DUMP_DIR=" + dump_dir,
|
||||||
"stdbuf", "-oL", BIN],
|
"stdbuf", "-oL", BIN],
|
||||||
stdin=subprocess.PIPE, stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
stdin=subprocess.PIPE, stdout=open(log, "ab"), stderr=subprocess.STDOUT,
|
||||||
preexec_fn=_preexec_death(os.getpid()),
|
preexec_fn=_preexec_death(os.getpid()),
|
||||||
)
|
)
|
||||||
s = Session(proc, log)
|
s = Session(proc, log, dump_dir)
|
||||||
try:
|
try:
|
||||||
s.wait_for(r"\[mode\] nested \(default\)")
|
s.wait_for(r"\[mode\] nested \(default\)")
|
||||||
assert "[mode] standalone" not in s.read_log()
|
assert "[mode] standalone" not in s.read_log()
|
||||||
|
|
@ -4743,46 +4775,26 @@ def test_standalone_headless_screencopy_frame(tmp_path):
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
def test_standalone_headless_tearing_and_fractional_globals(tmp_path):
|
def test_standalone_headless_tearing_and_fractional_globals(globals_session,
|
||||||
|
tmp_path):
|
||||||
"""The compositor advertises wp_tearing_control_manager_v1 (games request
|
"""The compositor advertises wp_tearing_control_manager_v1 (games request
|
||||||
unthrottled presentation) and wp_fractional_scale_manager_v1 (clients
|
unthrottled presentation) and wp_fractional_scale_manager_v1 (clients
|
||||||
learn the output scale). Tearing drives the commit's async-flip flag
|
learn the output scale). Tearing drives the commit's async-flip flag
|
||||||
from the shown surface's hint; fractional scale is notified at claim."""
|
from the shown surface's hint; fractional scale is notified at claim."""
|
||||||
out = tmp_path / "globals.txt"
|
names = _globals_of(globals_session, tmp_path)
|
||||||
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
|
for want in ("wp_tearing_control_manager_v1",
|
||||||
try:
|
"wp_fractional_scale_manager_v1"):
|
||||||
deadline = time.monotonic() + 15.0
|
assert want in names, \
|
||||||
while time.monotonic() < deadline:
|
f"missing global {want}; advertised: {sorted(names)}"
|
||||||
if out.exists():
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
assert out.exists(), "wltest --globals-out never wrote its file"
|
|
||||||
names = {line.split()[0] for line in out.read_text().splitlines()}
|
|
||||||
for want in ("wp_tearing_control_manager_v1",
|
|
||||||
"wp_fractional_scale_manager_v1"):
|
|
||||||
assert want in names, \
|
|
||||||
f"missing global {want}; advertised: {sorted(names)}"
|
|
||||||
finally:
|
|
||||||
s.close()
|
|
||||||
|
|
||||||
|
|
||||||
def test_standalone_headless_shortcuts_inhibit_globals(tmp_path):
|
def test_standalone_headless_shortcuts_inhibit_globals(globals_session,
|
||||||
|
tmp_path):
|
||||||
"""The compositor advertises zwp_keyboard_shortcuts_inhibit_manager_v1
|
"""The compositor advertises zwp_keyboard_shortcuts_inhibit_manager_v1
|
||||||
so a fullscreen game/VM can grab the compositor's shortcuts."""
|
so a fullscreen game/VM can grab the compositor's shortcuts."""
|
||||||
out = tmp_path / "globals.txt"
|
names = _globals_of(globals_session, tmp_path)
|
||||||
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
|
assert "zwp_keyboard_shortcuts_inhibit_manager_v1" in names, \
|
||||||
try:
|
f"missing inhibit manager; advertised: {sorted(names)}"
|
||||||
deadline = time.monotonic() + 15.0
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
if out.exists():
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
assert out.exists(), "wltest --globals-out never wrote its file"
|
|
||||||
names = {line.split()[0] for line in out.read_text().splitlines()}
|
|
||||||
assert "zwp_keyboard_shortcuts_inhibit_manager_v1" in names, \
|
|
||||||
f"missing inhibit manager; advertised: {sorted(names)}"
|
|
||||||
finally:
|
|
||||||
s.close()
|
|
||||||
|
|
||||||
|
|
||||||
def test_standalone_headless_shortcuts_inhibit_active(tmp_path):
|
def test_standalone_headless_shortcuts_inhibit_active(tmp_path):
|
||||||
|
|
@ -4812,23 +4824,12 @@ def test_standalone_headless_shortcuts_inhibit_active(tmp_path):
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
def test_standalone_headless_output_power_globals(tmp_path):
|
def test_standalone_headless_output_power_globals(globals_session, tmp_path):
|
||||||
"""The compositor advertises zwlr_output_power_manager_v1 so a client
|
"""The compositor advertises zwlr_output_power_manager_v1 so a client
|
||||||
(wlopm) can blank/unblank the output."""
|
(wlopm) can blank/unblank the output."""
|
||||||
out = tmp_path / "globals.txt"
|
names = _globals_of(globals_session, tmp_path)
|
||||||
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
|
assert "zwlr_output_power_manager_v1" in names, \
|
||||||
try:
|
f"missing power manager; advertised: {sorted(names)}"
|
||||||
deadline = time.monotonic() + 15.0
|
|
||||||
while time.monotonic() < deadline:
|
|
||||||
if out.exists():
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
assert out.exists(), "wltest --globals-out never wrote its file"
|
|
||||||
names = {line.split()[0] for line in out.read_text().splitlines()}
|
|
||||||
assert "zwlr_output_power_manager_v1" in names, \
|
|
||||||
f"missing power manager; advertised: {sorted(names)}"
|
|
||||||
finally:
|
|
||||||
s.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _oneshot_sc(sock, out_path, timeout=10.0):
|
def _oneshot_sc(sock, out_path, timeout=10.0):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue