Files
wine-access/tests/diagnostics_test.py

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")