Skip to content

fix(test): make the results/ tests hermetic so npm test stops failing - #54

Merged
jobordu merged 3 commits into
mainfrom
fix/hermetic-results-dir
Aug 7, 2026
Merged

jobordu merged 3 commits into
mainfrom
fix/hermetic-results-dir

Conversation

@jobordu

@jobordu jobordu commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

npm test fails on every run — exit 1, reproducible on a clean main — with ENOTEMPTY/ENOENT against the repo's real results/ directory. It also deletes results/.gitkeep as a side effect.

Cause

Six tests across four adversarial files each do the same dance on the real results/ directory:

  1. copy it to a temp backup
  2. fs.rmSync(results, { recursive: true }) — delete the shared directory
  3. put a temp dir or symlink in its place
  4. finally: remove that, copy the backup back

The node test runner runs files in parallel, so those six race on one shared global. File A's rmSync lands on file B's half-restored tree (ENOTEMPTY); B's cpSync runs after A has already deleted it (ENOENT). Which test reports the error is luck — which is why this reads as flake rather than as a design problem.

Fix

lib/runner.cjs resolves the results directory through resultsDir(), honoring NF_BENCH_RESULTS_DIR and defaulting to <repo>/results exactly as before. It's resolved per call, not cached at import, so a test can set the variable after the module is already loaded.

All six sites now point that variable at a directory inside their own temp dir and restore the previous value in finally. Nothing touches the shared directory at all, so there is nothing left to race on.

Verification

result
3 consecutive full npm test runs with the fix exit 0, no ENOENT/ENOTEMPTY
2 runs with the fix stashed, same tree exit 1, both

results/.gitkeep survives a full run now.

Noted, not fixed

BUG 11 in adversarial4 does the identical backup/delete/restore dance on challenges/. Same class, same shared global — it just hasn't been unlucky yet. Worth the same treatment if loadAllChallenges grows an injection point.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Result files can now be stored in a custom directory using the NF_BENCH_RESULTS_DIR environment variable.
    • Results continue to use the repository’s default results directory when no custom location is configured.
  • Bug Fixes

    • Improved handling of result-directory creation, loading, and JSON parsing.
  • Tests

    • Updated result-directory tests to use isolated temporary locations without modifying repository data.

`npm test` failed on every run — exit 1, reproducible on a clean main — with
ENOTEMPTY/ENOENT against the repo's real results/ directory, and it deleted
results/.gitkeep as a side effect.

Cause: six tests across four adversarial files each backed up the REAL results/
dir, deleted it, put a temp dir (or symlink) in its place, then restored it in a
finally block. The node test runner runs FILES IN PARALLEL, so those six raced on
one shared global — file A's rmSync landed on file B's half-restored tree
(ENOTEMPTY), or B's cpSync ran after A had already deleted it (ENOENT). Which
test reported the error was luck, which is why it read as flake rather than as a
design problem.

lib/runner.cjs now resolves the results directory through resultsDir(), honoring
NF_BENCH_RESULTS_DIR and defaulting to <repo>/results exactly as before. It is
resolved per call rather than cached at import, so a test can set the variable
after the module is loaded. All six sites set that variable to a directory inside
their own temp dir and restore the previous value in finally — nothing touches
the shared directory at all now.

Verified: 3/3 consecutive full `npm test` runs pass (exit 0) with the fix;
2/2 fail (exit 1) with it stashed on the same tree. results/.gitkeep survives.

Note for later: BUG 11 in adversarial4 does the same backup/delete/restore dance
on challenges/ — same class, same shared global, not yet triggered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jobordu, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57ff2664-16df-4dfb-816f-dac09faf0f0d

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba77c4 and f8a8aef.

📒 Files selected for processing (7)
  • bin/compute-improvement.cjs
  • bin/nf-benchmark.cjs
  • lib/challenges.cjs
  • test/adversarial.test.cjs
  • test/adversarial2.test.cjs
  • test/adversarial3.test.cjs
  • test/adversarial4.test.cjs
📝 Walkthrough

Walkthrough

The runner now resolves the results directory from NF_BENCH_RESULTS_DIR at runtime, with a repository-directory fallback. Result tests use temporary directories and restore the environment variable after execution.

Changes

Dynamic results directory

