Skip to content

bun-audit.test.ts: install each project once and run the report cases concurrently - #39034

Closed
robobun wants to merge 5 commits into
mainfrom
farm/1eaa7ab0/speed-up-bun-audit-test
Closed

robobun wants to merge 5 commits into
mainfrom
farm/1eaa7ab0/speed-up-bun-audit-test

Conversation

@robobun

@robobun robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • test/cli/install/bun-audit.test.ts is slow on the ASAN lane. Measured from the job log timestamps, the file's block took 23.2s, 21.8s and 20.4s on debian 13 x64-asan in three builds of unmodified code (#97275, #98555, #98543; the handoff that prompted this quoted 28s); the file sits 3rd to 9th in its shard on every lane, so 4 to 10s of each block is spent before the first test finishes, while the runner is still bringing up its service containers. The other lanes spend 2 to 10s actually running its tests (ubuntu x64 about 2s, windows 2019 x64 about 4.5s, windows 11 aarch64 about 10s). Under ASAN every spawned bun is the expensive unit.
  • The file spawned 512 bun processes, 256 of them bun install: 147 setup() calls installed only 63 distinct projects, and setupVulnerableADep installed the same two package.jsons for each of its 28 callers.
  • The 22 doAuditTest cases and the 3 tests next to them were plain test(), so they ran one at a time, while bun test runs the file's other tests 5 wide (the ASAN default for --max-concurrency, src/options_types/context.rs:506; 20 elsewhere). A serial test costs as much wall time as 5 concurrent ones.
  • Several of those cases only checked a substring or the exit code (not.toContain("error"), toContain("vulnerabilities")), and 5 relied on file snapshots.

