Skip to content

test(gap): de-flake test_gap_node_fs — its temp dir was not unique#6410

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/node-fs-gap-temp-dir-race
Jul 14, 2026
Merged

test(gap): de-flake test_gap_node_fs — its temp dir was not unique#6410
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/node-fs-gap-temp-dir-race

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

test_gap_node_fs is flaky, and like test_gap_fs_fd_2749 (#6400) it has been red-lighting unrelated PRs at random. This is the second one from the same family.

It isn't a parity failure

The CI diff looks alarming but says nothing about parity:

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'

That's a filesystem permission error, not a wrong answer.

The temp directory is not unique

const tmpDir = '/tmp/perry_fs_test_' + Date.now();
fs.mkdirSync(tmpDir, { recursive: true });

Date.now() has millisecond resolution, so two executions starting in the same millisecond pick the same directory — and the test ends by tearing its directory down with a recursive rmSync. One run then stats a file another run is deleting.

Evidence it's a race, not a regression

Running 8 copies of the compiled binary concurrently:

failures
before (Date.now() path) 2 of 8ENOENT, another run's rmSync removed the shared dir
after (mkdtempSync) 0 of 8

The fix

mkdtempSync is the primitive for exactly this: the kernel picks the suffix and creates the directory atomically, so no two runs can collide.

Output is byte-for-byte unchanged (all 30 lines); no assertion is touched — only the directory's construction.

Summary by CodeRabbit

  • Tests
    • Improved temporary directory setup for filesystem parity tests.
    • Tests now use unique system-generated temporary directories, reducing potential conflicts during execution.

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 — PerryTS#6409
(an HIR capture-box change, nothing to do with fs) failed one run and passed the
next on the SAME commit, and PerryTS#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.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1314a35d-2e0d-4a7a-ac3e-180f51e0ba02

📥 Commits

Reviewing files that changed from the base of the PR and between 3727c7b and eeb188a.

📒 Files selected for processing (1)
  • test-files/test_gap_node_fs.ts

📝 Walkthrough

Walkthrough

The filesystem parity test now creates its temporary directory atomically under the system temporary directory using fs.mkdtempSync, replacing timestamp-based path construction and explicit directory creation.

Changes

Filesystem Test Setup

Layer / File(s) Summary
Atomic temporary directory allocation
test-files/test_gap_node_fs.ts
The test uses os.tmpdir() and fs.mkdtempSync to create a unique temporary directory while preserving the existing filesystem assertions and cleanup flow.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: fixing flaky temp-dir creation in test_gap_node_fs.
Description check ✅ Passed The description covers the bug, fix, and validation, but it doesn't explicitly use the template's Summary, Changes, Related issue, and Test plan sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Follow-up: the same hazard is latent in three more gap tests. They use a fixed /tmp path with no uniqueness at all, which is strictly worse than Date.now() — any two concurrent executions collide, not just ones landing in the same millisecond:

test path
test_gap_fs_fd_2749.ts /tmp/perry_fs_fd_2749
test_gap_fs_errprop_2735plus.ts /tmp/perry_fs_errprop_2735
test_gap_fs_errprop2_2745plus.ts /tmp/perry_fs_errprop2_2745
test_gap_3662_node_argvalidation.ts /tmp/perry-3662-x

(test_gap_fs_fd_2749 is the one I de-flaked in #6400 — that fixed its ordering race; the fixed path is a separate, still-open hazard in the same file.)

They haven't bitten yet, so I've left them out of this PR rather than expand its scope. Happy to convert them to mkdtempSync in a follow-up if you want the class closed.

One honest caveat on the mechanism: what I proved is that the old path collides under concurrency (2 of 8 simultaneous runs fail, 0 of 8 after). I did not establish exactly where CI's concurrency comes from — whether the harness overlaps the node and perry executions, or something else on the runner. Either way the root cause is the same (a non-unique path plus a recursive teardown), and mkdtempSync removes it by construction.

@proggeramlug proggeramlug merged commit 69cf6e3 into PerryTS:main Jul 14, 2026
48 of 50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant