Skip to content

Commit ebe595e

Browse files
authored
test(#315): CoreAudioTapGatewayのカバレッジ0%をスモークテストで解消 (#316)
![agent](https://img.shields.io/badge/agent-claude-D97757) ![type](https://img.shields.io/badge/type-test-blue) ![breaking](https://img.shields.io/badge/breaking-no-green) ![scope](https://img.shields.io/badge/scope-coverage-blue) ![diff](https://img.shields.io/badge/diff-+94%20--3-green) ![files](https://img.shields.io/badge/files-4%20files-green) ![tests](https://img.shields.io/badge/tests-added-green) ![review](https://img.shields.io/badge/review-quick%20look-green) Closes #315 ## <img src="https://mojiemoji.jozo.beer/emoji/%E6%A6%82%E8%A6%81?font=toge&amp;color=f43f5e&amp;animation=kage_neon&amp;background=transparent&amp;outline=5ef43f&amp;outline_width=2" alt="概要" height="20" align="absmiddle"> <img src="https://mojiemoji.jozo.beer/emoji/PR?font=hachimaru&amp;color=ef4444&amp;animation=bure&amp;background=transparent&amp;outline=44ef44&amp;outline_width=2" alt="PR" height="20" align="absmiddle"> #314 で独立モジュール化した `CoreAudioTapGateway` に<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=zero&amp;color=a78bfa&amp;animation=chirichiri&amp;background=transparent&amp;outline=ed7c3a&amp;outline_width=2" alt="テスト" height="20" align="absmiddle">ターゲットが無く<img src="https://mojiemoji.jozo.beer/emoji/%E3%82%AB%E3%83%90%E3%83%AC?font=maru&amp;color=facc15&amp;animation=kirari&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="カバレ" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E3%83%83%E3%82%B8?font=maru&amp;color=facc15&amp;animation=kirari&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="ッジ" height="20" align="absmiddle"> 0% だったのを、<img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle"> 実行<img src="https://mojiemoji.jozo.beer/emoji/%E5%8F%AF%E8%83%BD?font=noto&amp;color=4ade80&amp;animation=mochimochi&amp;background=transparent&amp;outline=4a16a3&amp;outline_width=2" alt="可能" height="20" align="absmiddle">なスモーク<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=hachimaru&amp;color=f43f5e&amp;animation=yoko_scroll&amp;background=transparent&amp;outline=5ef43f&amp;outline_width=2" alt="テスト" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/5?font=zero&amp;color=fb923c&amp;animation=kira&amp;background=transparent&amp;outline_width=0" alt="5" height="20" align="absmiddle">本で<img src="https://mojiemoji.jozo.beer/emoji/%E8%A7%A3%E6%B6%88?font=gothic-bold&color=34d399&animation=mochimochi&speed=slow&background=transparent&outline=9934d3&outline_width=2" alt="解消" height="24" align="absmiddle">する。ローカル<img src="https://mojiemoji.jozo.beer/emoji/%E8%A8%88%E6%B8%AC?font=kurobara&amp;color=f43f5e&amp;animation=patapata&amp;background=transparent&amp;outline=5ef43f&amp;outline_width=2" alt="計測" height="20" align="absmiddle">で行<img src="https://mojiemoji.jozo.beer/emoji/%E3%82%AB%E3%83%90%E3%83%AC?font=maru&amp;color=facc15&amp;animation=kirari&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="カバレ" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E3%83%83%E3%82%B8?font=maru&amp;color=facc15&amp;animation=kirari&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="ッジ" height="20" align="absmiddle"> **0% → 85.5%**(関数 10/12)。 ## <img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=maru-bold&amp;color=fbbf24&amp;animation=yokomoya&amp;background=transparent&amp;outline=06d977&amp;outline_width=2" alt="テスト" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E5%86%85%E5%AE%B9?font=gothic&amp;color=fdba74&amp;animation=kira&amp;background=transparent&amp;outline_width=0" alt="内容" height="20" align="absmiddle"> | <img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=zero&amp;color=a78bfa&amp;animation=chirichiri&amp;background=transparent&amp;outline=ed7c3a&amp;outline_width=2" alt="テスト" height="20" align="absmiddle"> | <img src="https://mojiemoji.jozo.beer/emoji/%E6%A4%9C%E8%A8%BC?font=gothic&amp;color=22c55e&amp;animation=nami&amp;background=transparent&amp;outline=5e22c5&amp;outline_width=2" alt="検証" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E5%86%85%E5%AE%B9?font=maru-bold&amp;color=a855f7&amp;animation=mochimochi&amp;background=transparent&amp;outline=f7a855&amp;outline_width=2" alt="内容" height="20" align="absmiddle"> | <img src="https://mojiemoji.jozo.beer/emoji/%E6%B1%BA%E5%AE%9A?font=noto&amp;color=fb923c&amp;animation=zairu&amp;background=transparent&amp;outline=0cea58&amp;outline_width=2" alt="決定" height="20" align="absmiddle">性 | |---|---|---| | invalid object IDs → nil | `processPid` / `tapFormat` / `createIOProc` の guard <img src="https://mojiemoji.jozo.beer/emoji/%E3%83%91%E3%82%B9?font=pixel&amp;color=a78bfa&amp;animation=mabataki&amp;background=transparent&amp;outline=ed7c3a&amp;outline_width=2" alt="パス" height="20" align="absmiddle"> | <img src="https://mojiemoji.jozo.beer/emoji/%E6%B1%BA%E5%AE%9A?font=noto&amp;color=fb923c&amp;animation=zairu&amp;background=transparent&amp;outline=0cea58&amp;outline_width=2" alt="決定" height="20" align="absmiddle">的 | | destroy on invalid IDs | `destroyAggregateDevice` / `destroyProcessTap` がエラーを<img src="https://mojiemoji.jozo.beer/emoji/%E5%AE%89%E5%85%A8?font=chikara&amp;color=f472b6&amp;animation=disco&amp;background=transparent&amp;outline_width=0" alt="安全" height="20" align="absmiddle">に無視 | <img src="https://mojiemoji.jozo.beer/emoji/%E6%B1%BA%E5%AE%9A?font=noto&amp;color=fb923c&amp;animation=zairu&amp;background=transparent&amp;outline=0cea58&amp;outline_width=2" alt="決定" height="20" align="absmiddle">的 | | `processObjects` + pid 解決 | TCC <img src="https://mojiemoji.jozo.beer/emoji/%E4%B8%8D%E8%A6%81?font=mincho&amp;color=8b5cf6&amp;animation=gatagata&amp;background=transparent&amp;outline=f68b5c&amp;outline_width=2" alt="不要" height="20" align="absmiddle">の HAL メタデータクエリ。実プロセスオブジェクトで `processPid` の<img src="https://mojiemoji.jozo.beer/emoji/%E6%88%90%E5%8A%9F?font=maru&amp;color=10b981&amp;animation=bure&amp;background=transparent&amp;outline=8110b9&amp;outline_width=2" alt="成功" height="20" align="absmiddle">系もカバー | 環境依存(nil 許容) | | aggregate device round-trip | `createAggregateDevice` → `createIOProc` → `start`/`stop` → `destroyIOProc` → `destroyAggregateDevice` | 環境依存(nil 許容) | | `createProcessTap` guard <img src="https://mojiemoji.jozo.beer/emoji/%E3%83%91%E3%82%B9?font=chikara&amp;color=facc15&amp;animation=patapata&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="パス" height="20" align="absmiddle"> | TCC 未<img src="https://mojiemoji.jozo.beer/emoji/%E8%A8%B1%E5%8F%AF?font=tamanegi&amp;color=c084fc&amp;animation=zairu&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="許可" height="20" align="absmiddle">の無人ランナーでエラー → nil | **<img src="https://mojiemoji.jozo.beer/emoji/CI?font=kurobara&amp;color=22c55e&amp;animation=tatemoya&amp;background=transparent&amp;outline=5e22c5&amp;outline_width=2" alt="CI" height="20" align="absmiddle"> 限定** | ## <img src="https://mojiemoji.jozo.beer/emoji/%E8%A8%AD%E8%A8%88?font=tamanegi&amp;color=3b82f6&amp;animation=yurayura&amp;background=transparent&amp;outline=f63b82&amp;outline_width=2" alt="設計" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E5%88%A4%E6%96%AD?font=kurobara&amp;color=06b6d4&amp;animation=yurayura&amp;background=transparent&amp;outline=d406b6&amp;outline_width=2" alt="判断" height="20" align="absmiddle"> - **nil 許容のスモーク粒度** — HAL の状態に依存するステップは<img src="https://mojiemoji.jozo.beer/emoji/%E6%88%90%E5%8A%9F?font=gothic-bold&amp;color=facc15&amp;animation=mochimochi&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="成功" height="20" align="absmiddle">を assert せず、<img src="https://mojiemoji.jozo.beer/emoji/%E5%A4%B1%E6%95%97?font=maru-bold&amp;color=3b82f6&amp;animation=yokomoya&amp;background=transparent&amp;outline=f63b82&amp;outline_width=2" alt="失敗" height="20" align="absmiddle">時は<img src="https://mojiemoji.jozo.beer/emoji/%E6%97%A9%E6%9C%9F?font=gothic-bold&amp;color=f472b6&amp;animation=tatemoya&amp;background=transparent&amp;outline=77db27&amp;outline_width=2" alt="早期" height="20" align="absmiddle"> return。ヘッドレス <img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle"> ランナーでの flaky を避ける(issue #315 の方針どおり)。ローカル実機では round-trip が実際に<img src="https://mojiemoji.jozo.beer/emoji/%E5%AE%8C%E8%B5%B0?font=gothic-bold&color=60a5fa&animation=zairu&speed=slow&background=transparent&outline=fa60a5&outline_width=2" alt="完走" height="24" align="absmiddle">することを<img src="https://mojiemoji.jozo.beer/emoji/%E7%A2%BA%E8%AA%8D?font=gothic-bold&amp;color=d946ef&amp;animation=zairu&amp;background=transparent&amp;outline=efd946&amp;outline_width=2" alt="確認" height="20" align="absmiddle">済み - **`createProcessTap` は `CI` 環境変数でゲート** — ローカルの `swift test` で System Audio Recording の TCC プロンプトが出るのを防ぐ。GitHub Actions では `CI=true` が立つので guard <img src="https://mojiemoji.jozo.beer/emoji/%E3%83%91%E3%82%B9?font=chikara&amp;color=facc15&amp;animation=patapata&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="パス" height="20" align="absmiddle">が実行される - **codecov ignore への<img src="https://mojiemoji.jozo.beer/emoji/%E8%BF%BD%E5%8A%A0?font=kurobara&amp;color=60a5fa&amp;animation=psycho&amp;background=transparent&amp;outline_width=0" alt="追加" height="20" align="absmiddle">はしない** — ignore は protocol / stub / wiring 専用(coverage-ignored-modules の<img src="https://mojiemoji.jozo.beer/emoji/%E5%8E%9F%E5%89%87?font=pixel&amp;color=fbbf24&amp;animation=tatemoya&amp;background=transparent&amp;outline=06d977&amp;outline_width=2" alt="原則" height="20" align="absmiddle">)。covered module として<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=zero&amp;color=a78bfa&amp;animation=chirichiri&amp;background=transparent&amp;outline=ed7c3a&amp;outline_width=2" alt="テスト" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E5%8F%AF%E8%83%BD?font=noto&amp;color=4ade80&amp;animation=mochimochi&amp;background=transparent&amp;outline=4a16a3&amp;outline_width=2" alt="可能" height="20" align="absmiddle">な<img src="https://mojiemoji.jozo.beer/emoji/%E7%AF%84%E5%9B%B2?font=gothic-bold&amp;color=8b5cf6&amp;animation=zairu&amp;background=transparent&amp;outline=f68b5c&amp;outline_width=2" alt="範囲" height="20" align="absmiddle">を<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=hachimaru&amp;color=f43f5e&amp;animation=yoko_scroll&amp;background=transparent&amp;outline=5ef43f&amp;outline_width=2" alt="テスト" height="20" align="absmiddle">する `DarwinGateway` との<img src="https://mojiemoji.jozo.beer/emoji/%E5%AF%BE%E7%A7%B0?font=mincho&amp;color=10b981&amp;animation=zairu&amp;background=transparent&amp;outline=8110b9&amp;outline_width=2" alt="対称" height="20" align="absmiddle">性を保つ ## <img src="https://mojiemoji.jozo.beer/emoji/%E5%AF%BE%E8%B1%A1?font=gothic-bold&amp;color=f97316&amp;animation=gatagata&amp;background=transparent&amp;outline=16f973&amp;outline_width=2" alt="対象" height="20" align="absmiddle">外(Humble Object の残渣) `createProcessTap` の<img src="https://mojiemoji.jozo.beer/emoji/%E6%88%90%E5%8A%9F?font=gothic-bold&amp;color=facc15&amp;animation=mochimochi&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="成功" height="20" align="absmiddle">系(noErr 分岐)は TCC <img src="https://mojiemoji.jozo.beer/emoji/%E8%A8%B1%E5%8F%AF?font=gothic&amp;color=34d399&amp;animation=gatagata&amp;background=transparent&amp;outline=9934d3&amp;outline_width=2" alt="許可" height="20" align="absmiddle">+実音源が<img src="https://mojiemoji.jozo.beer/emoji/%E5%BF%85%E9%A0%88?font=akzk&amp;color=60a5fa&amp;animation=ekken&amp;background=transparent&amp;outline=fa60a5&amp;outline_width=2" alt="必須" height="20" align="absmiddle">で無人 <img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle"> では実行不<img src="https://mojiemoji.jozo.beer/emoji/%E5%8F%AF%E8%83%BD?font=noto&amp;color=4ade80&amp;animation=mochimochi&amp;background=transparent&amp;outline=4a16a3&amp;outline_width=2" alt="可能" height="20" align="absmiddle">。VM レーン/実機<img src="https://mojiemoji.jozo.beer/emoji/%E6%A4%9C%E8%A8%BC?font=tamanegi&amp;color=06b6d4&amp;animation=yoko_scroll&amp;background=transparent&amp;outline=d406b6&amp;outline_width=2" alt="検証" height="20" align="absmiddle">の領分として未カバーを<img src="https://mojiemoji.jozo.beer/emoji/%E8%A8%B1%E5%AE%B9?font=maru&color=94a3b8&animation=yurayura&speed=slow&background=transparent&outline=b894a3&outline_width=2" alt="許容" height="24" align="absmiddle">する。 ## <img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle"> プロセス<img src="https://mojiemoji.jozo.beer/emoji/%E5%88%86%E9%9B%A2?font=mincho&amp;color=22c55e&amp;animation=ekken&amp;background=transparent&amp;outline=5e22c5&amp;outline_width=2" alt="分離" height="20" align="absmiddle">(初回<img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle">の<img src="https://mojiemoji.jozo.beer/emoji/%E5%A4%B1%E6%95%97?font=hachimaru&amp;color=f472b6&amp;animation=mozaiku&amp;background=transparent&amp;outline=b6f472&amp;outline_width=2" alt="失敗" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E5%AF%BE%E5%BF%9C?font=maru&amp;color=fdba74&amp;animation=chirichiri&amp;background=transparent&amp;outline=74fdba&amp;outline_width=2" alt="対応" height="20" align="absmiddle">) 初回の<img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle">実行で、本スイートと<img src="https://mojiemoji.jozo.beer/emoji/%E5%90%8C%E4%B8%80?font=dela&amp;color=34d399&amp;animation=ekken&amp;background=transparent&amp;outline=9934d3&amp;outline_width=2" alt="同一" height="20" align="absmiddle">プロセス内で並列実行される `WallpaperPresenterTests`(実 `AVPlayer` を使用)が約20件タイムアウトする<img src="https://mojiemoji.jozo.beer/emoji/%E5%B9%B2%E6%B8%89?font=zero&amp;color=a78bfa&amp;animation=poyoon&amp;background=transparent&amp;outline=ed7c3a&amp;outline_width=2" alt="干渉" height="20" align="absmiddle">が<img src="https://mojiemoji.jozo.beer/emoji/%E7%99%BA%E7%94%9F?font=noto&amp;color=60a5fa&amp;animation=tate_scroll&amp;background=transparent&amp;outline=eb2563&amp;outline_width=2" alt="発生" height="20" align="absmiddle">した(新スイート<img src="https://mojiemoji.jozo.beer/emoji/5?font=zero&amp;color=fb923c&amp;animation=kira&amp;background=transparent&amp;outline_width=0" alt="5" height="20" align="absmiddle">本自体は全<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%91%E3%82%B9?font=gothic&amp;color=a855f7&amp;animation=kirari&amp;background=transparent&amp;outline=f7a855&amp;outline_width=2" alt="パス" height="20" align="absmiddle">・各0.9秒未満)。ヘッドレスランナー上でのHAL接触(aggregate device の<img src="https://mojiemoji.jozo.beer/emoji/%E7%94%9F%E6%88%90?font=maru&amp;color=facc15&amp;animation=tate_scroll&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="生成" height="20" align="absmiddle">/破棄・tap 試行)が AVFoundation 系の<img src="https://mojiemoji.jozo.beer/emoji/%E5%88%9D%E6%9C%9F?font=zero&amp;color=3b82f6&amp;animation=mabataki&amp;background=transparent&amp;outline=f63b82&amp;outline_width=2" alt="初期" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E5%8C%96?font=zero&amp;color=3b82f6&amp;animation=mabataki&amp;background=transparent&amp;outline=f63b82&amp;outline_width=2" alt="化" height="20" align="absmiddle">を不<img src="https://mojiemoji.jozo.beer/emoji/%E5%AE%89%E5%AE%9A?font=gothic&amp;color=c084fc&amp;animation=bure&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="安定" height="20" align="absmiddle">化させたと推定される。`test.yml` で本スイートのみを別の swift-test プロセスで実行するよう<img src="https://mojiemoji.jozo.beer/emoji/%E5%88%86%E9%9B%A2?font=mincho&amp;color=22c55e&amp;animation=ekken&amp;background=transparent&amp;outline=5e22c5&amp;outline_width=2" alt="分離" height="20" align="absmiddle">し(`--skip` / `--filter`)、profdata は<img src="https://mojiemoji.jozo.beer/emoji/%E6%97%A2%E5%AD%98?font=dela&amp;color=f472b6&amp;animation=kira&amp;background=transparent&amp;outline_width=0" alt="既存" height="20" align="absmiddle">の `*.profdata` マージへ<img src="https://mojiemoji.jozo.beer/emoji/%E5%90%88%E6%B5%81?font=noto&amp;color=fbbf24&amp;animation=yoko_scroll&amp;background=transparent&amp;outline=06d977&amp;outline_width=2" alt="合流" height="20" align="absmiddle">させた。ローカルの `swift test` は従来どおり全スイート一括で<img src="https://mojiemoji.jozo.beer/emoji/%E5%95%8F%E9%A1%8C?font=mincho&amp;color=facc15&amp;animation=zairu&amp;background=transparent&amp;outline=04ca8a&amp;outline_width=2" alt="問題" height="20" align="absmiddle">ない(実オーディオデバイスのある環境では<img src="https://mojiemoji.jozo.beer/emoji/%E5%B9%B2%E6%B8%89?font=akzk&amp;color=eab308&amp;animation=yokomoya&amp;background=transparent&amp;outline=08eab3&amp;outline_width=2" alt="干渉" height="20" align="absmiddle">は<img src="https://mojiemoji.jozo.beer/emoji/%E5%86%8D%E7%8F%BE?font=maru&amp;color=eab308&amp;animation=ekken&amp;background=transparent&amp;outline=08eab3&amp;outline_width=2" alt="再現" height="20" align="absmiddle">しない)。 ## <img src="https://mojiemoji.jozo.beer/emoji/%E6%A4%9C%E8%A8%BC?font=tamanegi&amp;color=06b6d4&amp;animation=yoko_scroll&amp;background=transparent&amp;outline=d406b6&amp;outline_width=2" alt="検証" height="20" align="absmiddle"> - `swift test` — 全 1151 <img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=hachimaru&amp;color=f43f5e&amp;animation=yoko_scroll&amp;background=transparent&amp;outline=5ef43f&amp;outline_width=2" alt="テスト" height="20" align="absmiddle">(273 スイート)<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%91%E3%82%B9?font=pixel&color=4ade80&animation=yatta&speed=normal&background=transparent&outline=804ade&outline_width=2" alt="パス" height="24" align="absmiddle"> - `make lint` — クリーン - `xcrun llvm-cov report` — <img src="https://mojiemoji.jozo.beer/emoji/%E5%AF%BE%E8%B1%A1?font=maru-bold&amp;color=8b5cf6&amp;animation=psycho&amp;background=transparent&amp;outline_width=0" alt="対象" height="20" align="absmiddle">ファイル行<img src="https://mojiemoji.jozo.beer/emoji/%E3%82%AB%E3%83%90%E3%83%AC?font=kurobara&amp;color=22c55e&amp;animation=chirichiri&amp;background=transparent&amp;outline=5e22c5&amp;outline_width=2" alt="カバレ" height="20" align="absmiddle"><img src="https://mojiemoji.jozo.beer/emoji/%E3%83%83%E3%82%B8?font=kurobara&amp;color=22c55e&amp;animation=chirichiri&amp;background=transparent&amp;outline=5e22c5&amp;outline_width=2" alt="ッジ" height="20" align="absmiddle"> 85.54%(ローカル、<img src="https://mojiemoji.jozo.beer/emoji/CI?font=rampart&amp;color=c084fc&amp;animation=gatagata&amp;background=transparent&amp;outline=fcc084&amp;outline_width=2" alt="CI" height="20" align="absmiddle"> 限定<img src="https://mojiemoji.jozo.beer/emoji/%E3%83%86%E3%82%B9%E3%83%88?font=zero&amp;color=a78bfa&amp;animation=chirichiri&amp;background=transparent&amp;outline=ed7c3a&amp;outline_width=2" alt="テスト" height="20" align="absmiddle">除く) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added automated coverage for Core Audio tap and aggregate-device workflows, including creation, startup, shutdown, and cleanup scenarios. * Added validation for handling invalid audio object identifiers safely. * **Chores** * Updated the application version to **2.20.6**. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 9b9868f + bc2f8c0 commit ebe595e

4 files changed

Lines changed: 94 additions & 3 deletions

File tree

.github/workflows/test.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,25 @@ jobs:
4848
PROFDATA="$BIN/codecov/default.profdata"
4949
COVERAGE_DIR="$RUNNER_TEMP/coverage"
5050
mkdir -p "$COVERAGE_DIR"
51-
swift test --enable-code-coverage
51+
# CoreAudioTapGatewayTests exercises the live CoreAudio HAL
52+
# (aggregate-device churn, process-tap attempt). On a headless
53+
# runner that destabilizes AVPlayer-dependent suites sharing the
54+
# same process, so it runs in its own swift-test process (#315).
55+
swift test --enable-code-coverage --skip CoreAudioTapGatewayTests
5256
5357
if [[ ! -f "$PROFDATA" ]]; then
5458
echo "Coverage profile not found: $PROFDATA"
5559
exit 1
5660
fi
61+
cp "$PROFDATA" "$COVERAGE_DIR/main.profdata"
5762
58-
cp "$PROFDATA" "$COVERAGE_DIR/default.profdata"
63+
swift test --enable-code-coverage --filter CoreAudioTapGatewayTests
64+
65+
if [[ ! -f "$PROFDATA" ]]; then
66+
echo "Coverage profile not found: $PROFDATA"
67+
exit 1
68+
fi
69+
cp "$PROFDATA" "$COVERAGE_DIR/gateway.profdata"
5970
6071
echo "coverage_dir=$COVERAGE_DIR" >> "$GITHUB_OUTPUT"
6172

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,10 @@ let package = Package(
515515
"Domain",
516516
]
517517
),
518+
.testTarget(
519+
name: "CoreAudioTapGatewayTests",
520+
dependencies: ["CoreAudioTapGateway"]
521+
),
518522
.testTarget(name: "EntityTests", dependencies: ["Entity"]),
519523
.testTarget(
520524
name: "AsyncRunnableCommandTests",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.20.5
1+
2.20.6
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import CoreAudio
2+
import CoreAudioTapGateway
3+
import Foundation
4+
import Testing
5+
6+
/// CI-runnable smoke tests for the live CoreAudio pass-through (#315).
7+
///
8+
/// Every wrapper is guard-based, so exercising each call covers its lines
9+
/// even when the HAL refuses the operation. Environment-dependent steps
10+
/// (aggregate device creation on a headless runner) tolerate `nil` instead
11+
/// of asserting success — the untestable residue is the process-tap happy
12+
/// path, which needs the System Audio Recording TCC grant plus a live audio
13+
/// source and stays out of scope here.
14+
///
15+
/// CI runs this suite in its own swift-test process (see test.yml): on a
16+
/// headless runner the live HAL churn destabilizes AVPlayer-dependent
17+
/// suites sharing the process (empirically: WallpaperPresenter timeouts).
18+
@Suite("CoreAudioTapGateway", .serialized)
19+
struct CoreAudioTapGatewayTests {
20+
private let gateway = CoreAudioTapGateway()
21+
private let invalid = AudioObjectID(kAudioObjectUnknown)
22+
23+
@Test("invalid object IDs take the guard path and return nil")
24+
func invalidObjectIDsReturnNil() {
25+
#expect(gateway.processPid(of: invalid) == nil)
26+
#expect(gateway.tapFormat(of: invalid) == nil)
27+
#expect(gateway.createIOProc(aggregateID: invalid, block: { _, _, _, _, _ in }) == nil)
28+
}
29+
30+
@Test("destroy calls on invalid IDs are safely ignored")
31+
func destroyOnInvalidIDsIsIgnored() {
32+
gateway.destroyAggregateDevice(invalid)
33+
gateway.destroyProcessTap(invalid)
34+
}
35+
36+
@Test("processObjects queries the HAL and resolves pids without TCC")
37+
func processObjectsResolvePids() {
38+
let objects = gateway.processObjects()
39+
#expect(objects.allSatisfy { $0 != invalid })
40+
let pids = objects.compactMap(gateway.processPid(of:))
41+
#expect(pids.allSatisfy { $0 > 0 })
42+
}
43+
44+
@Test("aggregate device round-trip: create → IOProc → start/stop → destroy")
45+
func aggregateDeviceRoundTrip() {
46+
let description: [String: Any] = [
47+
kAudioAggregateDeviceNameKey: "lyra-test-aggregate",
48+
kAudioAggregateDeviceUIDKey: UUID().uuidString,
49+
kAudioAggregateDeviceIsPrivateKey: true,
50+
]
51+
guard let aggregateID = gateway.createAggregateDevice(description) else { return }
52+
defer { gateway.destroyAggregateDevice(aggregateID) }
53+
guard
54+
let ioProcID = gateway.createIOProc(
55+
aggregateID: aggregateID, block: { _, _, _, _, _ in })
56+
else { return }
57+
defer { gateway.destroyIOProc(aggregateID: aggregateID, ioProcID: ioProcID) }
58+
guard gateway.start(aggregateID: aggregateID, ioProcID: ioProcID) else { return }
59+
gateway.stop(aggregateID: aggregateID, ioProcID: ioProcID)
60+
}
61+
62+
@Test("createProcessTap without a TCC grant takes the guard path")
63+
func processTapWithoutPermission() {
64+
// Creating a process tap can raise the System Audio Recording TCC
65+
// prompt on a developer machine, so this runs only on CI, where the
66+
// unattended denial exercises the error → nil guard path.
67+
guard ProcessInfo.processInfo.environment["CI"] == "true" else { return }
68+
guard #available(macOS 14.4, *) else { return }
69+
guard
70+
let tapID = gateway.createProcessTap(
71+
CATapDescription(stereoMixdownOfProcesses: []))
72+
else { return }
73+
_ = gateway.tapFormat(of: tapID)
74+
gateway.destroyProcessTap(tapID)
75+
}
76+
}

0 commit comments

Comments
 (0)