Dotfiles_ArchAP/tests/session_v2/xwayland_runtime.py

124 lines
6.3 KiB
Python

"""Real XWayland TEST_ONLY, with private compositor, config and children only."""
import json
import re
from unittest.mock import patch
from common import *
from lab import Lab as BaseLab, Surfaces, build_helper, until
from session_v2.errors import Failure
from session_v2.executor import Executor
from session_v2.ipc import Sway, walk, location, surface_identity
from session_v2.recovery import observe, process_start, JOURNAL, recovery_status
from session_v2.snapshot import capture
from session_v2.storage import private_dir
WEBAPP = "firefox.webapp-fcd72ebe-94c5-4209-b3f6-bbaa7784cded"
class Lab(BaseLab):
def start(self, argv):
if self.socket is None and "-c" in argv:
config = self.directory / "headless-x11.conf"
original = Path(argv[argv.index("-c") + 1]).read_text()
config.write_text(original.replace("xwayland disable", "xwayland enable"))
argv = list(argv)
argv[argv.index("-c") + 1] = str(config)
return super().start(argv)
def __enter__(self):
super().__enter__()
try:
# Read only the DISPLAY from our compositor's child environment.
# No inherited desktop socket, Xauthority, bus or profile is used.
helper, marker = self.directory / "display.py", self.directory / "display"
helper.write_text("import os\nfrom pathlib import Path\nPath(" + repr(str(marker))
+ ").write_text(os.environ.get('DISPLAY', ''))\n")
self.command("exec /usr/bin/python3 " + json.dumps(str(helper)))
until(marker.exists)
display = marker.read_text()
assert re.fullmatch(r":[0-9]+(?:\.[0-9]+)?", display), "NO_PRIVATE_X_DISPLAY"
self.env.update(DISPLAY=display, GDK_BACKEND="wayland")
return self
except BaseException:
self.__exit__(None, None, None)
raise
def windows(self):
return [n for n in walk(self.tree()) if n.get("app_id") or n.get("window")]
def x11(self, wm_class):
process = self.spawn(["/usr/bin/xterm", "-class", wm_class, "-name", "TEST_ONLY",
"-title", "TEST_ONLY", "-e", "/usr/bin/sleep", "180"])
found = until(lambda: [n for n in self.windows() if n.get("pid") == process.pid
and n.get("window_properties", {}).get("class") == wm_class])
assert len(found) == 1 and found[0]["app_id"] is None
return found[0], process
def main():
with Lab() as lab:
print("TEST_ONLY_DIRECTORY=" + str(lab.directory), flush=True)
binary = build(lab.directory)
native = Surfaces(lab, build_helper(lab.directory))
sway = Sway(lab.socket)
lab.command('workspace "TEST_ONLY_TARGET"')
chat, chat_process = lab.x11("Chatgpt")
whatsapp = native.create(WEBAPP)
time.sleep(.2)
data = capture(sway, Catalog(), None)
assert {a["application_id"] for a in data["applications"]} == {"chatgpt", "whatsapp-web"}
assert all(s["identity_requirement"] == "exact" for s in data["window_slots"])
(lab.directory / "snapshot.json").write_text(json.dumps(data, indent=2))
for node in (chat, whatsapp):
lab.command(f'[con_id={node["id"]}] move container to workspace "TEST_ONLY_SOURCE"')
lab.command('workspace "TEST_ONLY_FOREIGN"')
foreign, foreign_process = lab.x11("TEST_ONLY_Unknown")
time.sleep(.2)
tree = sway.tree()
foreign_before = observe(tree, next(n for n in walk(tree) if n["id"] == foreign["id"]), process_start)
before_ids = {n["id"] for n in sway.windows()}
commands = []
raw_command = sway.command
def command(text):
commands.append(text)
return raw_command(text)
sway.command = command
runtime = private_dir(lab.directory / "runtime")
# The production catalogue is used unchanged; all real app launches are
# forbidden. Our xterm and GTK clients expose synthetic protocol IDs only.
with patch("session_v2.executor.launch", side_effect=AssertionError("REAL_APP_LAUNCH_FORBIDDEN")):
result = Executor(sway, Catalog(), None, runtime, binary, lab.env).apply(data, True)
assert result["status"] == "success", result
assert result["final_tree_verified"] and result["final_focus_verified"]
tree = sway.tree()
assert {n["id"] for n in sway.windows()} == before_ids
assert location(tree, chat["id"])[0] == location(tree, whatsapp["id"])[0] == "TEST_ONLY_TARGET"
live_foreign = next(n for n in walk(tree) if n["id"] == foreign["id"])
assert observe(tree, live_foreign, process_start) == foreign_before
assert live_foreign["focused"]
assert chat_process.poll() is None and foreign_process.poll() is None
journal = json.loads((runtime / JOURNAL).read_text())
assert recovery_status(runtime)["status"] == "complete"
assert [e["app_id"] for e in journal["foreign"]] == ["x11-class:TEST_ONLY_Unknown"]
entry = next(e for e in journal["windows"] if e["origin"]["con_id"] == chat["id"])
assert entry["origin"]["app_id"] == entry["expected"]["app_id"] == "x11-class:Chatgpt"
assert any(c.startswith(f'[con_id={chat["id"]}] swap container with con_id ') for c in commands)
# An unknown X11 class stays protected foreign evidence and is still
# refused by capture; it never becomes an admissible catalogue target.
try:
capture(sway, Catalog(), None)
except Failure as exc:
assert exc.code == "APPLICATION_UNSUPPORTED", exc.code
else:
raise AssertionError("UNKNOWN_CLASS_WAS_SAVED")
assert surface_identity(live_foreign) == "x11-class:TEST_ONLY_Unknown"
evidence = {"result": result, "unknown_x11_preserved": True,
"unknown_x11_capture_refused": True, "focus_preserved": True,
"x11_app_id_remains_null": next(n for n in sway.windows() if n["id"] == chat["id"])["app_id"] is None,
"commands": commands}
(lab.directory / "xwayland-result.json").write_text(json.dumps(evidence, indent=2))
print("XWAYLAND_RUNTIME=PASS", flush=True)
native.close()
if __name__ == "__main__":
main()