test: win32 の fs semantics を windows-latest で実測する probe を足す - #503
Merged
Conversation
- #451 の symlink 境界は「flag が落ちた platform だとしたら」までしか pin できておらず、win32 の fs semantics は 1 つも実測されていなかった - 既存の skipIf(win32) test は un-skip しない。POSIX 前提の assertion (open 由来 ELOOP / ino 同一性 / mode 継承) の platform 分岐が広範囲に 要るため、測りたい前提だけを独立した probe file に置く - symlink 作成の失敗は skip に倒さない。「測れなかった」と「測って問題が なかった」を区別できない vacuous pass になるため - windows job は required check にしない。runner 固有の不安定さを merge gate に持ち込まないため Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 4 つの it が byte-identical な symlink fixture を作っていたので createFileSymlink に寄せる (simplify 指摘) - Windows は close 済み handle の解放が遅延しうるため、cleanup を git.test.ts の先例に合わせ maxRetries 付きの fsp.rm にする。infra 由来の EPERM / EBUSY を probe の赤にしないため - コメントの「Why not skip に倒さない」は二重否定で逆に読めるので修正 - ci.yml の恒常コスト記述を実態に合わせる。支配項は electron バイナリの 毎回取得であり、probe file 1 本に絞ってもそこは削れていない Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 「job 全体の支配項は electron バイナリ」と書いていたが、この job は まだ 1 度も走っておらず step timing の実測が存在しない。Windows runner の node_modules link I/O が上回る可能性を排除できない - 機構の事実 (postinstall 経由で毎回取得する / その cache は cache: pnpm の 対象外 / probe はそれを使わない) だけを残す - --ignore-scripts 保留の理由に katex css 生成も併記する Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- beforeEach が落ちた回でも afterEach は走るため、未代入の dir が fsp.rm に渡って TypeError になり、probe が出したい一次エラーに 二次エラーが重なっていた - git.test.ts の dirsToCleanup 配列パターンに揃える。作成に成功した分 だけが配列に入るので、失敗回は no-op になる - codex-review (qa-fixture) と code-reviewer が独立に同じ機構を指摘 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- rename(2) が末端 symlink を follow しない / O_EXCL が既存 symlink を EEXIST で拒否する、という 2 つの根拠は POSIX の probe でしか確かめて おらず、win32 の rename は MoveFileEx 系の別実装なので自動では移送 されない。pdf:export として Windows にも出荷される経路 - probe の形は darwin で実行して assert が成立することを確認済み。 Windows で赤くなれば test の誤りではなく platform 差になる Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 初回の windows 実測でこの 1 本だけ落ちたが、ループで 2 ケースを回して いたため、どちらが EEXIST を返さなかったのかが結果から読めなかった - probe の目的は platform 差の同定なので it を分ける Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- windows-latest の実測で 9 本中 8 本は想定どおりだったが、O_EXCL の dangling symlink だけ POSIX と割れた。win32 は reparse point を follow したうえで解決先が無いため CREATE_NEW が通り、解決先に file が作られる - open-nofollow.ts の「symlink であっても EEXIST」はこのケースで偽なので 事実を追記する。受容するか lstat を挟むかの判断は #504 - probe の期待値は実測値で pin する。production 側の判断が別 issue に 分かれているため、そちらが入ったら追随させる - 実測が入ったことで stale になった記述を更新: open-nofollow.test.ts の 「win32 実機では検証できない」と ADR-0011 の Windows bullet Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
win32 の fs semantics を実測する probe test を足し、
windows-latestでそれだけを回す最小 job を CI に追加する。Windows は出荷対象 platform(electron-builder.ymlの nsis /release.ymlの build matrix)でありながら、CI に test job が 1 つも無かった。#451 で
O_NOFOLLOWが無い platform 向けにlstatエミュレーションを入れたが、そこで pin できたのは「flag が落ちた platform だとしたらこう振る舞う」までで、win32 の fs semantics 自体は 1 つも実測していなかった。関連 Issue
closes #500
移行 Stage
実測結果(windows-latest, Node 22.13)
fs.constants.O_NOFOLLOWは win32 で undefined かNOFOLLOW_EMULATEDが true になるlstatは file symlink をisSymbolicLink()=trueで報告するかisFile()は false)readdir({withFileTypes:true})でどう出るかisDirectory()=false/isSymbolicLink()=true。walkMdFilesはent.isDirectory()でのみ再帰するので junction 経由の外部 tree 混入は無いrename(2)は destination の末端 symlink を follow しないかO_EXCLは既存 symlink を EEXIST で拒否するかGitHub の windows runner では
fs.symlinkの file symlink 作成が特権エラーにならなかった(1-7 すべてが実際に symlink を作って測れている)。junction も同様。#7 の扱い
open-nofollow.tsのNOFOLLOW_CREATE_EXCLUSIVE_FLAGSは「O_EXCLは既存 entry があれば symlink であっても EEXIST で落ちるので、tmp 名が衝突しても攻撃者が仕込んだ symlink を掴まされることはない」という根拠に乗っていた。この根拠は win32 の dangling symlink に対して偽だった(Windows は reparse point を follow したうえで解決先が無いためCREATE_NEWが通る)。本 PR では 事実の記録に留める(doc を実測に合わせ、probe の期待値を実測値で pin し、#504 を参照)。受容するか tmp path に
lstatを挟むかの production 判断は #504。実害はwriteFileAtomicNoFollowの tmp 名(48 bit 乱数)への先回りが要るため実用上は極小。変更内容
electron/main/utils/win32-fs-semantics.test.ts(新規)— win32 でのみ走る probe 9 本(上表に対応).github/workflows/ci.yml—win32-fs-probejob を追加electron/main/utils/open-nofollow.ts—O_EXCLの doc に win32 の dangling 例外を追記(win32 では O_EXCL が dangling symlink を follow して解決先に file を作る #504 参照)electron/main/utils/open-nofollow.test.ts/docs/adr/0011-search-visibility-follows-fs-read.md— 「win32 実機では検証できない」「CI で実測していない」が実測により stale になったので更新設計判断
skipIf(win32)test は un-skip しない —open-nofollow.test.ts/path-guard.test.tsの symlink 系は open 由来の ELOOP・stat().inoの同一性・mode 継承といった POSIX 前提の assertion で書かれており、win32 では拒否経路が emulation(lstat)に替わるため assertion 側の platform 分岐が広範囲に要る。測りたい前提だけを独立した probe file に置くfsp.symlinkは握らずそのまま await するci-skip.ymlは required job のみを stub する設計なので、そちらへの追加も不要動作確認
win32-fs-probejob pass 57s、9 tests passed)検証エビデンス
リスク分類
tier: high — ci-config: .github/workflows/ci.yml
実行した検証
env -u GIT_CONFIG_COUNT -u GIT_CONFIG_KEY_0 -u GIT_CONFIG_VALUE_0 -u GIT_CONFIG_KEY_1 -u GIT_CONFIG_VALUE_1 ./node_modules/.bin/vitest runwin32-fs-probejob(pnpm exec vitest run --project main electron/main/utils/win32-fs-semantics.test.ts)node <scratchpad>/probe-shape.mjs(rename / O_EXCL の assert を darwin で再現)./node_modules/.bin/biome check --write <変更した 3 file>./node_modules/.bin/tsc --noEmit -p tsconfig.node.json/-p tsconfig.web.json/-p tsconfig.e2e.jsongh pr checks 503 --watchレビュー指摘と対応
codex-review:
LOW/90 electron/main/utils/win32-fs-semantics.test.ts:35 — beforeEach の makeCanonicalTempDir が失敗した場合も afterEach が未初期化の dir を fsp.rm に渡し、元のセットアップエラーを別の引数エラーで覆い隠す → CONFIRMED+FIXED (ad3610c)code-reviewer / simplify(fix したもの):
createFileSymlinkhelper に集約(a7c84bf)git.test.tsの先例に合わせmaxRetries: 5, retryDelay: 100(a7c84bf)、さらに mkdtemp 失敗回の二次エラーを避けるためdirsToCleanup配列へ(ad3610c)writeFileAtomicNoFollowの rename / O_EXCL semantics が win32 未実測追跡先
backlog delta: 起票 1 件(#504)/ 本 PR で close 1 件(#500)/ 現在 open 16 件
起票理由: 実測で
open-nofollow.tsのセキュリティ根拠が win32 の dangling symlink に対して偽と判明した。probe の期待値を実測値へ書き換える以上、production 側の判断(受容 /lstat追加)を追跡先なしに残すと、事実だけがテストに固定されて判断が保留のまま定着する。--ignore-scripts案は allowBuilds の esbuild と katex 生成を巻き込み Windows CI でしか検証できない)Draft 判定