diff --git a/tests/test_fuzz.py b/tests/test_fuzz.py index f20c4de..25a5a28 100644 --- a/tests/test_fuzz.py +++ b/tests/test_fuzz.py @@ -11,6 +11,7 @@ Run with: pytest -m fuzz """ import os +import re import shutil import signal import time @@ -19,7 +20,7 @@ import pytest from test_smoke import ( ALT, DOWN, UP, LEFT, RIGHT, DELETE, KEY_PRESSED, KEY_RELEASED, - BIN, LOG, Session, _nested_session, + BIN, LOG, WL, Session, _nested_session, ) # Skip the entire module if no host Wayland session. @@ -33,6 +34,7 @@ GNOME_CALC = shutil.which("gnome-calculator") EVINCE = shutil.which("evince") MPV = shutil.which("mpv") XMSG = shutil.which("xmessage") +WAYBAR = shutil.which("waybar") def _alive(s): @@ -45,7 +47,7 @@ def _alive(s): def _crash_free(s): """Assert no crash signals or assertions in the log.""" log = s.read_log() - for pat in ("Segmentation fault", "Aborted", "Backtrace"): + for pat in ("Segmentation fault", "Aborted", "Backtrace", "BACKTRACE"): assert pat not in log, f"crash indicator '{pat}' in log" # Check for wlroots assertion failures (the pattern is "Assertion `...' failed") assert "assertion" not in log.lower() or "assertion failed" not in log.lower(), \ @@ -136,6 +138,65 @@ def test_fuzz_xmessage(): _run_fuzz(s) +# ── layer-shell panel tests ────────────────────────────────────────── + +def _kill_session_group(s): + """SIGTERM the panel kick spawns for --panel (the sh -c wrapper's group), + forcing its layer surfaces down through the normal destroy path.""" + pid = 0 + for line in s.read_log().splitlines(): + m = re.search(r"\[panel\] pid=(\d+)", line) + if m: + pid = int(m.group(1)) + assert pid, "no [panel] pid= line in log" + os.killpg(pid, signal.SIGTERM) + time.sleep(1.0) + + +@pytest.mark.fuzz +def test_fuzz_waybar_panel(): + """A --panel waybar should keep kick alive, including when the panel + dies and tears its layer surfaces down.""" + if not WAYBAR: + pytest.skip("waybar not installed") + s = _nested_session(f"{WL} '#ff0000'", extra_args=("--panel", WAYBAR)) + try: + s.wait_for(r"\[diag\] map app=\d", timeout=10.0) + s.wait_for(r"\[panel\] pid=\d+", timeout=10.0) + _alive(s) + + # A few cycles (exercises matching actions with a panel attached). + for _ in range(3): + s.mods(ALT) + s.tap(DOWN) + time.sleep(0.3) + s.mods(0) + _alive(s) + + # Hold Alt to check overlay renders. + s.mods(ALT) + time.sleep(0.5) + s.mods(0) + _alive(s) + + # Kill the panel, then re-cycle: layer-surface teardown must not + # crash the compositor. + _kill_session_group(s) + s.mods(ALT) + s.tap(DOWN) + time.sleep(0.5) + s.mods(0) + _alive(s) + + # Close the tab, then check the whole session was crash-free. + s.chord(ALT, DELETE) + time.sleep(1.0) + _alive(s) + _crash_free(s) + finally: + s.close() + + # ── multi-tab tests ───────────────────────────────────────────────── @pytest.mark.fuzz