Skip to content

Commit 32882cf

Browse files
committed
ci: probe Blacksmith stimulus audio and display (scratch, not for merge)
1 parent d0538b3 commit 32882cf

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
name: Blacksmith recording probe (scratch)
2+
on:
3+
push:
4+
branches: [scratch/verify-recording-bs-probe]
5+
permissions:
6+
contents: read
7+
jobs:
8+
real-capture:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
runner: [blacksmith-6vcpu-macos-15, blacksmith-6vcpu-macos-26]
13+
runs-on: ${{ matrix.runner }}
14+
timeout-minutes: 45
15+
env:
16+
CAP_VERIFY_CAPTURE: display
17+
CAP_VERIFY_REQUIRE_CAPTURE: "1"
18+
CAP_VERIFY_TIER: ${{ inputs.tier || 'fast' }}
19+
CAP_VERIFY_REPEAT: ${{ inputs.repeat || '5' }}
20+
CAP_VERIFY_CASE: ${{ inputs.case_filter || '' }}
21+
CAP_VERIFY_KEEP: ${{ inputs.keep_artifacts && '1' || '' }}
22+
CAP_VERIFY_SCRATCH: ${{ github.workspace }}/verify-scratch
23+
CAP_VERIFY_REPORT_DIR: ${{ github.workspace }}/verify-report
24+
CAP_VERIFY_DUMP: ${{ github.workspace }}/verify-dump
25+
# Hosted macos-26 has a virtual output device that gives
26+
# ScreenCaptureKit only silence, so system audio is not required
27+
# there; Blacksmith gets BlackHole (set up below), which carries the
28+
# stimulus's audio to ScreenCaptureKit, so system audio is required
29+
# and the system-only case runs.
30+
CAP_VERIFY_AUDIO_OUTPUT: ${{ startsWith(matrix.runner, 'blacksmith-') && 'BlackHole 2ch' || matrix.runner == 'macos-26' && 'Null Audio Device' || '' }}
31+
CAP_VERIFY_REQUIRE_SYSTEM_AUDIO: ${{ startsWith(matrix.runner, 'blacksmith-') && '1' || '' }}
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- uses: dtolnay/rust-toolchain@1.88.0
36+
37+
- uses: ./.github/actions/setup-rust-cache
38+
with:
39+
target: aarch64-apple-darwin
40+
prefix-key: v1-rust-verify-recording-${{ matrix.runner }}
41+
save-cache: "false"
42+
43+
- uses: ./.github/actions/install-desktop-deps
44+
45+
- uses: actions/setup-node@v4
46+
with:
47+
node-version: 24
48+
49+
- name: Native dependencies
50+
env:
51+
RUST_TARGET_TRIPLE: aarch64-apple-darwin
52+
run: node scripts/setup.js
53+
54+
- name: Runner devices
55+
run: |
56+
sw_vers
57+
system_profiler SPDisplaysDataType SPAudioDataType 2>/dev/null | sed -n 1,80p
58+
sysctl hw.memsize hw.ncpu
59+
60+
# Spotlight indexes the build and every recording the suite writes:
61+
# mds (priority 50) plus a dozen mdworkers averaged 1 to 1.8 of the 3
62+
# cores during the tests (ps samples, run 38736744971).
63+
- name: Disable Spotlight indexing
64+
run: |
65+
sudo mdutil -a -i off || true
66+
sudo mdutil -as || true
67+
68+
- name: Build
69+
run: |
70+
start=$(date +%s)
71+
cargo test --locked -p cap-verify --test recording --no-run
72+
echo "build seconds: $(( $(date +%s) - start ))"
73+
74+
# Blacksmith runs a logged-in GUI session with SIP disabled, but
75+
# ScreenCaptureKit is denied until TCC grants the responsible process
76+
# (Runner.Worker), so grant Screen Recording (and Microphone) to every
77+
# binary that captures plus the whole runner ancestry, then restart
78+
# tccd. It has no audio device: BlackHole provides input and output.
79+
- name: Blacksmith capture permissions and audio device
80+
if: startsWith(matrix.runner, 'blacksmith-')
81+
run: |
82+
set -euo pipefail
83+
brew install --cask blackhole-2ch
84+
brew install switchaudio-osx
85+
sudo killall coreaudiod || true
86+
for i in $(seq 1 30); do
87+
SwitchAudioSource -a -t output | grep -qx "BlackHole 2ch" && break
88+
sleep 1
89+
done
90+
SwitchAudioSource -s "BlackHole 2ch" -t output
91+
SwitchAudioSource -s "BlackHole 2ch" -t input
92+
echo "outputs:"; SwitchAudioSource -a -t output
93+
echo "default output: $(SwitchAudioSource -c -t output)"
94+
95+
cargo test --locked -p cap-verify --test recording --no-run --message-format=json 2>/dev/null \
96+
| python3 -c 'import json, sys
97+
for line in sys.stdin:
98+
m = json.loads(line)
99+
if m.get("reason") == "compiler-artifact" and m.get("executable") and m["target"]["name"] in ("recording", "cap-verify-stimulus"):
100+
print(m["executable"])' > "$RUNNER_TEMP/capture-binaries"
101+
test "$(wc -l < "$RUNNER_TEMP/capture-binaries")" -ge 2
102+
p=$$
103+
while [ "$p" -gt 1 ]; do
104+
ps -o comm= -p "$p"
105+
p=$(ps -o ppid= -p "$p" | tr -d ' ')
106+
done >> "$RUNNER_TEMP/capture-binaries"
107+
for c in "$(rustup which cargo)" "$HOME/.cargo/bin/cargo"; do echo "$c"; done >> "$RUNNER_TEMP/capture-binaries"
108+
109+
db="/Library/Application Support/com.apple.TCC/TCC.db"
110+
approvals="$HOME/Library/Group Containers/group.com.apple.replayd/ScreenCaptureApprovals.plist"
111+
mkdir -p "$(dirname "$approvals")"
112+
now=$(date +%s)
113+
until=$(date -u -v+1y '+%a %b %d %H:%M:%S GMT %Y')
114+
sort -u "$RUNNER_TEMP/capture-binaries" | grep '^/' | while read -r client; do
115+
for service in kTCCServiceScreenCapture kTCCServiceMicrophone; do
116+
sudo sqlite3 "$db" "INSERT OR REPLACE INTO access (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier,flags,last_modified) VALUES ('$service','$client',1,2,4,1,'UNUSED',0,$now);"
117+
done
118+
/usr/libexec/PlistBuddy -c "Add :$client date $until" "$approvals" >/dev/null 2>&1 || true
119+
echo "granted $client"
120+
done
121+
sudo killall tccd || true
122+
sleep 2
123+
124+
- name: Probe displays and ScreenCaptureKit
125+
run: |
126+
cat > /tmp/disp.swift <<'SWIFT'
127+
import CoreGraphics
128+
import ScreenCaptureKit
129+
import Foundation
130+
print("CGMainDisplayID", CGMainDisplayID(), "preflight", CGPreflightScreenCaptureAccess())
131+
var ids = [CGDirectDisplayID](repeating: 0, count: 8); var n: UInt32 = 0
132+
CGGetActiveDisplayList(8, &ids, &n)
133+
for i in 0..<Int(n) { print("active", ids[i], CGDisplayBounds(ids[i])) }
134+
let sem = DispatchSemaphore(value: 0)
135+
SCShareableContent.getWithCompletionHandler { c, e in
136+
if let e = e { print("sck error", e) }
137+
for d in c?.displays ?? [] { print("sck display", d.displayID, d.width, d.height, d.frame) }
138+
print("sck windows", c?.windows.count ?? -1)
139+
sem.signal()
140+
}
141+
sem.wait()
142+
SWIFT
143+
swiftc -O /tmp/disp.swift -o /tmp/disp
144+
/tmp/disp
145+
sudo sqlite3 "/Library/Application Support/com.apple.TCC/TCC.db" "select client from access where service='kTCCServiceScreenCapture';" | head -3
146+
/tmp/disp_bin_dummy 2>/dev/null || true
147+
148+
- name: Probe stimulus audio start
149+
run: |
150+
stim=$(grep cap-verify-stimulus "$RUNNER_TEMP/capture-binaries" | head -1)
151+
spec='{"fps":15.0,"seconds":4.0,"holds":[],"busy":true,"warmup":1.0,"audio":true,"audio_period":0.5,"audio_origin":0,"window":null}'
152+
for variant in blackhole default; do
153+
echo "=== variant $variant"
154+
if [ "$variant" = default ]; then unset CAP_VERIFY_AUDIO_OUTPUT; fi
155+
( sleep 100000 | "$stim" "$spec" /tmp/stim-$variant.jsonl > /tmp/stim-$variant.out 2>&1 ) &
156+
sleep 12
157+
pid=$(pgrep -f "cap-verify-stimulus" | head -1)
158+
echo "pid $pid"; cat /tmp/stim-$variant.out; echo "log:"; cat /tmp/stim-$variant.jsonl | head -5
159+
if [ -n "$pid" ]; then sample "$pid" 2 -file /tmp/sample-$variant.txt >/dev/null 2>&1; grep -A45 "Thread_.*DispatchQueue_1: com.apple.main-thread" /tmp/sample-$variant.txt | cut -c1-160 | head -60; pkill -f cap-verify-stimulus || true; fi
160+
sleep 2
161+
done
162+
163+
- name: Probe one capture case
164+
run: |
165+
RUST_LOG=cap_recording=debug,scap_targets=debug CAP_VERIFY_CASE=recording/instant/late-mic CAP_VERIFY_TIER=full CAP_VERIFY_REPEAT=1 \
166+
cargo test --locked -p cap-verify --test recording real_capture -- --nocapture 2>&1 | grep -iE "display|content filter|AsContentFilter|FAIL|pass|xfail|shareable" | head -40 || true

0 commit comments

Comments
 (0)