Skip to content

test(aot): run the compiled runs of a-module-whose-link-fails concurrently and state what each run says - #47854

Open
robobun wants to merge 1 commit into
mainfrom
robobun/b54a0622/link-fails-runs-concurrent
Open

robobun wants to merge 1 commit into
mainfrom
robobun/b54a0622/link-fails-runs-concurrent

Conversation

@robobun

@robobun robobun commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts takes 291 s on debian 13 x64-asan (build #137158), 67 s on x64, 48 s on aarch64.
  • Its five bun test --aot runs go one after another, and each compiles about 3,800 library functions.
  • No test states an exit code or every line of a run. A run that ignores --aot passes two of three tests.

Fix

  • Each compiled run is its own test, as in AOT: hangs, wrong answers, failed builds; bun test --aot follows #44833's mocks #47605, and test.concurrent. A debug build keeps them serial.
  • Plain Bun must give a stated report: every printed line, each test file's result, the counts, the exit code. Each compiled run must give the same and name its form.
  • The 480 s and 120 s limits go: a test holds one build.
  • Verified in CI (build #137270), in a process of its own: 65 s on x64-asan, 30 s on x64, 18 s on aarch64. Local batch as on main, release-asan: 209 s before, 160 s after. bun bd test: 250 s and 237 s.

Background

  • A module whose link fails is left to the loader, which reads it from disk. A run with one compiles 3,783 functions, a run without 173.
  • Each run asks its own question, so all five stay and processor time is unchanged. A batch that keeps every processor busy ends no sooner. What this file takes there depends on the files beside it.
  • Considered one run for both projects. The second tests that only a data file is shared, and a shared run adds shared code.
Notes

What stays as it was. Both projects are the same text. The commands are the same nine: four plain runs and five compiled runs, with the same flags. The two compiled runs that had no word now have TestRunSaysItsForm. That word makes a run print its form and changes nothing else (log_form_of_test_run in src/runtime/cli/run_compiled.rs). run() of harness/run.ts still fails the run in layers if it was not built in two layers.

Assertions, before and after.

Before After
Results of the six test files of the first project five, with toMatchObject all six, with toEqual, for both plain runs
Printed lines one line with toContain, for three of the four plain runs every line in order, for all four plain runs
Counts (2 pass, 4 fail, 4 errors, Ran 6 tests across 6 files.) not read stated for all four plain runs
Exit code compared with the plain run, never stated stated: 1, 1, 0, 1
That a run is compiled the run in layers only (expectLayers) all five: one program | test files N, or layers 2 | test files 6 | to a program 1
Compiled run against plain run toEqual of what was read toEqual of what is read now

expect() calls: 12 before, 10 after. One toEqual holds what several held.

Two faults that only the new file finds. I put each into both versions of the file and ran them with the release build.

Fault Main's file This PR
The compiled run of "in a run of one test file" starts without --aot 3 pass 1 fail
src/imports-absent.mjs imports one, which is there. Its test file passes and prints never printed 3 pass 2 fail

The first:

-   "form": StringMatching /^one program\b.* \| test files 1$/,

The second, in both "what plain Bun says" tests:

      "imports-a-name-that-is-not-there.test.mjs:": [
-       "SyntaxError: Export named 'absent' not found in module './src/esm.mjs'.",
+       "(pass) runs",
      ],
...
-     "2 pass",
-     "4 fail",
-     "4 errors",
+     "3 pass",
+     "3 fail",
+     "3 errors",
...
+     "never printed 1",
+     "never printed",

Where the time goes. Release build, 12 processors, each command alone:

Command Wall Processor time
each of the four plain runs 0.01 to 0.03 s
each of the five compiled runs 2.2 to 3.1 s 8.9 to 12.2 s user, 1.4 to 3.7 s system

With BUN_JSC_logAOTPhaseTimes=1 each compiled run says that it compiles 127 to 137 units and 3,749 to 4,004 functions. A project with one passing test file compiles 57 units and 173 functions in 0.8 s of processor time. The same project with a second test file that imports a module whose link fails compiles 128 units and 3,783 functions in 9.7 s. So the cost comes with the subject of the file. With the debug build one compiled run takes 216 to 293 s of processor time. A compiled run holds 0.2 to 0.3 GB at its peak with the release build and 1.1 GB with release-asan, so five at once hold 5.6 GB there.

Why five builds.

  • One program in one realm, one program with a realm to a file, two layers: the three forms of a run of the first project.
  • One test file: the only run in which no test file fails to load. Only import() reaches the module whose link fails.
  • A file of data: the only run in which the module whose link fails shares no code with a compiled file.

All local runs. Linux x64, 12 vCPUs, 15.6 GB, taskset for fewer processors. Before and after ran in turn.

The file alone:

Build Processors Before After
debug, bun bd test 12 252.5, 247.4 s 211.4, 240.6, 236.9 s
release, bun-profile 12 14.6, 15.9, 15.9, 16.6 s 7.0, 6.3, 6.3, 6.5 s
release, bun-profile 4 16.0, 15.7 s 14.4, 14.6 s with --max-concurrency=2. 14.2, 14.3 s with =4
release-asan, through scripts/runner.node.ts 8 62.1, 50.5, 57.7 s 40.0, 36.8, 36.4 s

User time of those rows: debug 1479, 1401 s before and 1362, 1401, 1420 s after. Release on 12 processors 54 s before and 56 s after. Release-asan 238, 231, 242 s before and 261, 249, 249 s after. One build keeps about four processors busy, so on 4 processors two builds at once end little sooner than one after the other.

The file in a batch, started as runParallelBucket of scripts/runner.node.ts starts one (bun test --parallel=<n> --max-concurrency=2):

Batch Before After
release-asan, 8 processors, --parallel=7, this file and six long files this file 202.7, 215.4 s 156.1, 164.1 s
the batch 251.7, 278.0 s 244.2, 258.1 s
user time 1631, 1692 s 1616, 1659 s
release, 4 processors, --parallel=3, the same seven files this file 32.6, 33.0, 32.8 s 16.7, 15.4, 20.4 s
the batch 84.8, 87.0, 89.4 s 88.1, 88.5, 90.5 s
user time 315, 323, 330 s 326, 328, 332 s
release, 4 processors, --parallel=3, this file and seven shorter files this file 18.2, 16.5, 22.8, 19.9, 20.7, 19.0, 17.7 s 21.9, 22.4, 25.6, 25.6, 24.7, 23.6, 22.4 s
the batch 41.7, 39.7, 45.6, 45.0, 43.6, 40.8, 40.8 s 38.2, 38.0, 42.8, 42.6, 41.4, 40.4, 38.2 s
  • The six long files: a-mocked-commonjs-file-keeps-its-own-exports, a-module-that-a-test-writes-imports-compiled-files, a-module-that-exists-only-as-a-mock-is-found-wherever-mock-comes-from, a-name-imported-from-a-built-in-module-keeps-what-the-module-had-at-the-link, a-name-that-two-stars-export-is-ambiguous-to-code-from-disk, a-package-that-loads-one-of-two-files-by-node-env-in-a-test-run. Each takes 17 to 68 s in the release batch.
  • The seven shorter files take 6 to 19 s each in the release batch. Beside them this file took longer than before: two of its builds at once took 9 to 11 s each, where one build took 3 to 4 s.
  • In build #137158 the file took 291 s in its batch on x64-asan. Alone on 8 processors it takes 51 to 62 s. So its batch in CI keeps the processors busy, as the first two batches above do.

CI. The file passed on its first try on each of its three lanes.

Lane Main, build #137158, in its batch This PR, build #137270, in a process of its own
debian 13 x64-asan 291 s 65.3 s
debian 13 x64 67 s 30.3 s
debian 13 aarch64 48 s 17.5 s

The two columns are not the same way of running the file. On main the file is in a batch: --parallel=7 --max-concurrency=2 on x64-asan, so two of its builds run at once. A file that a PR changes runs in a process of its own with --max-concurrency=min(20, processors): here all five builds at once beside two other files on x64-asan, and four at once beside one other file on x64 and aarch64. For the batch on main, the local rows "in a batch" above are the measure.

Five builds at once take processors from the files beside them. On x64-asan code-the-compiler-never-saw.test.ts ran beside this file and took 120 s. Its median on that lane is 75 s (test/expected-durations.json). It passed.

Limits. A test has the default of test/aot/supported.ts: 120 s, and 270 s with a sanitizer or a debug build. The longest test took 80.5 s in the release-asan batch, 11.4 s in the release batches, and 55.5 s with the debug build.

Why a debug build keeps the runs one after another. Five at once with the debug build took 154 s on 12 processors (a test took 144 to 150 s), 196 s on 8 (184 to 193 s) and 242 s on 6 (226 to 240 s). A test has 270 s there. One after another a test takes 38 to 56 s. The file says test.serial and not test: in #47605 test of supported.ts is concurrent with a sanitizer, and a debug build has one.

#47605. That PR splits the first test in the same way: a beforeAll for the two plain runs, two tests for what plain Bun says, one test for each compiled run, no explicit limits. This PR takes that shape and its titles, so one rebases onto the other without a change of structure. On top of #47605 this PR adds test.concurrent where no sanitizer is, the stated reports and the form. #47605 imports afterAll from harness/default-timeout. Main does not export it there yet, so here it comes from bun:test.

Not taken.

  • One run for both projects, or one run for all test files and for the one test file: each removes a build and changes what a run holds (see "Why five builds").
  • A word or a flag that makes a build cheaper: the file would then test another build than bun test --aot makes.
  • toMatchInlineSnapshot of stdout and stderr: a compiled run prints its form first, and with BUN_JSC_logAOTPhaseTimes in the environment thousands of lines more. The reports are data that seven tests share.

Not run. CI runs this file on the three Debian lanes only (runsOnThisLane in scripts/runner.node.ts). I ran it on Linux x64 only. The patterns for the form accept the line of a Bun that loads one program a realm (macOS, Windows). I read that line in log_form_of_test_run and did not run it.


[auto-merge] gate passed · iteration 0 · 1 files touched

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

Debug/ASAN (expected pass):
$ bun bd test 'test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts
bun test v1.4.4 (367d939d9)

test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts:
(pass) what plain Bun says: in one realm [3.77ms]
(pass) what plain Bun says: a realm to a file [0.54ms]
(pass) each test file fails as under bun test, and the others run: one program, in one realm [44384.02ms]
(pass) each test file fails as under bun test, and the others run: one program, a realm to a file [42165.65ms]
(pass) each test file fails as under bun test, and the others run: two layers [39915.69ms]
(pass) in a run of one test file [41645.73ms]
(pass) where a file of data is all that is shared [39994.53ms]

 7 pass
 0 fail
 10 expect() calls
Ran 7 tests across 1 file. [212.09s]
Exit: 0
diff hotspot
...k-fails-imports-what-other-files-import.test.ts | 178 ++++++++++++++++-----
 1 file changed, 134 insertions(+), 44 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                                      reads  edits  tests
…hose-link-fails-imports-what-other-files-import.test.ts      8      3     25

…ently and state what each run says

Each `bun test --aot` run of the file is a test of its own, as in #47605,
and the five are `test.concurrent`: `--max-concurrency` says how many build
at once. A debug build keeps them one after another.

Plain Bun must give a stated report: every printed line, the result of each
test file, the counts and the exit code. Each compiled run must give the
same report and name its form.
@coderabbitai

coderabbitai Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next included review available in 8 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 3bf1a06 and ae00f3f.

Files selected for processing: 1
  • test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@github-actions github-actions Bot added the claude label Nov 3, 2026
@robobun

robobun commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 7:50 AM PT - Nov 3rd, 2026

❌ @robobun, your commit ae00f3f has 40 failures in Build #137270 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 47854

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

bun-47854 --bun

@robobun

robobun commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. This is a test-only change to test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts. The file passed on its first try on its three lanes in build #137270: 65.3 s on debian 13 x64-asan, 30.3 s on x64, 17.5 s on aarch64.

Build #137270 has ended red for 29 other test files. None is this file, and this diff touches none of them.

  • bun run ci:errors marks each of the 29 as red on main too.
  • 37 more files failed once and passed on a retry. Most are timeouts inside the batches of x64-asan, in 15 of its 20 shards. This file ran in one shard, before the batches of that shard.

How this was checked, on Linux x64:

  • bun bd test test/aot/a-module-whose-link-fails-imports-what-other-files-import.test.ts: 7 pass in 237 s. Main's file: 3 pass in 250 s. A debug build keeps the five compiled runs one after another.
  • Release build on 12 processors: 6.3 s. Main's file: 15.9 s.
  • release-asan through scripts/runner.node.ts on 8 processors: 37 s alone, 160 s in a batch of seven files. Main's file: 58 s and 209 s.
  • Two faults put into both versions of the file pass on main and fail here. The PR body shows them.

The CI times above are for the file in a process of its own, which is how CI runs a file that a PR changes. On main the file runs in a batch with two builds at once. The PR body has the local measure for that.

@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.

Code review found no issues

No high-confidence issues detected in this change.

This branch has not been deployed

No deployments
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