114 lines
5.9 KiB
Python
114 lines
5.9 KiB
Python
"""Exercise version/startup diagnostics on a private D-Bus session and Wine prefix.
|
|
|
|
Run under xvfb-run. No requests go to the active screen reader.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from gi.repository import Gio, GLib
|
|
|
|
from wine_test_environment import add_test_options, run_wine_test, wine_test_directory
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--build-dir", type=Path, default=Path("build"))
|
|
parser.add_argument("--session", action="store_true")
|
|
add_test_options(parser)
|
|
args = parser.parse_args()
|
|
buildDir = args.build_dir.resolve()
|
|
if not args.session:
|
|
sys.exit(run_wine_test(["dbus-run-session", "--", sys.executable, __file__,
|
|
"--session", "--build-dir", str(buildDir)], buildDir, "diagnostics_test",
|
|
keep=args.keep, keepOnFailure=args.keepOnFailure))
|
|
|
|
busName = "org.stormux.Cthulhu1.Service"
|
|
objectPath = "/org/stormux/Cthulhu1/Service"
|
|
xml = '<node><interface name="' + busName + '">'
|
|
for method, signature in (("PresentAccessibleEvent", "sssssssiis"),
|
|
("SpeakText", "sb"), ("CancelSpeech", "")):
|
|
xml += '<method name="' + method + '">'
|
|
xml += ''.join('<arg type="' + kind + '" direction="in"/>' for kind in signature)
|
|
xml += '<arg type="b" direction="out"/></method>'
|
|
xml += '</interface></node>'
|
|
|
|
|
|
def record(connection, sender, path, interface, method, parameters, invocation):
|
|
invocation.return_value(GLib.Variant("(b)", (True,)))
|
|
|
|
|
|
with wine_test_directory(prefix="wine-access-diagnostics-") as temporary:
|
|
testDir = Path(temporary)
|
|
env = os.environ.copy()
|
|
env.update(WINEPREFIX=str(testDir / "prefix"), WINEDEBUG="-all",
|
|
WINEDLLOVERRIDES="mscoree,mshtml=", CTHULHU_WINE_ACCESS_MANAGED="1")
|
|
helper = None
|
|
try:
|
|
subprocess.run(["wineboot", "-u"], env=env, check=True, timeout=45)
|
|
executable = buildDir / "wine-access.exe"
|
|
version = subprocess.run([str(executable), "--version"], env=env,
|
|
check=True, capture_output=True, text=True, timeout=5)
|
|
assert version.stdout.startswith("Wine Access ") and not version.stderr
|
|
# No reader exists yet; ordinary startup must identify itself and fail.
|
|
failed = subprocess.run([str(executable), "--no-controller"], env=env,
|
|
capture_output=True, text=True, timeout=5)
|
|
assert failed.returncode != 0 and "reader service" in failed.stderr
|
|
assert "wine-access: identity " in failed.stderr and "wine-access: ready " not in failed.stderr
|
|
|
|
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
|
connection.register_object(objectPath, Gio.DBusNodeInfo.new_for_xml(xml).interfaces[0], record, None, None)
|
|
connection.call_sync("org.freedesktop.DBus", "/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus", "RequestName",
|
|
GLib.Variant("(su)", (busName, 0)), None,
|
|
Gio.DBusCallFlags.NONE, 1000, None)
|
|
for missingHooks in (False, True):
|
|
selected = executable
|
|
if missingHooks:
|
|
incompleteDir = testDir / "incomplete"
|
|
incompleteDir.mkdir()
|
|
for name in ("wine-access.exe", "wine-access.exe.so"):
|
|
shutil.copy2(buildDir / name, incompleteDir / name)
|
|
selected = incompleteDir / "wine-access.exe"
|
|
logPath = testDir / ("missing.log" if missingHooks else "healthy.log")
|
|
with logPath.open("w") as diagnostics:
|
|
helper = subprocess.Popen([str(selected), "--no-controller"], env=env,
|
|
stdout=subprocess.DEVNULL, stderr=diagnostics)
|
|
deadline = time.monotonic() + 5
|
|
while "wine-access: ready " not in logPath.read_text() and time.monotonic() < deadline:
|
|
while GLib.MainContext.default().iteration(False):
|
|
pass
|
|
assert helper.poll() is None, logPath.read_text()
|
|
time.sleep(0.01)
|
|
lines = logPath.read_text().splitlines()
|
|
identity = next(json.loads(line.removeprefix("wine-access: identity "))
|
|
for line in lines if line.startswith("wine-access: identity "))
|
|
ready = next(json.loads(line.removeprefix("wine-access: ready "))
|
|
for line in lines if line.startswith("wine-access: ready "))
|
|
assert identity["version"] in version.stdout and identity["revision"] in version.stdout
|
|
for field in ("controls_enabled", "review", "msaa_worker", "win_events", "control_poll"):
|
|
assert ready[field], (field, ready)
|
|
assert not ready["controller"]
|
|
for field in ("menu_hook", "input_hook", "host32_started"):
|
|
assert ready[field] == (not missingHooks), (field, ready)
|
|
if missingHooks:
|
|
assert "native menu hook unavailable" in logPath.read_text()
|
|
assert "32-bit menu host unavailable" in logPath.read_text()
|
|
assert "unavailable (error 0)" not in logPath.read_text(), logPath.read_text()
|
|
# A query must bypass the singleton held by this helper.
|
|
queried = subprocess.run([str(selected), "--version"], env=env,
|
|
check=True, capture_output=True, text=True, timeout=5)
|
|
assert queried.stdout == version.stdout and not queried.stderr
|
|
helper.terminate()
|
|
helper.wait(timeout=5)
|
|
helper = None
|
|
finally:
|
|
subprocess.run(["wineserver", "-k"], env=env, timeout=10, check=False)
|
|
if helper is not None:
|
|
helper.wait(timeout=10)
|
|
print("PASS: version without reader/with singleton, unavailable reader, healthy and missing-hook startup")
|