After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.
This commit is contained in:
1 parent
c018f5fd29
commit
0e50de4c76
1047 files changed
+58364
-63747
No files matched your search
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Private X11 hardware-speech integration using PTYs and fake libusb only."""
|
||||
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import tomllib
|
||||
|
||||
from native_terminal import KeyboardFixture, wait_for
|
||||
|
||||
|
||||
def main():
|
||||
if not os.environ.get("DISPLAY") or os.environ.get("FENRIR_PRIVATE_X11") != "1":
|
||||
raise SystemExit("Use xvfb-run -a env FENRIR_PRIVATE_X11=1 python3 tests/native_hardware_speech.py")
|
||||
repository = Path(__file__).resolve().parents[1]
|
||||
fixture = str(repository / "tests/native_terminal.py")
|
||||
binary = os.environ.get("FENRIR_TEST_BINARY", str(repository / "fenrir"))
|
||||
with tempfile.TemporaryDirectory(prefix="fenrir-hardware-", dir="/tmp") as directory:
|
||||
suiteRoot = Path(directory)
|
||||
libraryRoot = suiteRoot / "usb"
|
||||
libraryRoot.mkdir()
|
||||
subprocess.run(["cc", "-shared", "-fPIC", "-o", str(libraryRoot / "libusb-1.0.so.0"),
|
||||
str(repository / "internal/speech/testdata/usb_fixture.c")], check=True)
|
||||
keyboard = KeyboardFixture()
|
||||
try:
|
||||
for driver in ("dectalk", "dectalk-usb", "litetalk", "doubletalk", "tripletalk"):
|
||||
for layout in ("desktop", "laptop"):
|
||||
root = suiteRoot / (driver + "-" + layout)
|
||||
root.mkdir()
|
||||
(root / "home").mkdir()
|
||||
(root / "keyboard").mkdir()
|
||||
layoutName = "laptop.toml" if layout == "laptop" else "custom.toml"
|
||||
(root / "keyboard" / layoutName).write_bytes((repository / "config/keyboard" / (layout + ".toml")).read_bytes())
|
||||
if layout == "laptop":
|
||||
(root / "mode").write_text("laptop")
|
||||
shell = root / "shell"
|
||||
shell.write_text(f'#!{sys.executable}\nimport runpy, sys\nsys.argv = [{fixture!r}, "--fixture-shell"]\nrunpy.run_path({fixture!r}, run_name="__main__")\n')
|
||||
shell.chmod(0o700)
|
||||
master, slave = os.openpty()
|
||||
fcntl.fcntl(master, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
settings = root / "settings.toml"
|
||||
settings.write_text('version=3\n[presentation.sound]\nenabled=false\n'
|
||||
'[presentation.speech]\n' + f'driver="{driver}"\nhardware_device="{os.ttyname(slave)}"\nrate=0\n'
|
||||
'[reading]\npunctuation_level="none"\nspellcheck=false\n'
|
||||
f'[remote]\nsocket_file="{root / "remote.sock"}"\n')
|
||||
environment = dict(os.environ, FENRIR_TEST_BINARY=binary, FENRIR_FIXTURE_DIR=str(root),
|
||||
HOME=str(root / "home"), TMPDIR=str(root), LOGNAME="Username", USER="Username",
|
||||
SPEECHD_ADDRESS="unix_socket:" + str(root / "unused-speech.sock"),
|
||||
LD_LIBRARY_PATH=str(libraryRoot), USB_FIXTURE_MODE="ok", USB_FIXTURE_LOG=str(root / "usb.log"))
|
||||
terminal = subprocess.Popen(["xterm", "-geometry", "80x25", "-e", sys.executable, fixture, "--outer", str(root)], env=environment)
|
||||
captured = bytearray()
|
||||
|
||||
def output():
|
||||
if driver == "dectalk-usb":
|
||||
log = root / "usb.log"
|
||||
return b"".join(bytes.fromhex(line.partition(":")[2]) for line in log.read_text().splitlines() if line.startswith("write-02:")) if log.exists() else b""
|
||||
while select.select([master], [], [], 0)[0]:
|
||||
captured.extend(os.read(master, 65536))
|
||||
return bytes(captured)
|
||||
|
||||
def expect(text, start=0):
|
||||
wait_for(lambda: text in output()[start:], f"{driver}/{layout}: {text!r}; errors=" + ((root / "reader-errors").read_text() if (root / "reader-errors").exists() else ""))
|
||||
|
||||
try:
|
||||
wait_for(lambda: (root / "started").exists(), "reader startup")
|
||||
state = json.loads((root / "started").read_text())
|
||||
subprocess.run(["xdotool", "windowfocus", "--sync", state["window"]], check=True)
|
||||
expect(b"prompt")
|
||||
start = len(output())
|
||||
keyboard.key("Insert+Right", "Insert+Right", "Insert+Right")
|
||||
expect(b"speech rate 0", start) # Hardware wraps after volume.
|
||||
start = len(output())
|
||||
keyboard.key("Insert+Up", "Insert+ctrl+s")
|
||||
expect(b"Settings saved", start)
|
||||
wait_for(lambda: tomllib.loads(settings.read_text())["presentation"]["speech"]["rate"] == 10, "hardware quick save")
|
||||
start = len(output())
|
||||
keyboard.key("Insert+f")
|
||||
expect(b"Fenrir Menu", start)
|
||||
terminator = b"\x0b" if driver.startswith("dectalk") else b"\r"
|
||||
for _ in range(24):
|
||||
start = len(output())
|
||||
keyboard.key("Tab")
|
||||
wait_for(lambda: terminator in output()[start:], "hardware form control")
|
||||
if b"Test synthesizer button" in output()[start:]:
|
||||
break
|
||||
else:
|
||||
raise AssertionError("Hardware test button not reachable")
|
||||
start = len(output())
|
||||
keyboard.key("Return")
|
||||
expect(b"Voice test: The quick brown fox jumps over the lazy dog.", start)
|
||||
start = len(output())
|
||||
keyboard.key("Escape")
|
||||
expect(b"Fenrir Menu closed", start)
|
||||
start = len(output())
|
||||
keyboard.key("Insert+i" if layout == "laptop" else "KP_Up")
|
||||
expect(b"prompt", start)
|
||||
assert not (root / "input").exists() or not (root / "input").read_bytes(), "reader chords leaked to the child"
|
||||
keyboard.key("Insert+q")
|
||||
wait_for(lambda: (root / "finished").exists(), "reader shutdown")
|
||||
result = json.loads((root / "finished").read_text())
|
||||
assert result == {"status": 0, "restored": True, "flagsRestored": True}, result
|
||||
if driver == "dectalk-usb":
|
||||
assert (root / "usb.log").read_text().endswith("release\nclose\nexit\n")
|
||||
print(f"PASS: {driver}/{layout}: startup, quick controls, save, owned-connection preview, review, input isolation and cleanup", flush=True)
|
||||
finally:
|
||||
terminal.terminate()
|
||||
try:
|
||||
terminal.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
terminal.kill()
|
||||
terminal.wait()
|
||||
os.close(master)
|
||||
os.close(slave)
|
||||
finally:
|
||||
keyboard.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user