Version and better logging added.
This commit is contained in:
1 parent
b16c6968ae
commit
77db0b83ab
10 files changed
+326
-8
No files matched your search
@@ -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.
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
Reference in new issue
Block a user