Repository navigation
test(aot): run the five builds of errors-made-by-a-constructor-that-the-build-cannot-name.test.ts concurrently and pin every line - #47850
Conversation
…he-build-cannot-name.test.ts concurrently and pin every line One test ran five `bun test --aot` builds one after another under a limit of 360 s. Each build is now a test of its own, and the five are concurrent. A debug build runs them one by one. A snapshot pins the 45 lines that plain Bun prints, the result of both inner tests, the totals and the exit code. Each compiled run must equal it and must say that it was built in the form that its row names. On a debug build every run hides the frames of private scripts, as a release does. Without that the plain run lists a frame that compiled code does not have, and the file fails under `bun bd test`.
|
Status: ready for review. The diff is green, and it needs a maintainer to merge.
How the slowness was measured:
Result, the file alone, local:
Result in CI:
The columns of |
|
No actionable comments were generated in the recent review. WalkthroughThe test captures and snapshots normalized plain-Bun output, including traces, test results, totals, and exit status. It compares five AOT configurations with that baseline and runs the configurations serially in debug builds or concurrently in other builds. ChangesStack trace tests
Suggested reviewers: Merge Risk:
|
Two of the 45 pinned lines say `undefined` where Node has a stack: an error that `Reflect.construct` makes for another `new.target`, and one made under `Error.stackTraceLimit = 0`. A comment above the snapshot says that this is what plain Bun does today, so that a change of plain Bun updates the text knowingly.
…tructor fails `beforeAll` kept the plain run without a look at its exit code. When plain Bun failed the project, the five rows still built and each failed against a result that holds the raw stderr of the plain run: six failures and five builds, where the one test of main failed at once. `beforeAll` now holds the plain run to exit code 0 and shows its stderr when it fails. That is one failure, and no build starts.
There was a problem hiding this comment.
LGTM — the latest commit adds the fail-fast in beforeAll, and the rest of the rewrite checks out against the runtime and harness.
What was reviewed:
BUN_JSC_showPrivateScriptsInStackTraces=0is live: the debug default is set in ZigGlobalObject.cpp before theBUN_JSC_env loop, so the override wins;AOT test run | one program|layers|programs without layersis printed bylog_form_of_test_runin src/runtime/cli/run_compiled.rs whenever aTestRun*word is set, which every row sets.- A throwing
beforeAllskips straight toafterAll(Execution.rsfailure_skip_to), so no build starts when plain Bun fails;afterAlltolerates the unsupported path wherebeforeAllis a no-op anddirstays undefined. - Snapshot totals match the fixture (3
expect()calls in a.test.js + 1 in b.test.js, 2 files);normalizeBunSnapshotstrips the[N ms]suffixes the(pass)/Ranfilters rely on;test.serial.each/test.concurrent.eachexist in bun:test and pass throughsupported.ts's skip proxy.
Extended reasoning...
The diff rewrites one test file under test/aot: the plain run moves into a beforeAll, its full output is pinned in an inline snapshot, and the five --aot builds become a test.each table run concurrently on release and serially on debug. It touches no production code and no security-sensitive surface; the only env knobs it sets are read by src/ as verified above. The one commit since the prior run addresses the fail-fast point raised then, no CODEOWNER covers test/aot, and the bug hunt ran dry, which decided approve over defer.
Problem
test/aot/errors-made-by-a-constructor-that-the-build-cannot-name.test.tstakes 344 s on debian 13 x64-asan (build #137158). One test runs fivebun test --aotbuilds in turn.--aotpasses.bun bd testfails on main: a debug build lists a private frame in plain Bun's stacks.Fix
bun bd test: 1 fail before, 6 pass in 265 to 338 s after.Background
bun test --aotcompiles the test files and what they import, then runs them. In layers, the lowest program holds all of Bun's own JavaScript.bun test --aotbuilds and runs in one command.bun test --aotfollows #44833's mocks #47605 splits this file the same way. Itstest/aot/every-build.txtleaves the file out of CI.Notes
What stays as it was. The project of the test is the same text, byte for byte. The six commands are the same: plain
bun test --isolate, then--aotas one program, over one layer, over two layers, and as one program and over two layers withBUN_JSC_validateAOTRuntime=1. The words ofBUN_DEBUG_TEST_AOT_WORDSare the same.run()ofharness/run.tsstill fails a run that was not built in the layers that it asked for. The one new variable isBUN_JSC_showPrivateScriptsInStackTraces=0, on a debug build only.Assertions, before and after.
(pass)for both tests,2 pass,0 fail,4 expect() calls,Ran 2 tests across 2 files.for every runrun()AOT test run | one programor| layers. The runs as one program must sayone programbeforeAllfails with the exit code and the stderr of the plain runtest/aot/supported.tsfor one build: 120 s, or 270 s with assertionsOn debian 13 x64-asan the one test of main takes 344 s of its 360 s.
Three faults show the difference. Each was put into both versions of the file and run with the release build.
b.test.jsskips its test(skip),1 pass,1 skip,3 expect() calls--aotout"built": undefinedA fourth fault makes the plain run fail:
b.test.jsfails its test. Main's file and this PR each report one failure in 0.1 s and start no build. The second commit of this PR reported six failures in 8.0 s and started five builds.Where the time goes. One run at a time, processor time of the run and of all that it starts:
BUN_JSC_logAOTPhaseTimes=1, the lowest layer takes 2.3 s of the 2.9 s of a run over two layers.import("data:text/javascript,..")lines it takes 5.3 s. The build cannot read what such an import loads, and the program then holds most of Bun's library. Both lines stay: they are cases of the test.Why not fewer builds. The two runs that validate repeat the builds of the runs beside them:
validateAOTRuntimeis an option of the run time.bun test --aotwrites the program into an unnamed file and starts itself again with it (start_second_process,src/runtime/cli/run_compiled.rs). A test has no way to run that program a second time. A Bun that is built with--compiledLibrary=ondoes not compile its library for a run in layers, but then the package is a program of its own above the library. In the Bun under test the two are one program.All local timings. Linux x64, 12 vCPUs, 15.6 GB,
tasksetfor fewer CPUs. Before and after were taken in turn. The kernel time of this machine changes from run to run, and wall time follows it.release (
bun run build:release,bun-profile), the file alone, 12 CPUs:release, the file alone, 4 CPUs, the environment that
scripts/runner.node.tsgives a file:--max-concurrency=4--max-concurrency=2release, inside the batch of CI's x64 lane: the 28 files that build #137158 ran beside this one,
bun test --parallel=3 --max-concurrency=2 --timeout=90000, 4 CPUs:One other test of that batch, in
test/aot/perf/threads-of-a-build.test.ts, failed in three of these four runs, before and after. It counts the threads of a build, andtasksetchanges what it sees.release-asan (
bun run build:asan,bun-asan), the file alone, 8 CPUs, the environment of the lane with AddressSanitizer:--max-concurrency=8--max-concurrency=2--max-concurrency=8is what the runner passes when the file runs in a process of its own.--max-concurrency=2is what a batch gets there.-j4passed.debug (
bun bd test), 12 CPUs:at <anonymous> (a.test.js:54:2)at the end of 22 of its 45 lines.Most memory in use at once, beyond what was in use before the run:
--max-concurrency=8, 2.87, 2.86 GB with--max-concurrency=2The lane with AddressSanitizer has 64 GB.
The debug build. A debug build sets
showPrivateScriptsInStackTraces(src/jsc/bindings/ZigGlobalObject.cpp:436). Plain Bun then lists one more frame,at <anonymous> (a.test.js:54:2), below the test function until its firstawait. A compiled run does not list it. WithBUN_JSC_showPrivateScriptsInStackTraces=0the debug build prints the same 45 lines as the release build and the release-asan build, plain and compiled.Why a debug build runs the builds in turn. One build keeps 6 of 12 processors busy there. Five at once took 160 s, and each run took 153 to 160 s of the 270 s that a test has. In turn the five took 300 s, and each run took 52 to 75 s.
One folder for six runs. The runs share the project folder, as in #47605. A build writes nothing into it: the program goes into an unnamed file (
create_unnamed_file,src/runtime/cli/run_compiled.rs:1509), and the text that stands in for the test files stays in memory (set_stand_in, line 91). The folder holds the same files after five runs at once.A test that runs out of time.
bun testends the child of a test that ran out of time only where one test runs at a time (kill_dangling_processes_on_timeout,src/runtime/test_runner/Execution.rs:442). So a compiled run whose test runs out of time goes on until the file ends, where the one test of main ended it at once. It does not go on behind the file:swap_global_for_test_isolation,src/jsc/VirtualMachine.rs:6471).BUN_FEATURE_FLAG_NO_ORPHANS=1for a file oftest/aot, and the child ends with the test process.sleep 300behind its timeout. The next file of the same worker finds the child gone. So does the shell after a run of the one file with that variable. Without the variable, in a run by hand, the child outlivesbun test.On main, 72 test files of
test/aotimportrun()and declare concurrent tests. A limit for one run belongs inrun()ofharness/run.ts, which this PR does not change.The snapshot. It holds what plain Bun prints today. Two of its lines say
undefinedwhere the stack is read:Reflect.construct(Error, [..], Subclass)and an error made underError.stackTraceLimit = 0. Node gives both a stack. The snapshot does not say that this is right. It says that a compiled run does the same.CI, before. Build #137158, from the logs of the test jobs:
CI, this PR. Builds #137257, #137264 and #137268 ran the first, the second and the third commit. The second commit adds a comment. The third makes a plain run that fails one failure.
mainand of this PR are not like for like. CI runs a file that a PR changes in a process of its own, in front of the other files that build many executables: beside two of them with--max-concurrency=8on x64-asan, beside one with--max-concurrency=4on the others. Onmainthe file runs inside its batch with--max-concurrency=2.On top of #47605. That PR has
beforeAllfor the plain run andtest.eachwith the same five rows.testof itstest/aot/supported.tsis concurrent with a sanitizer only. To combine the two, keep this file's version.test.serialandtest.concurrentpass through the proxies of thatsupported.ts. The snapshot test has noawait, so the snapshot also works where thattestis concurrent.[auto-merge] gate passed · iteration 1 · 1 files touched
passes on PR (with fix)
diff hotspot
gate history · 2 passed · 0 rejected · iteration 1
evidence per changed file