refactor: modernize Cast discovery and app activation

This commit is contained in:
2026-10-22 09:16:19 +02:00
parent f1893a5a5f
commit 419ac8d677
8 changed files with 279 additions and 67 deletions
+40 -7
View File
@@ -102,6 +102,31 @@ def check_window_layout(root, app, fixtures):
f"{report['animatedQueueTransitions']} queue animations, stable video size, responsive reversals, "
"screen-edge fitting, fullscreen fixed bounds, minimum sizes and exact normal geometry restored")
def check_startup_errors(root, app):
config = root / "invalid-startup.toml"
config.write_text("[ui]\nbuffer_info = [\n")
for name, arguments, title, detail in [
("configuration", ["--config-file", config.as_uri()],
"Could not load Terebi configuration", str(config)),
("progress", ["--no-config", "--progress-file", str(root / "missing-parent/progress.jsonl")],
"Could not initialize playback progress reporting", "progress"),
]:
report_path = root / f"startup-error-{name}.json"
diagnostics = root / f"startup-error-{name}.log"
result = subprocess.run(
["open", "-n", "-W", "--stderr", str(diagnostics), "-a", str(app), "--args",
"--check-startup-error", str(report_path), *arguments],
capture_output=True, text=True, timeout=25,
)
report = json.loads(report_path.read_text()) if report_path.exists() else {}
log = diagnostics.read_text(errors="replace") if diagnostics.exists() else ""
if (result.returncode != 0 or not report.get("passed") or title not in report.get("text", [])
or detail not in "\n".join(report.get("text", [])) or detail not in log):
raise RuntimeError(f"native {name} startup alert failed: {report}; "
f"open status {result.returncode}; {result.stdout}{result.stderr}{log}")
print("PASS native startup errors: configuration and progress failures show focused critical alerts with diagnostics")
def check_cli_instances(root, app, fixtures):
cli = root / "terebi"
@@ -349,8 +374,9 @@ def check_cli_instances(root, app, fixtures):
invoke([str(movie)])
first = wait_states(1, lambda states: all(state["media"] == movie.name and state["status"] == 3
and state["position"] > 0 and state["hasVideo"] for state in states.values()),
"cold default CLI opens one actual player window")
and state["position"] > 0 and state["hasVideo"] and state["active"]
and state["keyWindow"] and state["mainWindow"] for state in states.values()),
"cold default CLI opens and focuses one actual player window before synthetic input")
first_pid = next(iter(first))
renderer = first[first_pid]["window"]
if (not renderer.get("rendererAttached") or renderer.get("rendererHidden")
@@ -382,8 +408,10 @@ def check_cli_instances(root, app, fixtures):
if settings(first[first_pid]) != (False, False, True) or config.parent.exists():
raise RuntimeError(f"missing config must use defaults without creating a file: {first}")
invoke([])
wait_states(1, lambda states: states[first_pid]["media"] == movie.name,
"warm default CLI reuses the existing process without replacing media")
wait_states(1, lambda states: states[first_pid]["media"] == movie.name
and states[first_pid]["active"] and states[first_pid]["keyWindow"]
and states[first_pid]["mainWindow"],
"warm default CLI focuses the existing process without replacing media")
input_request(first_pid, 1, lambda state: state["status"] == 2, "default Space pauses once",
events=[{"key": " ", "code": 49}])
input_request(first_pid, 1, lambda state: state["status"] == 2, "held Space cannot oscillate",
@@ -660,8 +688,10 @@ def check_cli_instances(root, app, fixtures):
shutil.copy2(fixtures / "av.mp4", replacement)
invoke([str(replacement)])
after = wait_states(count, lambda states: sum(state["media"] == replacement.name
for state in states.values()) == 1,
"default media delivery still reuses exactly one existing process")
for state in states.values()) == 1 and all(
state["active"] and state["keyWindow"] and state["mainWindow"]
for state in states.values() if state["media"] == replacement.name),
"default document delivery focuses and reuses exactly one existing process")
changed = [pid for pid in before if before[pid]["media"] != after[pid]["media"]]
if len(changed) != 1:
raise RuntimeError(f"default media open changed multiple players: {before}; {after}")
@@ -674,12 +704,14 @@ def check_cli_instances(root, app, fixtures):
app_source, library, fixtures, dependencies_root = map(lambda path: Path(path).resolve(), sys.argv[1:])
with tempfile.TemporaryDirectory(prefix="terebi-startup-") as directory:
root = Path(directory)
app = root / "terebi.app"
app = root / "Terebi Startup Check.app"
shutil.copytree(app_source, app)
identifier = "local.personal.terebi.startup-check." + uuid.uuid4().hex
info_path = app / "Contents/Info.plist"
info = plistlib.loads(info_path.read_bytes())
info["CFBundleIdentifier"] = identifier
info["CFBundleName"] = "Terebi Startup Check"
info["CFBundleDisplayName"] = "Terebi Startup Check"
info_path.write_bytes(plistlib.dumps(info))
shutil.copy2(library, app / "Contents/Frameworks/libterebi-player.dylib")
subprocess.run([sys.executable, str(Path(__file__).resolve().parent.parent / "Scripts/bundle.py"),
@@ -702,6 +734,7 @@ with tempfile.TemporaryDirectory(prefix="terebi-startup-") as directory:
if launch.returncode != 0 or not result["passed"] or result["bundleIdentifier"] != identifier:
raise RuntimeError(f"packaged startup failed: {result}; {stdout}{stderr}")
print("PASS packaged bootstrap: registered app PID/identity, in-process player load, actual playback and Now Playing")
check_startup_errors(root, app)
check_overlay_hover(root, app, fixtures)
if os.environ.get("TEREBI_CHECK_HOVER_ONLY") == "1":
sys.exit(0)