From eeb188a82d52aadb5a9a5e0615fc9eb2741a78b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 14 Jul 2026 20:17:51 +0200 Subject: [PATCH] =?UTF-8?q?test(gap):=20de-flake=20test=5Fgap=5Fnode=5Ffs?= =?UTF-8?q?=20=E2=80=94=20its=20temp=20dir=20was=20not=20unique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test built its scratch directory by hand: const tmpDir = '/tmp/perry_fs_test_' + Date.now(); fs.mkdirSync(tmpDir, { recursive: true }); Date.now() has millisecond resolution, so two executions that start in the same millisecond pick the SAME directory — and the test ends by tearing its directory down with a recursive rmSync. One run then touches a file another run is deleting. On Linux CI that surfaced as FAIL test_gap_node_fs (output mismatch) Node.js: true Perry: Error: EACCES: Permission denied (os error 13), stat '/tmp/perry_fs_test_1784051019891/test.txt' which is not a parity failure at all. It red-lit unrelated PRs at random — #6409 (an HIR capture-box change, nothing to do with fs) failed one run and passed the next on the SAME commit, and #6403 failed once then went green untouched. mkdtempSync is the primitive for this: the kernel picks the suffix and creates the directory atomically, so no two runs can collide. Proof, 8 concurrent runs of the compiled binary: before — 2 of 8 fail (ENOENT: another run's rmSync removed the shared dir) after — 0 of 8 fail Output is byte-for-byte unchanged (30 lines); no assertion touched. --- test-files/test_gap_node_fs.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test-files/test_gap_node_fs.ts b/test-files/test_gap_node_fs.ts index d1b3e1774f..6b53f8db3f 100644 --- a/test-files/test_gap_node_fs.ts +++ b/test-files/test_gap_node_fs.ts @@ -3,9 +3,20 @@ import * as fs from 'fs'; import * as path from 'path'; - -const tmpDir = '/tmp/perry_fs_test_' + Date.now(); -fs.mkdirSync(tmpDir, { recursive: true }); +import * as os from 'os'; + +// `'/tmp/perry_fs_test_' + Date.now()` is not a unique path: Date.now() has +// millisecond resolution, so two executions that start in the same millisecond +// pick the SAME directory — and this test ends by tearing its directory down +// with a recursive rmSync. One run then stats a file another run is deleting, +// which surfaced on Linux CI as +// EACCES: Permission denied, stat '/tmp/perry_fs_test_/test.txt' +// and red-lit unrelated PRs at random (it failed one #6409 run and passed the +// next, on the same commit). +// +// mkdtempSync is the primitive for this: the kernel picks the suffix and +// creates the directory atomically, so no two runs can ever collide. +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'perry_fs_test_')); const testFile = path.join(tmpDir, 'test.txt'); const testContent = 'hello perry fs';