fix: launch CI smoke app through macOS Launch Services
Build / macos (push) Failing after 1m3s

This commit is contained in:
2026-09-10 00:25:09 +02:00
parent 58fbe56298
commit 31584792e6
2 changed files with 33 additions and 11 deletions
+2 -1
View File
@@ -88,7 +88,8 @@ identity keeps the same sandbox container across updates.
`.gitea/workflows/build.yml` builds universal macOS artifacts for pushes and pull
requests to `main`. The repository-scoped `mac-mini-meetingnotes` runner uses the
`macos-arm64` label. Smoke tests use a separate CI bundle identifier and temporary
launch preferences. Release jobs import the original Developer ID certificate
launch preferences, an internal-volume staging directory, and Launch Services
so the GUI app runs in the logged-in Mac session. Release jobs import the original Developer ID certificate
into a temporary keychain and keep the original Sparkle signing key in Gitea
Actions secrets. They never publish from a development branch.
+31 -10
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
"""Check the CI app's unauthenticated API without changing saved preferences."""
import json
import os
import signal
import socket
import subprocess
import sys
@@ -20,11 +22,20 @@ with tempfile.TemporaryDirectory(prefix="smoke-", dir=cache) as directory:
with socket.socket() as listener:
listener.bind(("127.0.0.1", 0))
port = listener.getsockname()[1]
with tempfile.TemporaryFile() as log:
with tempfile.NamedTemporaryFile() as log:
process = subprocess.Popen(
[str(app), "-muteDeckAPIEnabled", "YES", "-muteDeckAPIPort", str(port), "-hasCompletedOnboarding", "YES", "-hasAcceptedTerms", "YES", "-SUEnableAutomaticChecks", "NO"],
["open", "-n", "-W", "-g", "-a", str(staged), "--stdout", log.name, "--stderr", log.name, "--args", "-ApplePersistenceIgnoreState", "YES", "-muteDeckAPIEnabled", "YES", "-muteDeckAPIPort", str(port), "-hasCompletedOnboarding", "YES", "-hasAcceptedTerms", "YES", "-SUEnableAutomaticChecks", "NO"],
stdout=log, stderr=subprocess.STDOUT,
)
def app_pid():
# The unique staged path identifies only this test's app instance.
listing = subprocess.check_output(["ps", "-axo", "pid=,command="], text=True)
for line in listing.splitlines():
fields = line.strip().split(None, 1)
if len(fields) == 2 and fields[1].startswith(str(app) + " "):
return int(fields[0])
return None
try:
for attempt in range(30):
if process.poll() is not None:
@@ -46,17 +57,27 @@ with tempfile.TemporaryDirectory(prefix="smoke-", dir=cache) as directory:
raise RuntimeError("Recording status allowed an unauthenticated request")
print("Local API readiness and authentication checks passed")
except Exception:
if process.poll() is None:
sample = subprocess.run(["sample", str(process.pid), "1", "1"], capture_output=True, text=True, timeout=10)
pid = app_pid()
if pid is not None:
sample = subprocess.run(["sample", str(pid), "1", "1"], capture_output=True, text=True, timeout=10)
print(sample.stdout[:14000], file=sys.stderr)
log.seek(0)
print(log.read().decode(errors="replace")[-8000:], file=sys.stderr)
raise
finally:
if process.poll() is None:
process.terminate()
pid = app_pid()
if pid is not None:
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
os.kill(pid, signal.SIGTERM)
except ProcessLookupError:
pass
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
if app_pid() == pid and pid is not None:
try:
os.kill(pid, signal.SIGKILL)
except ProcessLookupError:
pass
process.terminate()
process.wait(timeout=10)