リモートMacで開発したmacOSアプリのファイル読み取り機能が、テストディレクトリでは正常に動くのに、ユーザー環境では「アクセス権がありません」とエラーになることがあります。テストファイルがたまたまアプリ自身のコンテナ内にあったり、ファイル選択直後しか確認せず、アプリの終了後に再び開けるかを検証していなかったりするのが典型的な原因です。この機能を受け入れ検証するには、ユーザーが自ら選択したコンテナ外のファイルを使い、アプリの再起動後まで確認する必要があります。
テストの対象と範囲を明確にする
App Sandboxは、アプリによるコンテナ外のファイルへのアクセスを制限します。ユーザーがシステムのファイル選択ダイアログを通じてアクセスを許可しても、アプリが元のパスだけを使って永続的に読み取れるわけではありません。アプリがそのファイルを記憶する必要がある場合は、セキュリティスコープ付きブックマークを保存し、次回の起動時に解決したうえで、実際の読み書き中にアクセスを開始する必要があります。
内容を識別できる通常のテキストファイルを1つ用意し、アプリのコンテナ外に置きます。別途、システムのプライバシー権限で保護されるディレクトリは選ばないでください。今回のテストに別の権限付与の仕組みが混ざるのを避けるためです。比較用にコンテナ内のファイルも1つ用意します。こちらを読み取れても、基本的なファイル処理コードが動くことを示すだけで、外部ファイルへのアクセス手順が正しい証明にはなりません。
合格基準は「ファイル選択ダイアログを閉じた後にエラーが出ない」ことではありません。「アプリの再起動後も、許可された範囲で指定ファイルを読み取れ、許可が使えない場合には復旧できる操作を提示する」ことです。
配布するアプリの権限宣言を確認する
まず確認するのは、実際に配布する .app です。プロジェクト内で編集した権限ファイルだけを見て判断しないでください。ビルド設定や署名処理に食い違いがあると、最終成果物の宣言が異なる場合があります。以下の APP を今回のビルド成果物の実際のパスに変更します。
APP="$PWD/build/Reader.app"
codesign --verify --strict --verbose=2 "$APP"
codesign -d --entitlements :- "$APP"
Sandboxの権限が有効か確認します。読み取り専用で取り込む機能なら、ユーザーが選択したファイルの読み取り権限を確認します。元ファイルの変更が本当に必要な場合に限り、読み書き権限を検討してください。起動をまたいでアクセス権を保持する場合は、アプリスコープのブックマークに関する宣言も確認します。テストを通すために、無関係なディレクトリへのアクセス権を広げてはいけません。
署名の確認で分かるのは「何を宣言しているか」までです。「ユーザーがファイルを選択したか」「ブックマークからアクセスを回復できるか」は分かりません。この2点はアプリを実行して検証する必要があります。
ブックマークを保存し、アクセスの開始と終了を対にする
NSOpenPanel からユーザーが選択したURLを受け取ったら、セキュリティスコープ付きブックマークを作成し、得られた Data をアプリ自身の永続ストレージに保存します。url.path だけを保存してはいけません。パスは場所を記録しますが、アクセス権を再取得するための情報は含みません。
読み取り処理では、ブックマークの解決、アクセスの開始、終了を同じ関数内にまとめ、エラー時にも後処理が漏れないようにします。
enum FileAccessError: Error {
case staleBookmark
case accessDenied
}
func readSelectedFile(bookmark: Data) throws -> Data {
var stale = false
let url = try URL(
resolvingBookmarkData: bookmark,
options: .withSecurityScope,
relativeTo: nil,
bookmarkDataIsStale: &stale
)
guard !stale else { throw FileAccessError.staleBookmark }
guard url.startAccessingSecurityScopedResource() else {
throw FileAccessError.accessDenied
}
defer { url.stopAccessingSecurityScopedResource() }
return try Data(contentsOf: url)
}
ブックマークの作成時にもセキュリティスコープのオプションを指定し、返されたデータを適切に保存する必要があります。この例では、ブックマークが失効していれば読み取りを中止します。製品の画面にはファイルを再選択する手段を用意し、新たな許可を得てからブックマークを作り直してください。startAccessingSecurityScopedResource() の成否確認を省略したり、stopAccessingSecurityScopedResource() の呼び出しをアプリ終了時まで先延ばしにしたりしてはいけません。
GUIセッションで再起動を含む回帰テストを行う
クラウドMacではコマンドラインから署名を確認できますが、NSOpenPanel の操作を検証するには、利用可能なmacOSのGUIセッションが必要です。SSHでコマンドを実行できることと、ファイル選択ダイアログが正常に表示されることを混同しないでください。自動テストでも、テストプロセスがGUIセッションを利用できる環境で動いていることを確認します。
同じテストファイルを使い、次の順序で実行します。各段階で期待結果と実際の結果を記録してください。
- アプリを初回起動し、ファイル選択ダイアログを開かずに、保存済みのパスから外部ファイルを直接読み取ろうとする。パスだけで想定した権限付与を回避できないことを確認する。
- ダイアログでそのファイルを明示的に選択し、内容を読み取って照合するとともに、ブックマークを保存する。
- アプリを完全に終了し、同じ配布成果物を再起動する。ブックマークを解決し、ファイルを再度読み取って内容を照合する。
- テストファイルを移動してから再試行する。ファイルが存在しない状態を署名の不具合として誤って報告するのではなく、理解できるエラーと再選択の手段が表示されることを確認する。
- 破損したブックマークデータでエラー処理をテストする。アプリは読み取りを中止して再選択を求めるべきであり、ダイアログを繰り返し表示したり、無限に再試行したりしてはいけない。
見落としやすいのは3番目です。同じプロセスが動いている間だけ読み取るテストでは、ブックマークが永続ストレージに保存されていない問題を見逃す可能性があります。ファイルへの書き戻しも要件に含まれる場合は、読み書き権限を使ってこの一連の操作を別途繰り返してください。読み取り専用のテストで代用はできません。
失敗時は証拠を基に原因を絞り込む
「選択直後に失敗する」場合は、まずファイル選択ダイアログが返したURLを正しく使っているか、権限宣言が読み書きの要件に合っているかを確認します。「選択直後は成功するが、再起動後に失敗する」場合は、ブックマークが実際に永続化されたか、再起動したアプリが同じアプリIDと署名設定を維持しているかを優先して調べます。「特定のディレクトリだけ失敗する」場合は、Sandboxの権限を広げる前に、そのディレクトリ固有の追加のプライバシー権限が原因でないか切り分けてください。
回帰テストの記録には、少なくとも配布するアプリのバージョン、最終的な権限宣言、テストファイルを置いたディレクトリの種類、ファイル選択前後の結果、再起動後の結果、エラーの種類を残します。ログには必要な状態とエラーだけを記録し、ブックマークの生データやファイルの内容は出力しません。そうすれば次のビルドで失敗しても、チームは権限宣言の変更、ブックマークのライフサイクル、テスト環境にGUIセッションがないことのどれが原因か判断でき、「自分のマシンでは開ける」という話から推測をやり直さずに済みます。
よくある質問
ファイル選択後にブックマークを保存する理由は何ですか?
再起動後も同じファイルにアクセスするためです。セキュリティスコープ付きブックマークを保存し、読み取り時に復元してアクセスを開始し、完了後に終了します。
SSHだけで一連の検証を実行できますか?
できません。署名と権限の宣言はコマンドラインで確認できますが、ファイル選択画面と対話操作の検証には利用可能なmacOSのGUIセッションが必要です。
クラウドMacをワークフローに取り入れる
VPSPushでは、リモート接続できる専有の物理Macをご利用いただけます。ロケーションと契約期間を選び、注文画面で接続情報と最終金額をご確認ください。