136 lines
6.5 KiB
Python
136 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Chromium AT-SPI proof on a private X server, bus, and browser profile."""
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
|
|
BROWSER_PROOFS = {
|
|
"fragments": ("./internal/review", "^TestNativeBrowserFragmentLines$", 30, 35, "browser-fragments.html"),
|
|
"positions": ("./internal/atspi", "^TestNativeBrowserSetPositions$", 25, 30, "browser-positions.html"),
|
|
"tabs": ("./internal/app", "^TestNativeBrowserTabs$", 30, 35),
|
|
"namedlinks": ("./internal/app", "^TestNativeBrowserNamedLinks$", 25, 30, "browser-named-links.html"),
|
|
"linkedheadings": ("./internal/app", "^TestNativeLinkedHeadings$", 45, 50, "browser-linked-headings.html"),
|
|
"catalog": ("./internal/atspi", "^TestNativeBrowserCatalogCapture$", 30, 35, "browser-catalog.html"),
|
|
"lines": ("./internal/atspi", "^TestNativeBrowserLogicalLines$", 25, 30, "browser-lines.html"),
|
|
"tables": ("./internal/app", "^TestNativeBrowserTables$", 35, 40),
|
|
"dynamic": ("./internal/app", "^TestNativeBrowserDynamicNavigation$", 40, 45),
|
|
"browser": ("./internal/atspi", "^TestNativeBrowser$", 45, 50),
|
|
"live": ("./internal/atspi", "^TestNativeBrowserLiveRegion$", 30, 35),
|
|
"document": ("./internal/app", "^TestNativeBrowserDocumentCommands$", 45, 50),
|
|
"editing": ("./internal/app", "^TestNativeBrowserEditing$", 45, 50),
|
|
"selection": ("./internal/app", "^TestNativeBrowserSelection$", 35, 40),
|
|
"modes": ("./internal/app", "^TestNativeBrowserModes$", 45, 50),
|
|
"keys": ("./internal/platform/xorg", "^TestNativeBrowseKeyTransitions$", 20, 25),
|
|
}
|
|
|
|
|
|
def selected_proofs():
|
|
selected = {name.strip() for name in os.environ.get(
|
|
"CTHULHU_NATIVE_BROWSER_TESTS", ",".join(BROWSER_PROOFS)).split(",") if name.strip()}
|
|
unknown = selected - BROWSER_PROOFS.keys()
|
|
if not selected or unknown:
|
|
raise ValueError("Select browser proofs from: " + ",".join(BROWSER_PROOFS) +
|
|
("; unknown: " + ",".join(sorted(unknown)) if unknown else ""))
|
|
return selected
|
|
|
|
|
|
def run_inside():
|
|
browser = os.environ.get("CTHULHU_BROWSER") or shutil.which("brave")
|
|
if not browser:
|
|
raise RuntimeError("Set CTHULHU_BROWSER to a Chromium-family executable")
|
|
selected = selected_proofs()
|
|
for name, proof in BROWSER_PROOFS.items():
|
|
if name in selected:
|
|
print(f"Running browser proof: {name}", flush=True)
|
|
# F7 toggles a profile-wide setting. Each proof must start from
|
|
# the same browser state whether run alone or after editing.
|
|
run_browser_proof(browser, proof)
|
|
|
|
|
|
def run_browser_proof(browser, proof):
|
|
with tempfile.TemporaryDirectory(prefix="cthulhu-browser-profile-") as profile:
|
|
with tempfile.TemporaryFile() as log:
|
|
process = subprocess.Popen([
|
|
browser, "--user-data-dir=" + profile, "--no-first-run",
|
|
"--no-default-browser-check", "--disable-background-networking",
|
|
"--disable-component-update", "--disable-sync", "--password-store=basic",
|
|
"--force-renderer-accessibility", "--ozone-platform=x11",
|
|
"--new-window", Path(__file__).with_name(proof[4] if len(proof) > 4 else "browser.html").resolve().as_uri(),
|
|
], stdout=log, stderr=log)
|
|
try:
|
|
deadline = time.monotonic() + 20
|
|
while time.monotonic() < deadline:
|
|
if process.poll() is not None:
|
|
raise RuntimeError("Browser exited before the probe")
|
|
window_name = "Cthulhu Linked Headings Probe" if proof[1] == "^TestNativeLinkedHeadings$" else "Cthulhu Browser Probe"
|
|
result = subprocess.run(
|
|
["xdotool", "search", "--onlyvisible", "--name", window_name],
|
|
capture_output=True, text=True)
|
|
if result.stdout.strip():
|
|
window = result.stdout.splitlines()[0]
|
|
subprocess.run(["xdotool", "windowfocus", window], check=True)
|
|
break
|
|
time.sleep(0.1)
|
|
else:
|
|
raise RuntimeError("Browser fixture window missing")
|
|
environment = dict(os.environ, CTHULHU_NATIVE_BROWSER="1")
|
|
if proof[1] == "^TestNativeLinkedHeadings$":
|
|
environment["CTHULHU_NATIVE_LINKED_HEADINGS"] = "1"
|
|
environment.setdefault("GOCACHE", "/tmp/cthulhu-cache")
|
|
package, expression, testTimeout, runTimeout = proof[:4]
|
|
subprocess.run(["go", "test", package, "-run", expression,
|
|
"-count=1", "-v", f"-timeout={testTimeout}s"],
|
|
env=environment, check=True, timeout=runTimeout,
|
|
cwd=Path(__file__).resolve().parents[1])
|
|
finally:
|
|
process.terminate()
|
|
try:
|
|
process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
process.kill()
|
|
process.wait(timeout=5)
|
|
|
|
|
|
def main():
|
|
selected = selected_proofs()
|
|
if len(sys.argv) == 2 and sys.argv[1] == "--inside":
|
|
run_inside()
|
|
return
|
|
with tempfile.TemporaryDirectory(prefix="cthulhu-browser-"):
|
|
read_fd, write_fd = os.pipe()
|
|
server = subprocess.Popen(
|
|
["Xvfb", "-displayfd", str(write_fd), "-screen", "0", "800x600x24", "-nolisten", "tcp"],
|
|
pass_fds=(write_fd,),
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
os.close(write_fd)
|
|
try:
|
|
with os.fdopen(read_fd) as reader:
|
|
display = reader.readline().strip()
|
|
if not display:
|
|
raise RuntimeError("Xvfb failed to start")
|
|
environment = dict(os.environ, DISPLAY=":" + display, XDG_SESSION_TYPE="x11")
|
|
environment.pop("DBUS_SESSION_BUS_ADDRESS", None)
|
|
environment.pop("AT_SPI_BUS_ADDRESS", None)
|
|
environment.pop("WAYLAND_DISPLAY", None)
|
|
subprocess.run(
|
|
["dbus-run-session", "--", sys.executable, str(Path(__file__).resolve()), "--inside"],
|
|
env=environment,
|
|
check=True,
|
|
# Budget each isolated browser startup, bounded Go run and
|
|
# cleanup instead of imposing one 120-second suite limit.
|
|
timeout=15 + sum(30 + BROWSER_PROOFS[name][3] for name in selected),
|
|
)
|
|
finally:
|
|
server.terminate()
|
|
server.wait(timeout=3)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|