Skip to content

bun test exits after failures are not LSan-clean (bail and failing runs abort under detect_leaks=1) #32183

Description

@robobun

Follow-up from #32180 (which makes green bun test exits LSan-clean). Runs that end in failure still abort under ASAN_OPTIONS=detect_leaks=1:abort_on_error=1 when BUN_DESTRUCT_VM_ON_EXIT is not set, turning an intended exit 1 into exit 134. Three distinct mechanisms, found while evaluating a review suggestion on #32180:

  1. Ordinary failing test with --bail: CommandLineReporter::handle_test_completed (src/runtime/cli/test_command.rs, the bail branch around line 1374) calls Global::exit(1) directly. No deinit_for_exit(), no RUNNER clear, no final collection, and the active file is mid-execution at that point, so the straightforward fix (running the exec tail's teardown there) is not obviously safe: the run loop still holds the BunTestCell strong and BunTest::run frames may be live.

  2. Module-evaluation-failure bail in TestCommand::run (the Status::Rejected branch around line 3179): runs deinit_for_exit() and clears RUNNER but exits without the final collection added in bun test: free test-runner finalizer-owned allocations before exit so LSan lanes don't abort green runs #32180, so wrapper boxes created before the bail (Expect, RefData, RcInner<BunTestCell>) stay malloc-live.

  3. Any failing run, including through the exec tail: printing the failure caches a ParsedSourceMap Arc in vm.saved_source_map_table (SavedSourceMap::get_with_content, src/jsc/SavedSourceMap.rs:365, reached via remap_zig_exception). LSan cannot scan the table's backing memory, so the Arc is reported even though it is live VM state:

Direct leak of 168 byte(s) in 1 object(s) allocated from:
    <alloc::sync::Arc<bun_sourcemap::parsed_source_map::ParsedSourceMap>>::new
    <bun_jsc::saved_source_map::SavedSourceMap>::get_with_content  src/jsc/SavedSourceMap.rs:365
    <bun_jsc::virtual_machine::VirtualMachine>::remap_zig_exception  src/jsc/VirtualMachine.rs:5324

Repro (debug ASAN build):

mkdir /tmp/fail-leak && cd /tmp/fail-leak
printf 'import { expect } from "bun:test";\nexpect(1 + 1).toBe(3);\n' > a.test.js
ASAN_OPTIONS="allow_user_segv_handler=1:disable_coredump=0:detect_leaks=1:abort_on_error=1" \
LSAN_OPTIONS="malloc_context_size=30:print_suppressions=0:suppressions=$BUN_REPO/test/leaksan.supp" \
bun-debug test .          # exit 134 (mechanism 3)
bun-debug test --bail .   # exit 134 (mechanisms 2 and 3)

No CI exposure today: every test that spawns bun test --bail is listed in test/no-validate-leaksan.txt (bun-test.test.ts, parallel.test.ts, regression/issue/12250.test.ts), and the CI leak lane sets BUN_DESTRUCT_VM_ON_EXIT=1, whose destroy() path frees the source-map table, so mechanism 3 does not fire there. This is why #32180 scoped itself to green-run exits. Fixing this class would let failing-run assertions (exitCode === 1) run under the leak lane without exemptions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions