Files
cthulhu/tests/check_speech.py
2026-10-27 00:40:49 -04:00

105 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run required speech regressions, or check each commit tip in a Git push."""
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
from native_test_session import run_private_session
requiredTests = {
"TestSpeechWorkerRadioSelectionDoesNotCancelLabel",
"TestPrivateWineReviewUpdatesPreserveQueuedTitleSpeech",
"TestPrivateWineTitleSurvivesReviewFocusBurst",
"TestAutomaticTitleSurvivesMovementRepeatsBeforeAndAfterDelivery",
"TestAutomaticTitleStillStopsForExplicitInput",
"TestTitleProtectionDoesNotChangeOrdinaryReading",
"TestRepeatTrackerRecognizesNativeSequences",
"TestTypingEchoModeCombinations",
"TestTypingEchoVoiceCombinations",
"TestTypingEchoLineBreakBeforeExistingText",
"TestTypingEchoLineBreakKeepsPriorCaretContext",
"TestEchoSpeakerSwitchesBothVoicesTransactionally",
"TestSpeechWorkerKeyEchoCompletesBeforeCaretSpeech",
"TestSpeechWorkerKeyEchoWaitStillAllowsInputInterruption",
}
def run_checks(sourceRoot: Path) -> None:
testEnv = os.environ.copy()
# Git exports repository paths to hooks. Tests run against the snapshot.
for name in tuple(testEnv):
if name.startswith("GIT_"):
del testEnv[name]
testEnv.pop("DISPLAY", None)
testEnv.pop("WAYLAND_DISPLAY", None)
testEnv["CTHULHU_WINE_TITLE_DBUS"] = "1"
command = [
"dbus-run-session", "--", "go", "test", "-json", "-race", "-count=1",
"-timeout=60s", "-run", "^Test.*(Title|Repeat|Interrupt|Echo)|^Test(SpeechWorker|Cancel|WineReview)",
"./internal/app", "./internal/input", "./internal/presentation", "./internal/speech", "./internal/wine",
]
print("Checking speech queues, key echo, Wine metadata and key interruption...", flush=True)
result = run_private_session(command, cwd=sourceRoot, env=testEnv, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False)
passedTests = set()
diagnostics = []
for line in result.stdout.splitlines():
try:
event = json.loads(line)
except ValueError:
diagnostics.append(line + "\n")
continue
if "Output" in event:
diagnostics.append(event["Output"])
if event.get("Action") == "pass" and "Test" in event:
passedTests.add(event["Test"])
missingTests = requiredTests - passedTests
if result.returncode or missingTests:
print("".join(diagnostics), end="", file=sys.stderr)
if missingTests:
print("Required tests did not pass (missing or skipped): " +
", ".join(sorted(missingTests)), file=sys.stderr)
raise RuntimeError("speech regression check failed")
print("Speech regression checks passed.", flush=True)
def check_push(repoRoot: Path) -> None:
checkedCommits = set()
for line in sys.stdin:
localRef, localCommit, remoteRef, remoteCommit = line.split()
if set(localCommit) == {"0"} or localCommit in checkedCommits:
continue
checkedCommits.add(localCommit)
print(f"Checking {localRef} ({localCommit[:12]}) before push...", flush=True)
with tempfile.TemporaryDirectory(prefix="cthulhu-push-") as directory:
sourceRoot = Path(directory)
with tempfile.TemporaryFile() as archive:
subprocess.run(["git", "archive", "--format=tar", localCommit],
cwd=repoRoot, stdout=archive, check=True)
archive.seek(0)
subprocess.run(["tar", "-xf", "-", "-C", str(sourceRoot)],
stdin=archive, check=True)
run_checks(sourceRoot)
def main() -> int:
repoRoot = Path(__file__).resolve().parents[1]
try:
if len(sys.argv) > 1 and sys.argv[1] == "--pre-push":
check_push(repoRoot)
else:
run_checks(repoRoot)
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
print(f"Speech check blocked: {error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())