Fix

  • setup() installs each distinct single-package.json project once (installedProject, memoized by package.json text, installed straight from verdaccio) and gives every test its own copy through tempDir(prefix, dir), the same copy the static fixtures use. The copy gets its bun.lock tarball URLs re-pointed at the test's registry and a fresh bunfig; setupVulnerableADep is one two-step template. Workspace and scoped-registry projects (21 calls, 14 distinct shapes) still install per test.
  • Why a copy is as good as an install: bun.lock is the only file naming the registry, and the template's bunfig and cache are removed, so a copy talks only to its test's registry. The new test setup() > a copied project is what installing through the test's registry produces reads every file of a copy and of a fresh install through the same proxy registry and requires the two sets to be equal. Installs never hit the bulk endpoint, so bulkHits and bulkBodies assertions mean what they did before.
  • The one thing a copy lacks is a warm install cache, and three tests depended on it: the denyManifests tests only prove that audit fix fetches manifests afresh (it turns the manifest cache off, src/install/audit_fix.rs) while the cache holds a manifest that would have answered. Those three now go through setupWithCachedManifests, which installs through the test's own registry and hands the project over only once .bun-cache holds one .npm manifest per dependency. bun install writes those files from its thread pool without waiting for them at exit (save_async in src/install/npm.rs), so an install occasionally ends without them (asserting on them flaked once on Windows in build #98329); a project that came out cold is thrown away and installed again, at most a few times. Checked by deleting the two enable.set(..., false) lines in audit_fix.rs: main's versions of the three tests fail, the copy-based versions from the first revision of this PR pass (the coverage gap review caught), and the current versions fail again, with audit fix upgrading a-dep from the stale cached manifest. The denyTarballs test is unaffected: the tarball it denies was never in the cache.
  • doAuditTest registers test.concurrent and starts a fixture registry per case that records the request bodies; the two scoped-registry tests and the double-quote test are concurrent too. The tests share nothing writable: verdaccio is read only, and each test has its own directory, cache and registry.
  • Every doAuditTest case now asserts the full normalized stdout and stderr (inline snapshots for the express@3 report, the --audit-level critical report and the two error cases; MS_REPORT for the four cases that print the ms report) and the exact bulk request bodies: [] where the registry must not be contacted (no package.json, no lockfile, invalid --audit-level), the exact name/version map otherwise (the mix fixture proves is-number is sent and not printed, the workspace case proves the workspace package is not sent, the scoped-registry cases prove the default registry is asked about nothing). The --json cases compare the parsed document against the fixture response (MS_ADVISORIES) with one toEqual. __snapshots__/bun-audit.test.ts.snap is deleted; nothing uses toMatchSnapshot any more (it is also rejected inside concurrent tests).
  • Spawns, counted with a preload that wraps Bun.spawn: 512 -> 416 bun processes, bun install 256 -> 160 (one more per cold-cache retry, which is rare). What is left: ~50 template installs, 21 workspace/scoped setups, 28 second-step reinstall()s (almost all distinct), the 6 warm-cache installs above, ~40 --frozen-lockfile installs that check the lockfile audit fix wrote (kept on purpose), and the few tests that run bun install themselves to pass linker flags. bun audit (96) and bun audit fix (160) spawns are unchanged: every repeated invocation checks a different flag, mode or cwd, so none was dropped. The expect() count goes 2993 -> 2360 because runBunInstall makes about six calls per install.
  • The file set no per-test timeouts, so there was nothing to remove there.
  • CI, same measurement, split at the first progress dot into the startup window (unchanged by this PR and dominated by the shard starting up) and the part that runs the tests. Tests-running, builds of unmodified code (#98555, #98543, and #98020 where it has the lane) -> this PR's builds #98061 and #98510: debian 13 x64-asan 15.3s / 14.7s -> 11.8s / 11.1s; windows 11 aarch64 9.6 to 10.4s -> 8.8s / 8.6s; ubuntu x64 1.8 to 2.5s -> 1.9s / 2.3s; windows 2019 x64 4.3 to 4.6s -> 4.7s / 5.7s (two samples on a lane whose startup window alone swings 4.5 to 7.6s between builds). Whole blocks: asan 20.4 to 23.2s -> 19.4s / 18.0s; on the other lanes the block totals move with the startup window, and this PR's builds also run the file one slot earlier because the runner puts modified files first, so those totals say nothing either way.
  • Verified with bun bd test --timeout=90000 test/cli/install/bun-audit.test.ts (the flag mirrors the runner's ASAN per-test timeout): 183 pass, 182 before plus the new one.
Local before/after runs

Release build of this branch, whole file, interleaved rounds (bun test runs it 20 wide there): Linux, 12 cores, load average about 38: 2.91 / 3.69 / 4.03 / 3.45s -> 2.86 / 3.05 / 3.65 / 3.53s. Windows Server 2019 x64, 16 logical CPUs, with the canary on PATH: 3.00 / 2.97 / 3.00s -> 2.65 / 2.67 / 2.66s (182 -> 183 tests passing in every run). Copying a template costs 0.7ms per project in a release build.

Debug + ASAN build (bun bd, 5 wide like the CI lane, but the copies cost about 30ms each there and the box's load average was 60 to 250, so wall time mostly measures the host): CPU of the whole process tree over six interleaved pairs 155 / 172 / 175 / 145 / 156 / 159s -> 146 / 139 / 146 / 144 / 127 / 138s; with --max-concurrency=20 the two valid pairs were 28.6s -> 23.1s and 30.3s -> 30.7s wall, 126 / 138s -> 105 / 113s CPU.

Background

  • bun audit posts { name: [installed versions] } to the registry's bulk advisory endpoint and prints what comes back; bun audit fix also fetches manifests, rewrites bun.lock (and pins in package.json) and runs an install. Each test in this file therefore needs an installed project plus a registry, which is a Bun.serve proxy in front of one verdaccio shared by the whole file; the proxy answers the bulk endpoint itself.
  • For any registry other than npm's, bun.lock stores each package's full tarball URL, so a project installed from one local registry has to have bun.lock rewritten before another local registry can serve it. That rewrite is what copyProject does and what the new test pins down.
  • The install cache also stores fetched manifests, as <name hash>-<registry URL hash>.npm files (src/install/npm.rs, manifest_file_name). The registry hash is why copying a template's cache could not have stood in for a warm cache either: a copy uses a different registry URL, so its lookups would never hit the template's files. audit fix disables this cache so that a fix is planned from what the registry says now.
  • audit-fixtures.json holds the real npm responses for the four checked-in projects under registry/fixtures/audit, keyed by request body; startFixtureRegistry serves those, which is why the --json cases can assert against the fixture response directly.

[stamp-90s] gate passed · iteration 2 · 2 files touched

passes on PR (with fix)
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/cli/install/bun-audit.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/cli/install/bun-audit.test.ts
bun test v1.4.0 (55b32d4e3)

test/cli/install/bun-audit.test.ts:
(pass) `bun audit` > should fail with no package.json [288.43ms]
(pass) `bun audit` > should fail with package.json but no lockfile [278.73ms]
(pass) `bun audit` > should exit 0 when there are no dependencies in package.json [336.56ms]
(pass) `bun audit` > should exit 0 when there are no vulnerabilities [554.51ms]
(pass) `bun audit` > should exit code 1 when there are vulnerabilities [473.18ms]
(pass) `bun audit` > --audit-level that drops every advisory says how many it dropped [509.94ms]
(pass) `bun audit` > --ignore that drops every advisory says how many it ignored [463.98ms]
(pass) `bun audit` > --audit-level and --ignore drops are listed separately [289.95ms]
(pass) `bun audit` > should print valid JSON and exit 0 when --json is passed and there are no vulnerabilities [272.11ms]
(pass) `bun audit` > --json still exits 1 when an advisory survives --audit-level and --ignore [172.04ms]
(pass) `bun audit` > should print valid JSON and exit 1 when --json is passed and there are vulnerabilities [537.80ms]
(pass) `bun audit` > --json exits 0 when --audit-level filters out every advisory, but still prints them all [542.05ms]
(pass) `bun audit` > --json exits 0 when --ignore covers every advisory, but still prints them all [550.23ms]
(pass) setup() > a copied project is what installing through the test's registry produces [1669.82ms]
(pass) `bun audit` > should exit 1 and behave exactly the same when there are vulnerabilities when only devDependencies are specified [401.98ms]
(pass) `bun audit` > when a project has some safe dependencies and some vulnerable dependencies, we should not print the safe dependencies [267.40ms]
(pass) `bun audit` > packages served by a scoped registry are audited against that registry [318.50ms]
(pass) `bun audit` > packages whose scoped registry does n
... (truncated)
Exit: 0
diff hotspot
.../install/__snapshots__/bun-audit.test.ts.snap   | 436 ----------------
 test/cli/install/bun-audit.test.ts                 | 571 +++++++++++++++------
 2 files changed, 423 insertions(+), 584 deletions(-)

gate history · 3 passed · 0 rejected · iteration 2

evidence per changed file
file                                                   reads  edits  tests
test/cli/install/__snapshots__/bun-audit.test.ts.snap      1      0      0
test/cli/install/bun-audit.test.ts                        22     16      0

… concurrently

setup() installs every distinct single-package.json project once, from
verdaccio, and hands each test a copy with bun.lock re-pointed at the
test's registry; setupVulnerableADep becomes one two-step template. A new
test checks that such a copy is identical to installing through the
test's registry. doAuditTest registers concurrent tests against a
per-case fixture registry that records the bulk request bodies, and
every case asserts the full stdout, stderr and request bodies instead of
substrings and file snapshots.

bun processes spawned by the file: 512 -> 411 (bun install 256 -> 155).
@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 41 seconds

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6e14b843-4d83-4845-b84b-d1abcc94bb7e

📥 Commits

Reviewing files that changed from the base of the PR and between 88a6398 and d4fcd06.

⛔ Files ignored due to path filters (1)
  • test/cli/install/__snapshots__/bun-audit.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • test/cli/install/bun-audit.test.ts

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

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: test-only change, ready for review (5 commits; the last four are review follow-ups: warm cache for the manifest-404 tests, retry when the cache write loses the race with exit, templates dir created before verdaccio starts, dispose-before-assert in that retry).

  • Measured how: a --preload wrapping Bun.spawn counted the bun processes the file starts (512 before, 416 after; bun install 256 -> 160). CI job logs, split at the first progress dot: the tests-running part of the file on debian 13 x64-asan is 15.3s / 14.7s in unmodified builds and 11.8s / 11.1s in this PR's builds; whole blocks 20.4 to 23.2s -> 18.0 / 19.4s. Release builds of this branch run the whole file 3.5s -> 3.3s on Linux and 3.0s -> 2.7s on Windows Server 2019 (interleaved rounds, table in the description).
  • Verified how: bun bd test --timeout=90000 test/cli/install/bun-audit.test.ts, 183 pass (182 before plus the new setup() test that diffs a copied project against a fresh install); the file has passed on every lane of builds #98061, #98329, #98510 and #98615 except the one Windows flake that commit 43470e2 addresses. With the manifest-cache bypass deleted from audit_fix.rs locally, the three manifest-404 tests fail, as main's versions do.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. It's a substantial test-infrastructure refactor (new memoized template-install layer shared across ~150 concurrent tests, assertion semantics changed for ~20 cases, snapshot file removed), so a human look would still be worthwhile.

What was reviewed:

  • installedProject memoization: the get→size-read→set path has no await, so concurrent callers with the same or different keys can't race on the map or collide on template subdirectory indices.
  • copyProject/setup() isolation: template's bunfig and cache are removed before copy, bun.lock URLs are rewritten to the per-test registry, and each copy gets its own BUN_INSTALL_CACHE_DIR — copies share no writable state; the new equivalence test pins this.
  • Assertion changes: file snapshots replaced with inline snapshots / toEqual(resolveBulkAdvisoryFixture(...)); every case now also asserts stderr and the exact bulk request bodies — checked that no assertion was silently weakened.
Extended reasoning...

Overview

Test-only change to test/cli/install/bun-audit.test.ts (+250/−100) plus deletion of the 436-line .snap file. Introduces installedProject() (memoized per-package.json-sequence installs into a shared templates tempDir, installed once from verdaccio) and copyProject() (per-test copy with bun.lock URL rewrite + fresh bunfig). setup() now routes single-package.json projects through the copy path and only workspace/scoped projects through a real per-test install. doAuditTest becomes test.concurrent with a per-test fixture registry that records request bodies; three neighboring tests are also made concurrent. File snapshots are replaced with inline snapshots and MS_REPORT, and every case now asserts stderr and the exact bulk request payload. setupVulnerableADep becomes a two-step template + copy. A new test proves a copied project is byte-identical to a fresh install through the same proxy registry.

Security risks

None. Test-only; no runtime, auth, or network-facing code touched. The registries are local Bun.serve({ port: 0 }) proxies in front of a local verdaccio.

Level of scrutiny

Medium. Nothing here ships to users, but the change rewires how ~150 tests obtain their fixtures and moves ~25 tests from serial to concurrent execution against a shared verdaccio and a shared installedProjects map. I traced the memoization for concurrency safety (no await between get, installedProjects.size read, and set, so it's atomic within the JS event loop) and the copy path for isolation (bunfig/cache stripped from templates, bun.lock rewritten, fresh bunfig + BUN_INSTALL_CACHE_DIR per copy). Both look correct. The assertion rewrites are strictly stronger (full stdout/stderr + request bodies vs. the old toContain/substring checks), and I didn't find any case where a load-bearing property of the old assertion was dropped.

Other factors

The PR description is thorough and the author added a self-test for the copy invariant, which is good. That said, this is a moderately large refactor with a new caching pattern and mixed wall-time numbers by the author's own measurement (some paired runs got slower under load). The design choice of copy-and-rewrite-lockfile vs. per-test install, the switch to test.concurrent for these cases, and whether the --json assertions now proving "output equals fixture response" instead of specific severity/count checks are the right shape are all judgment calls a maintainer should sign off on rather than an automated approval.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. make more tests faster #33704 - Rewrites the same doAuditTest helper in test/cli/install/bun-audit.test.ts to register test.concurrent for the same CI-speedup purpose (opting the snapshot cases out instead of converting them to inline snapshots).

🤖 Generated with Claude Code

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

On the duplicate flag: #33704 is a 53-file concurrency sweep whose change to this file is limited to doAuditTest registering test.concurrent, with the snapshot cases opting back out (toMatchSnapshot is rejected inside concurrent tests). This PR covers that part differently (the snapshot cases become exact inline assertions, so all of them run concurrently) and the bulk of it is elsewhere: the shared install templates, which are where most of the spawns went, the request-body assertions, and the removal of the snapshot file. If #33704 lands first the overlap is a few lines in doAuditTest and I will rebase onto it.

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 2:48 PM PT - Aug 15th, 2026

✅ @robobun, your commit d4fcd069c987d621bbd2096cf9d40549682a88de passed in Build #98633! 🎉


🧪   To try this PR locally:

bunx bun-pr 39034

That installs a local version of the PR into your bun-39034 executable, so you can run:

bun-39034 --bun

audit fix turns the manifest cache off before it looks packages up, and the
three tests that make the registry stop serving a manifest only prove that
while the cache holds one; they now install through their own registry and
check the cache before breaking it, instead of starting from a copied
project whose cache is empty.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. Because it's a substantial test-infrastructure refactor (memoized install templates with bun.lock rewriting, cold-cache copies replacing warm-cache installs, snapshot → inline assertion migration), a human look would still be worthwhile.

What was reviewed:

  • installedProject memoization is race-free (synchronous check-then-set before any await; installedProjects.size read and map insert happen in the same tick).
  • The three denyManifests tests that relied on a warm manifest cache were correctly migrated to setupWithCachedManifests; the denyTarballs test is unaffected since the denied tarball (1.0.4) was never cached before either.
  • Assertions were strengthened, not weakened — the deleted .snap content is fully covered by the new inline snapshots and MS_REPORT, plus new exact request-body checks.
Extended reasoning...

Overview

This PR refactors test/cli/install/bun-audit.test.ts (~400 lines changed) and deletes its 436-line .snap file. It introduces installedProject() to memoize installs of distinct single-package.json projects (installed once from verdaccio, then copied per-test with bun.lock URLs rewritten to the test's registry), converts doAuditTest and three neighbouring tests to test.concurrent with a per-test fixture registry that records request bodies, replaces all toMatchSnapshot calls with inline snapshots or exact toBe/toEqual, and adds setupWithCachedManifests for the three tests that need a warm manifest cache. No production code is touched.

Security risks

None. Test-only; no auth, crypto, or user-facing surface. All servers are local Bun.serve({ port: 0 }) proxies over the file's own verdaccio instance.

Level of scrutiny

Moderate. While test-only, the change alters what setup() produces: previously every test's directory had a warm .bun-cache from a real install; now most tests get a copied directory with no cache. This is a semantic difference that could silently weaken tests whose point was "audit fix ignores the cache" — the author identified the three denyManifests tests and moved them to setupWithCachedManifests (which asserts the manifest count in .bun-cache), and I checked that the remaining denyTarballs case is unaffected. The copy-equals-install assumption is pinned by a new test that diffs every file of a copy against a fresh install.

Other factors

  • The installedProject map uses installedProjects.size as the directory index and sets the map entry synchronously before awaiting, so concurrent callers with different keys cannot collide on the same index and same-key callers share one promise.
  • join(templates, ...) where templates is a DisposableString (boxed String) works in Bun's path.join — verified, and the pattern is already used throughout the file with tempDir results.
  • Assertion coverage increased: every doAuditTest case now checks the exact bulk request body (including [] for the no-contact cases) and full stderr; the deleted snapshot content is byte-for-byte reproduced in the express@3 inline snapshot and MS_REPORT. The --json cases now assert equality against the fixture response object rather than a stored snapshot.
  • The local timing numbers in the description are mixed (some pairs slower), which the author attributes to host load; the spawn-count reduction (512→411, installs 256→155) is the concrete metric. A maintainer should confirm the CI ASAN lane actually improves.
  • There's a flagged overlap with PR #33704; the author has stated a rebase plan if that lands first.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

On the open question about the ASAN lane: measured from the job log timestamps around the file (header line to ::endgroup::), bun-audit.test.ts took 23.2s on debian 13 x64-asan in the baseline build #97275 and 19.4s in this PR's build #98061; the description now carries those numbers and moves the noisy local wall-clock figures into a details block.

The second commit is a coverage fix that came out of review: the three denyManifests tests only pin audit fix's manifest-cache bypass while their cache is warm, and copied projects start cold. They now install through their own registry and check the cache before breaking it. I confirmed the point by deleting the two enable.set(..., false) lines in audit_fix.rs: main's versions of the three tests fail, the first revision's copy-based versions pass, and the current versions fail again.

…ace with exit

bun install saves manifests from its thread pool without waiting for them
before exiting, so asserting that the cache is warm right after an install
flaked on Windows; install into a fresh directory again instead until the
files are there.
@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

One more commit (43470e2): in build #98329 the new warm-cache check failed once on Windows 2019 x64 and passed on retry. The cause is in src/install/npm.rs: save_async writes manifest cache files from the thread pool and deliberately does not hold the process open for them, so a bun install can exit before the .npm file lands. setupWithCachedManifests now installs into a fresh directory again when that happens (bounded, and verified locally by simulating a cold result once per test) instead of asserting that the file is there. No other failure in that build involved this file.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. Because it's a ~560-line refactor of the test setup infrastructure (memoized template installs + copy-and-rewrite in place of per-test installs, plus a retry loop for the manifest-cache write race), a human look would still be worthwhile to confirm the copy-based projects are equivalent to fresh installs for every way audit fix reads project state.

Checked: the removed module-level fixture server has no remaining references (each concurrent test now starts its own); installedProjects memoization is race-free (get/set is synchronous before any await); tempDir(prefix, path) supports the directory-copy overload used by copyProject; the bun.lock URL rewrite in copyProject matches the existing pattern in startRegistry's manifest proxy; the three denyManifests tests were correctly moved to setupWithCachedManifests so they still exercise the manifest-cache bypass.

Extended reasoning...

Overview

Test-only refactor of test/cli/install/bun-audit.test.ts (~560 lines changed) plus deletion of its 436-line .snap file. The change memoizes single-package.json project installs into a shared templates directory (installed once from verdaccio, then copied per test with bun.lock URLs rewritten to the test's registry), converts doAuditTest and three neighboring tests to test.concurrent with a per-test fixture registry, replaces five toMatchSnapshot assertions with inline snapshots / exact toBe/toEqual comparisons, and adds request-body assertions to every doAuditTest case. Three denyManifests tests move to a new setupWithCachedManifests helper that installs through the test's own registry and retries until the async manifest-cache write lands, restoring the warm-cache precondition that copied projects lack. A new self-test diffs a copied project against a fresh install byte-for-byte.

Security risks

None. Test-only; no production code, no network egress (verdaccio + local Bun.serve proxies), no new dependencies.

Level of scrutiny

Moderate-to-high. It touches no shipped code, but it rewires how ~150 existing tests obtain their fixture project. The repo's review rules are explicit that weakening or subtly changing what an existing test asserts is a merge blocker, and the first revision of this PR did exactly that for the three manifest-404 tests (fixed in the second commit after review). The author's verification is unusually thorough — spawn counts, CI wall-clock, mutation testing against audit_fix.rs, and a byte-level equivalence test — so I have reasonable confidence, but the copy-vs-install equivalence is the kind of assumption a maintainer who owns bun audit should sign off on.

Other factors

  • The installedProjects map is module-global mutable state shared across concurrent tests; it's safe because the get→set happens synchronously before the first await, so concurrent callers with the same key share one promise.
  • setupWithCachedManifests has a bounded retry (expect(attempt).toBeLessThan(5)) around a documented race in src/install/npm.rs (save_async); this is a poll-for-condition, not a sleep, and disposes the failed dir between attempts.
  • Assertions are strictly strengthened everywhere I checked (substring → exact stdout/stderr, added requests body checks, added stderr = "" checks). The one place coverage could have regressed (warm cache) was caught and fixed.
  • No prior claude[bot] review on this PR; CI for the latest commit is still building (#98510).
  • ~560 lines of non-mechanical test-infrastructure change is beyond what I'd approve without a human look.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Build #98510 (the current revision, with the retry) has bun-audit.test.ts at 18.0s on debian 13 x64-asan, measured the same way as the 23.2s baseline and the 19.4s of the first revision. The non-ASAN lanes are single-shard samples that move around by a few seconds between builds of the same code, so I am collecting a few more builds before putting a per-lane table in the description; the ASAN lane is the one this change was aimed at.

…daccio

If verdaccio fails to start, afterAll otherwise adds a TypeError about the
unset templates handle on top of the real failure.
Comment thread test/cli/install/bun-audit.test.ts
@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Measurements are now in the description in a form that separates what this PR changes from what it does not. Each lane's block for this file starts 3rd to 9th in its shard, while the runner is still starting its service containers, so 4 to 10s of every block passes before the first test finishes, and that window varies more between builds than the tests themselves take on the fast lanes (it is also why this PR's builds, which run modified files first, looked slower on a couple of lanes when comparing whole blocks). Splitting at the first progress dot: the tests-running part on x64-asan goes from 15.3s / 14.7s (unmodified builds) to 11.8s / 11.1s here, windows 11 aarch64 from about 10s to 8.7s, and the ubuntu and windows 2019 lanes, where the tests take 2s and 4.5s, stay within their noise. A release build of this branch runs the whole file 3.5s -> 3.3s on Linux and 3.0s -> 2.7s on Windows Server 2019 in interleaved rounds, so there is no platform where the copies cost more than the installs they replace (a copy is about 0.7ms in a release build).

d4fcd06 takes the dispose-before-assert suggestion from the line comment.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for addressing the dispose-before-assert nit in setupWithCachedManifests (d4fcd06). I re-reviewed and found no further issues. Given the scope — new template/copy install memoization shared across ~150 setup calls, the concurrent conversion of 25 tests, and the wholesale snapshot→exact-assertion rewrite — a human pass on the infrastructure design would still be worthwhile.

What was reviewed:

  • installedProject/copyProject memoization: template dirs live under one tempDir disposed in afterAll; the map key is the full step list; bun.lock rewrite covers the only registry reference and the new self-test pins that.
  • Concurrency safety: each doAuditTest now spawns its own fixture registry and temp dir, so nothing writable is shared; verdaccio and the templates are read-only after install.
  • setupWithCachedManifests: the three denyManifests tests keep their warm-cache precondition; retry loop now disposes before the bound check.
  • Snapshot deletions: each removed toMatchSnapshot is replaced by an equal-or-stronger exact assertion (full stdout/stderr + request bodies), not weakened.
Extended reasoning...

Overview

Test-only refactor of test/cli/install/bun-audit.test.ts (~420 additions, ~580 deletions including the deleted .snap file). Three orthogonal changes: (1) setup() now memoizes single-package.json installs into a shared templates directory and hands each test a copy with bun.lock re-pointed at its own registry, cutting bun install spawns from 256 to ~160; (2) the 22 doAuditTest cases and 3 neighbors move from test() to test.concurrent, each with its own fixture registry; (3) five toMatchSnapshot calls become inline snapshots or exact toBe/toEqual assertions on full stdout/stderr and recorded request bodies, deleting the 436-line snapshot file. A new setupWithCachedManifests helper preserves the warm-cache precondition for the three denyManifests tests, with a bounded retry for the save_async race the author observed on Windows.

Security risks

None. Test code only; no production paths, no auth/crypto/permissions touched. The registries are local Bun.serve({port: 0}) proxies in front of the file-shared verdaccio.

Level of scrutiny

Medium-high for a test change. It is test-only, so correctness risk to shipped behavior is nil, but the refactor introduces non-trivial shared infrastructure (a module-level Map of install promises, a shared templates temp dir, a copy-and-rewrite step that must be equivalent to a fresh install for every downstream assertion to remain meaningful). The author added a dedicated self-test proving copy ≡ install byte-for-byte and verified the denyManifests coverage by locally reverting audit_fix.rs's cache bypass — both are the right kind of evidence. Still, the assertion rewrite touches ~20 test bodies and the concurrency conversion changes execution ordering, which is the sort of thing a maintainer familiar with this file's flake history should eyeball.

Other factors

My prior inline nit (dispose the cold retry dir before the bound assertion) was addressed in d4fcd06. The bug-hunting system found nothing this run. The PR description is unusually thorough on methodology (spawn counts via preload, CI timings from job logs, the mutation test on audit_fix.rs). The one design choice I'd want a maintainer to confirm is that memoizing installs by JSON.stringify(steps) and copying via tempDir(prefix, dir) is the pattern they want for this file going forward — it is sound, but it is new infrastructure that future test authors will build on.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

For whoever picks this up, the one design question left open by the reviews is whether the install templates (installedProject / copyProject, about 60 lines plus the setup() self-test) are wanted at all, or whether the concurrent conversion and the exact assertions should land on their own. The numbers for the templates alone are the CPU figures in the description (the concurrent conversion does not change the work done); they are kept local to this file on purpose, since the bun.lock rewrite depends on how this file's proxy registries are set up. If the same trick is wanted for the larger install files it should become a harness helper, and I would rather do that as a follow-up than widen this PR. Dropping the templates here is a small edit if that is the preference.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

doesn't move numbers enough

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants