refactor: modernize Cast discovery and app activation
This commit is contained in:
+40
-7
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user