Version and better logging added.

This commit is contained in:
Storm Dragon committed 2026-10-15 12:49:33 -04:00
1 parent b16c6968ae
commit 77db0b83ab
10 files changed
+326 -8

No files matched your search

+24
View File
@@ -574,3 +574,27 @@ the new focus event; the Go presenter must suppress that unchanged state
presentation. For live acceptance, change the document, close Notepad and
verify each Tab announces only the newly focused button, including reverse
navigation. Escape must return to the editor without discarding the document.
## Helper identity and startup diagnostics
```sh
python3 tests/version_metadata_test.py
xvfb-run -a python3 tests/diagnostics_test.py --build-dir build
```
The metadata check uses a temporary Git repository and a source archive to
verify clean/modified builds, version edits, commit changes and unchanged
header timestamps. The Wine check uses a private D-Bus service, display and
prefix. It queries `--version` without a reader and while the singleton is
held, verifies identity on failed reader discovery, and checks normal and
missing-hook initialization records. It never contacts the active reader.
After installing both updates and restarting Cthulhu, launch AudioQuake and
check its log for `Wine accessibility helper identity` and
`Wine accessibility helper initialized`, with the expected version/revision.
Enable debug logging to confirm the executable, loader and prefix selected.
Check the launcher list with Down/Down/Up and Tab/Shift+Tab, its map chooser,
and returning to the launcher after game exit. Recheck ordinary Wine Notepad
typing, arrow navigation, menus and shutdown, including password exclusion.
Audible speech and physical Braille require separate live checks. This change
adds diagnostics; it does not establish a fix for a remote machine's silence.
+110
View File
@@ -0,0 +1,110 @@
"""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 tempfile
import time
from gi.repository import Gio, GLib
parser = argparse.ArgumentParser()
parser.add_argument("--build-dir", type=Path, default=Path("build"))
parser.add_argument("--session", action="store_true")
args = parser.parse_args()
buildDir = args.build_dir.resolve()
if not args.session:
sys.exit(subprocess.call(["dbus-run-session", "--", sys.executable, __file__,
"--session", "--build-dir", str(buildDir)]))
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 tempfile.TemporaryDirectory(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")
+56
View File
@@ -0,0 +1,56 @@
"""Check build identity refresh and source-archive handling without running Wine."""
from pathlib import Path
import shutil
import subprocess
import tempfile
projectDir = Path(__file__).resolve().parents[1]
with tempfile.TemporaryDirectory(prefix="wine-access-version-") as temporary:
sourceDir = Path(temporary) / "source"
sourceDir.mkdir()
shutil.copytree(projectDir / "cmake", sourceDir / "cmake")
(sourceDir / "VERSION").write_text("2026.10.15\n")
outputFile = Path(temporary) / "version.h"
def generate_version():
subprocess.run(["cmake", f"-DSOURCE_DIR={sourceDir}",
f"-DOUTPUT_FILE={outputFile}", "-P",
str(sourceDir / "cmake/GenerateVersion.cmake")], check=True)
return outputFile.read_text()
def git(*arguments):
return subprocess.check_output(["git", "-C", str(sourceDir), *arguments],
text=True, stderr=subprocess.PIPE).strip()
assert 'WINE_ACCESS_REVISION "unavailable"' in generate_version()
git("init", "--quiet")
git("config", "user.name", "Fixture Author")
git("config", "user.email", "author@example.invalid")
git("add", ".")
git("-c", "commit.gpgsign=false", "commit", "--quiet", "-m", "Initial fixture")
firstRevision = git("rev-parse", "--short=12", "HEAD")
cleanHeader = generate_version()
assert firstRevision in cleanHeader and 'WINE_ACCESS_MODIFIED "false"' in cleanHeader
unchangedTime = outputFile.stat().st_mtime_ns
assert generate_version() == cleanHeader
assert outputFile.stat().st_mtime_ns == unchangedTime, "unchanged build rewrote version header"
(sourceDir / "VERSION").write_text("2026.10.16\n")
dirtyHeader = generate_version()
assert 'WINE_ACCESS_VERSION "2026.10.16"' in dirtyHeader
assert 'WINE_ACCESS_MODIFIED "true"' in dirtyHeader and ", modified)" in dirtyHeader
git("add", "VERSION")
git("-c", "commit.gpgsign=false", "commit", "--quiet", "-m", "Updated fixture")
secondRevision = git("rev-parse", "--short=12", "HEAD")
assert secondRevision != firstRevision
assert secondRevision in generate_version(), "incremental identity retained previous commit"
(sourceDir / "untracked.txt").write_text("fixture\n")
assert 'WINE_ACCESS_MODIFIED "true"' in generate_version()
# An archive nested inside another repository must not borrow its identity.
archiveDir = sourceDir / "archive"
archiveDir.mkdir()
shutil.copytree(projectDir / "cmake", archiveDir / "cmake")
(archiveDir / "VERSION").write_text("2026.10.15\n")
sourceDir = archiveDir
assert 'WINE_ACCESS_REVISION "unavailable"' in generate_version()
print("PASS: clean, modified, incremental, unchanged and archive build identities")