Layer / File(s) Summary
Dynamic resolution and storage
lib/runner.cjs
resultsDir() reads NF_BENCH_RESULTS_DIR on each call. Result saving and loading use the resolved directory. The export changes from RESULTS_DIR to resultsDir.
Isolated result tests
test/adversarial*.test.cjs
Result-loading, saving, and concurrent-save tests use temporary directories through NF_BENCH_RESULTS_DIR. Cleanup restores or removes the previous environment value.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: making results-directory tests hermetic to prevent npm test failures.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hermetic-results-dir

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/runner.cjs`:
- Around line 8-17: Update compute-improvement’s result-directory lookup to use
the exported resultsDir() resolver instead of its fixed RESULTS_DIR constant,
ensuring NF_BENCH_RESULTS_DIR controls both saving and reading results. If
resultsDir() is not currently accessible there, move it to a shared module and
reuse it from both paths.

In `@test/adversarial.test.cjs`:
- Around line 212-222: Protect environment restoration by moving each test’s
filesystem setup into the try block that follows NF_BENCH_RESULTS_DIR
assignment: update test/adversarial.test.cjs lines 212-222 for directory and
malformed-file setup, test/adversarial2.test.cjs lines 77-86 for directory and
empty-file setup, and test/adversarial3.test.cjs lines 61-72 plus
test/adversarial4.test.cjs lines 75-85 for fixture copying; keep the existing
finally restoration logic unchanged.

In `@test/adversarial3.test.cjs`:
- Around line 59-63: Capture the fixture source by calling resultsDir() before
overriding NF_BENCH_RESULTS_DIR, then use the stored path as the fs.cpSync
source; apply this change at test/adversarial3.test.cjs lines 59-63 and
test/adversarial4.test.cjs lines 73-77.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 36d10662-4600-46fa-98ee-3a320750c887

📥 Commits

Reviewing files that changed from the base of the PR and between ad589d2 and 7ba77c4.

📒 Files selected for processing (5)
  • lib/runner.cjs
  • test/adversarial.test.cjs
  • test/adversarial2.test.cjs
  • test/adversarial3.test.cjs
  • test/adversarial4.test.cjs

Comment thread lib/runner.cjs Resolved
Comment thread test/adversarial.test.cjs Resolved
Comment thread test/adversarial3.test.cjs Resolved
open-swe and others added 2 commits August 7, 2026 09:21
…lts/)

BUG 11 in adversarial4 did the identical backup/delete/repopulate/restore dance on
the REAL challenges/ directory that the six results/ sites did — the same shared
global under the same parallel runner. It hadn't been unlucky yet; that is the
only difference.

lib/challenges.cjs now resolves through challengesDir(), honoring
NF_BENCH_CHALLENGES_DIR and defaulting to <repo>/challenges as before, resolved
per call rather than cached at import. The test redirects the env var at its own
temp fixture tree and restores the previous value in finally.

Flagged in the PR body as noted-not-fixed; fixing it now instead, since leaving a
known race armed to catch someone later is worse than the ten lines it costs.

3/3 consecutive full `npm test` runs pass; results/.gitkeep survives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…de try

Two valid findings on the hermetic-results change, plus a naming cleanup:

- bin/compute-improvement.cjs and bin/nf-benchmark.cjs each carried their own
  `path.join(__dirname, '..', 'results')`. With NF_BENCH_RESULTS_DIR set, results
  were WRITTEN to the redirected dir and READ from the fixed one. Both now import
  resultsDir() from lib/runner.cjs, so one env var means one directory everywhere.

- All six sites set NF_BENCH_RESULTS_DIR before entering `try`, so a throw during
  fixture setup would skip `finally` and leak the override into every later test
  in that file (process.env is global). The assignment and the seeding now happen
  inside the protected block.

The third finding — that `fs.cpSync(resultsDir, ...)` passes a function and
throws ERR_INVALID_ARG_TYPE — is a false positive: `resultsDir` there is a local
`const resultsDir = path.join(tmp, 'results')` fixture path, and those files do
not import the export (see the destructure at adversarial3:11). The suite passes,
which it could not if a function were reaching cpSync. The concern about the NAME
is fair though, so the locals are now `fixtureResultsDir` — nothing shadows the
resolver even if a future edit imports it.

Full `npm test` green across 3 consecutive runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jobordu
jobordu merged commit 8f604ee into main Aug 7, 2026
8 checks passed
@jobordu
jobordu deleted the fix/hermetic-results-dir branch August 7, 2026 13:38
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.

2 participants