## Summary
Fixes a panic that occurred when `console.log()` tried to format a Set
or Map instance with a non-numeric `size` property.
## Issue
When a Set or Map subclass overrides the `size` property with a
non-numeric value (like a constructor function, string, or other
object), calling `console.log()` on the instance would trigger a panic:
```javascript
class C1 extends Set {
constructor() {
super();
Object.defineProperty(this, "size", {
writable: true,
enumerable: true,
value: Set
});
console.log(this); // panic!
}
}
new C1();
```
## Root Cause
In `src/bun.js/ConsoleObject.zig`, the Map and Set formatting code
called `toInt32()` directly on the `size` property value. This function
asserts that the value is not a Cell (objects/functions), causing a
panic when `size` was overridden with non-numeric values.
## Solution
Changed both Map and Set formatting to use `coerce(i32, globalThis)`
instead of `toInt32()`. This properly handles non-numeric values using
JavaScript's standard type coercion rules and propagates any coercion
errors appropriately.
## Test Plan
Added regression tests to `test/js/bun/util/inspect.test.js` that verify
Set and Map instances with overridden non-numeric `size` properties can
be inspected without panicking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
### What does this PR do?
Fixes unhelpful FFI error messages that made debugging extremely
difficult. The user reported that when dlopen fails, the error doesn't
tell you which library failed or why.
**Before:**
```
Failed to open library. This is usually caused by a missing library or an invalid library path.
```
**After:**
```
Failed to open library "libnonexistent.so": /path/libnonexistent.so: cannot open shared object file: No such file or directory
```
### How did you verify your code works?
1. **Cross-platform compilation verified**
- Ran `bun run zig:check-all` - all platforms compile successfully
(Windows, macOS x86_64/arm64, Linux x86_64/arm64 glibc/musl)
2. **Added comprehensive regression tests**
(`test/regression/issue/dlopen-missing-symbol-error.test.ts`)
- ✅ Tests dlopen error shows library name when it can't be opened
- ✅ Tests dlopen error shows symbol name when symbol isn't found
- ✅ Tests linkSymbols shows helpful error when ptr is missing
- ✅ Tests handle both glibc and musl libc systems
3. **Manually tested error messages**
- Missing library: Shows full path and "No such file or directory"
- Invalid library: Shows "invalid ELF header"
- Missing symbol: Shows symbol and library name
- linkSymbols without ptr: Shows helpful explanation
### Implementation Details
1. **Created cross-platform getDlError() helper**
(src/bun.js/api/ffi.zig:8-21)
- On POSIX: Calls `std.c.dlerror()` to get actual system error message
- On Windows: Returns generic message (detailed errors handled in C++
layer via `GetLastError()` + `FormatMessageW()`)
- Follows the pattern established in `BunProcess.cpp` for dlopen error
handling
2. **Improved error messages**
- dlopen errors now include library name and system error details
- linkSymbols errors explain the ptr field requirement clearly
- Symbol lookup errors already showed both symbol and library name
3. **Fixed linkSymbols error propagation** (src/js/bun/ffi.ts:529)
- Added missing `if (Error.isError(result)) throw result;` check
- Now consistent with dlopen which already had this check
### Example Error Messages
- **Missing library:** `Failed to open library "libnonexistent.so":
cannot open shared object file: No such file or directory`
- **Invalid library:** `Failed to open library "/etc/passwd": invalid
ELF header`
- **Missing symbol:** `Symbol "nonexistent_func" not found in
"libc.so.6"`
- **Missing ptr:** `Symbol "myFunc" is missing a "ptr" field. When using
linkSymbols() or CFunction()...`
Fixes the issue mentioned in:
https://fxtwitter.com/hassanalinali/status/1977710104334963015🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Adds comprehensive support to `generate-classes.ts` for JavaScript
classes that need both named WriteBarrier members (like callbacks) and a
dynamic array of JSValues, all properly tracked by the garbage
collector. This replaces error-prone manual `protect()/unprotect()`
calls with proper GC integration.
## Motivation
The shell interpreter was using `JSValue.protect()/unprotect()` to keep
JavaScript objects alive, which caused memory leaks when cleanup paths
didn't properly unprotect values. This is a common pattern that needed a
better solution.
## What Changed
### Code Generator (`generate-classes.ts`)
When a class has both `values: ["resolve", "reject"]` and `valuesArray:
true`:
**Generated C++ class gets:**
- `WTF::FixedVector<JSC::WriteBarrier<JSC::Unknown>> jsvalueArray`
member for dynamic array
- Individual `JSC::WriteBarrier<JSC::Unknown> m_resolve, m_reject`
members for named values
- 4 `create()` overloads covering all combinations:
1. Basic: `create(vm, globalObject, structure, ptr)`
2. Array only: `create(..., FixedVector<WriteBarrier<Unknown>>&&)`
3. Named values: `create(..., JSValue resolve, JSValue reject)`
4. Both: `create(..., FixedVector&&, JSValue resolve, JSValue reject)`
**Constructor overloads using `WriteBarrierEarlyInit`:**
```cpp
JSShellInterpreter(VM& vm, Structure* structure, void* ptr,
JSValue resolve, JSValue reject)
: Base(vm, structure)
, m_resolve(resolve, JSC::WriteBarrierEarlyInit) // ← Key technique
, m_reject(reject, JSC::WriteBarrierEarlyInit)
{
m_ctx = ptr;
}
```
The `WriteBarrierEarlyInit` tag allows initializing WriteBarriers in the
constructor initializer list before the object is fully constructed,
which is required for proper GC integration.
**Extern C bridge functions:**
- `TypeName__createWithValues(globalObject, ptr, markedArgumentBuffer*)`
- `TypeName__createWithInitialValues(globalObject, ptr, resolve,
reject)`
- `TypeName__createWithValuesAndInitialValues(globalObject, ptr,
buffer*, resolve, reject)`
**Zig convenience wrappers:**
- `toJSWithValues(this, globalObject, markedArgumentBuffer)`
- `toJSWithInitialValues(this, globalObject, resolve, reject)`
- `toJSWithValuesAndInitialValues(this, globalObject, buffer, resolve,
reject)`
### Shell Interpreter Memory Leak Fix
**Before:**
```zig
const js_value = JSShellInterpreter.toJS(interpreter, globalThis);
resolve.protect(); // Manual reference counting
reject.protect();
// ... later in cleanup ...
resolve.unprotect(); // Easy to forget/miss in error paths
reject.unprotect();
```
**After:**
```zig
const js_value = Bun__createShellInterpreter(
globalThis,
interpreter,
parsed_shell_script,
resolve, // Stored with WriteBarrierEarlyInit
reject, // GC tracks automatically
);
// No manual memory management needed!
```
### Supporting Changes
- Added `MarkedArgumentBuffer.wrap()` helper in Zig for safe
MarkedArgumentBuffer usage
- Created `ShellBindings.cpp` with `Bun__createShellInterpreter()` using
the new API
- Removed all `protect()/unprotect()` calls from shell interpreter
- Applied pattern to both `ShellInterpreter` and `ShellArgs` classes
## Benefits
1. **No memory leaks**: GC tracks all references automatically
2. **Safer**: Cannot forget to unprotect values
3. **Cleaner code**: No manual reference counting
4. **Reusable**: Pattern works for any class needing to store JSValues
5. **Performance**: Same cost as manual protect/unprotect but safer
## Testing
Existing shell tests verify the functionality. The pattern is already
used throughout JavaScriptCore for similar cases (see
`JSWrappingFunction`, `AsyncContextFrame`, `JSModuleMock`, etc.)
## When to Use This Pattern
Use `values` + `valuesArray` + `WriteBarrierEarlyInit` when:
- Your C++ class needs to keep JavaScript values alive
- You have both known named callbacks AND dynamic arrays of values
- You want the GC to track references instead of manual
protect/unprotect
- Your class extends `JSDestructibleObject`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
Makes sure strings are doubled quoted when they start with flow
indicators and `:`.
Fixes#23502
### How did you verify your code works?
Added tests for each indicator in flow and block context
### What does this PR do?
fix tests failing because of example.com
### How did you verify your code works?
CI
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## What does this PR do?
Bumps Bun version from 1.2.24 to 1.3.0, marking the start of the 1.3.x
release series.
## Changes
- **`package.json`**: Updated version from `1.2.24` to `1.3.0`
- **`LATEST`**: Updated from `1.2.23` to `1.3.0` (used by installation
scripts)
- **`test/bundler/bundler_bun.test.ts`**: Updated version check to
include `1.3.x` so export conditions tests continue to run
## Verification
✅ Debug build successful showing version `1.3.0-debug`
✅ All platforms compile successfully via `bun run zig:check-all` (49/49
steps)
✅ Bundler tests pass with updated version check
## Additional Notes
- CI workflow Bun versions (e.g., `1.2.3`, `1.2.0` in
`.github/workflows/release.yml`) are intentionally left unchanged -
these are pinned versions used to run the release tooling, not the
version being released
- Docker images use `ARG BUN_VERSION` passed at build time and don't
need updates
- The actual release version comes from git tags via `${{
env.BUN_VERSION }}`
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Fixes#23380 - this is a use-case for the `--only` flag that I missed
Adds back the `--only` flag. When running `bun test` on a full test
suite, without this flag it will run only that test in its file, but it
will run all other tests from other files. With this flag, it will not
run things from other files.
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pfg <pfg@pfg.pw>
### What does this PR do?
Fixes data loss when reading large amounts of data from subprocess pipes
on Windows, a regression introduced by the libuv 1.51.0 upgrade in
commit e3783c244f.
### The Problem
When piping large data through a subprocess on Windows (e.g.,
`process.stdin.pipe(process.stdout)`), Bun randomly loses ~73KB of data
out of 1MB, receiving only ~974KB instead of the full 1048576 bytes.
The subprocess correctly receives all 1MB on stdin, but the parent
process loses data when reading from the subprocess stdout.
### Root Cause Analysis
#### libuv 1.51.0 Change
The libuv 1.51.0 upgrade (commit
[libuv/libuv@727ee723](727ee7237e))
changed Windows pipe reading behavior:
**Before:** libuv would call `PeekNamedPipe` to check available bytes,
then read exactly that amount.
**After:** libuv attempts immediate non-blocking reads (up to 65536
bytes) before falling back to async reads. If less data is available
than requested, it returns what's available and signals `more=0`,
causing the read loop to break.
This optimization introduces **0-byte reads** when data isn't
immediately available, which are delivered to Bun's read callback.
#### The Race Condition
When Bun's `WindowsBufferedReader` called `onRead(.drained)` for these
0-byte reads, it created a race condition. Debug logs clearly show the
issue:
**Error case (log.txt):**
```
Line 79-80: onStreamRead = 0 (drained)
Line 81: filesink closes (stdin closes)
Line 85: onStreamRead = 6024 ← Should be 74468!
Line 89: onStreamRead = -4095 (EOF)
```
**Success case (success.log.txt):**
```
Line 79-80: onStreamRead = 0 (drained)
Line 81: filesink closes (stdin closes)
Line 85: onStreamRead = 74468 ← Full chunk!
Line 89-90: onStreamRead = 0 (drained)
Line 91: onStreamRead = 6024
Line 95: onStreamRead = -4095 (EOF)
```
When stdin closes while a 0-byte drained read is pending, the next read
returns truncated data (6024 bytes instead of 74468 bytes).
### The Fix
Two changes to `WindowsBufferedReader` in `src/io/PipeReader.zig`:
#### 1. Ignore 0-byte reads (line 937-940)
Don't call `onRead(.drained)` for 0-byte reads. Just return and let
libuv queue the next read. This prevents the race condition that causes
truncated reads.
```zig
0 => {
// With libuv 1.51.0+, calling onRead(.drained) here causes a race condition
// where subsequent reads return truncated data. Just ignore 0-byte reads.
return;
},
```
#### 2. Defer `has_inflight_read` flag clearing (line 827-839)
Clear the flag **after** the read callback completes, not before. This
prevents libuv from starting a new overlapped read operation while we're
still processing the current data buffer, which could cause memory
corruption per the libuv commit message:
> "Starting a new read after uv_read_cb returns causes memory corruption
on the OVERLAPPED read_req if uv_read_stop+uv_read_start was called
during the callback"
```zig
const result = onReadChunkFn(this.parent, buf, hasMore);
// Clear has_inflight_read after the callback completes
this.flags.has_inflight_read = false;
return result;
```
### How to Test
Run the modified test in
`test/js/bun/spawn/spawn-stdin-readable-stream.test.ts`:
```js
test("ReadableStream with very large chunked data", async () => {
const chunkSize = 64 * 1024; // 64KB chunks
const numChunks = 16; // 1MB total
const chunk = Buffer.alloc(chunkSize, "x");
const stream = new ReadableStream({
pull(controller) {
if (pushedChunks < numChunks) {
controller.enqueue(chunk);
pushedChunks++;
} else {
controller.close();
}
},
});
await using proc = spawn({
cmd: [bunExe(), "-e", `
let length = 0;
process.stdin.on('data', (data) => length += data.length);
process.once('beforeExit', () => console.error(length));
process.stdin.pipe(process.stdout)
`],
stdin: stream,
stdout: "pipe",
env: bunEnv,
});
const text = await proc.stdout.text();
expect(text.length).toBe(chunkSize * numChunks); // Should be 1048576
});
```
**Before fix:** Randomly fails with ~974KB instead of 1MB
**After fix:** Consistently passes with full 1MB
Run ~100 times to verify the race condition is fixed.
### Related Issues
This may also fix#23071 (Windows scripts hanging), though that issue
needs separate verification.
### Why Draft?
Marking as draft for Windows testing by the team. The fix is based on
detailed debug log analysis showing the exact race condition, but needs
verification on Windows CI.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Breaking changes:
- bun:test: disallow creating snapshots or using .only() in ci
- for users: hopefully this should only reveal existing bugs in tests,
not cause failures.
- general: enable calling unhandled rejection handlers for
ErrorBuilder.reject()
- for users: this might reveal some unhandled rejections that were not
visible before.
### What does this PR do?
Adds `expect().toBe()` checks for anchors/aliases. Also adds git commit
the tests were translated from.
### How did you verify your code works?
Manually
### What does this PR do?
Fixes bugs in the parser bringing it to 90% passing the official
[yaml-test-suite](https://github.com/yaml/yaml-test-suite) (362/400
passing tests)
Still missing from our parser: |- and |+ (about 5%), and cyclic
references.
Translates the yaml-test-suite to our tests.
fixes#22659fixes#22392fixes#22286
### How did you verify your code works?
Added tests for yaml-test-suite and each of the linked issues
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Adds birthtime (file creation time) support on Linux using the `statx`
syscall
- Stores birthtime in architecture-specific unused fields of the kernel
Stat struct (x86_64 and aarch64)
- Falls back to traditional `stat` on kernels < 4.11 that don't support
`statx`
- Includes comprehensive tests validating birthtime behavior
Fixes#6585
## Implementation Details
**src/sys.zig:**
- Added `StatxField` enum for field selection
- Implemented `statxImpl()`, `fstatx()`, `statx()`, and `lstatx()`
functions
- Stores birthtime in unused padding fields (architecture-specific for
x86_64 and aarch64)
- Graceful fallback to traditional stat if statx is not supported
**src/bun.js/node/node_fs.zig:**
- Updated `stat()`, `fstat()`, and `lstat()` to use statx functions on
Linux
**src/bun.js/node/Stat.zig:**
- Added `getBirthtime()` helper to extract birthtime from
architecture-specific storage
**test/js/node/fs/fs-birthtime-linux.test.ts:**
- Tests non-zero birthtime values
- Verifies birthtime immutability across file modifications
- Validates consistency across stat/lstat/fstat
- Tests BigInt stats with nanosecond precision
- Verifies birthtime ordering relative to other timestamps
## Test Plan
- [x] Run `bun bd test test/js/node/fs/fs-birthtime-linux.test.ts` - all
5 tests pass
- [x] Compare behavior with Node.js - identical behavior
- [x] Compare with system Bun - system Bun returns epoch, new
implementation returns real birthtime
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Add a new generator for JS → Zig bindings. The bulk of the conversion is
done in C++, after which the data is transformed into an FFI-safe
representation, passed to Zig, and then finally transformed into
idiomatic Zig types.
In its current form, the new bindings generator supports:
* Signed and unsigned integers
* Floats (plus a “finite” variant that disallows NaN and infinities)
* Strings
* ArrayBuffer (accepts ArrayBuffer, TypedArray, or DataView)
* Blob
* Optional types
* Nullable types (allows null, whereas Optional only allows undefined)
* Arrays
* User-defined string enumerations
* User-defined unions (fields can optionally be named to provide a
better experience in Zig)
* Null and undefined, for use in unions (can more efficiently represent
optional/nullable unions than wrapping a union in an optional)
* User-defined dictionaries (arbitrary key-value pairs; expects a JS
object and parses it into a struct)
* Default values for dictionary members
* Alternative names for dictionary members (e.g., to support both
`serverName` and `servername` without taking up twice the space)
* Descriptive error messages
* Automatic `fromJS` functions in Zig for dictionaries
* Automatic `deinit` functions for the generated Zig types
Although this bindings generator has many features not present in
`bindgen.ts`, it does not yet implement all of `bindgen.ts`'s
functionality, so for the time being, it has been named `bindgenv2`, and
its configuration is specified in `.bindv2.ts` files. Once all
`bindgen.ts`'s functionality has been incorporated, it will be renamed.
This PR ports `SSLConfig` to use the new bindings generator; see
`SSLConfig.bindv2.ts`.
(For internal tracking: fixes STAB-1319, STAB-1322, STAB-1323,
STAB-1324)
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Alistair Smith <hi@alistair.sh>
Multiple inline snapshots from one call should be avoided because they
will cause problems if one changes but not the other, but this allows
them if they both have the same value.
### What does this PR do?
bad:
```ts
function oops(a) {
expect(a).toMatchInlineSnapshot();
}
test("whoops", () => {
oops(1);
oops(2);
});
```
```
2 | expect(a).toMatchInlineSnapshot();
^
error: Failed to update inline snapshot: Multiple inline snapshots on the same line must all have the same value:
Expected: 1
Received: 2
at /Users/pfg/Dev/Node/bun/repro.ts:2:35
```
acceptable:
```ts
function ok(a) {
expect(a).toMatchInlineSnapshot(`1`);
}
test("whokay", () => {
ok(1);
ok(1);
});
```
```
✓ whokay
1 pass
0 fail
snapshots: +1 added
2 expect() calls
```
### How did you verify your code works?
TODO: add tests
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
- Fixed segmentation fault when calling `toContainAnyKeys`,
`toContainKeys`, and `toContainAllKeys` on non-object values (null,
undefined, numbers, strings, etc.)
- Added proper validation to check if value is an object before calling
`hasOwnPropertyValue` or `keys()`
- Added comprehensive test coverage for edge cases
## Problem
The matchers were crashing with a segmentation fault when called with
non-object values because:
1. `toContainAnyKeys` and `toContainKeys` were calling
`hasOwnPropertyValue` without checking if the value is an object first
2. `toContainAllKeys` was calling `keys()` without checking if the value
is an object first
3. The `hasOwnPropertyValue` function documentation explicitly states:
"If the object is not an object, it will crash. **You must check if the
object is an object before calling this function.**"
## Solution
- Added `value.isObject()` check in `toContainAnyKeys` before attempting
to check for properties
- Fixed `toContainKeys` by replacing the `toBoolean()` check with
`isObject()` check
- Fixed `toContainAllKeys` by adding proper object validation before
calling `keys()`
- For non-objects with empty expected arrays, the matchers return true
(matching jest-extended behavior)
## Test plan
- [x] Added comprehensive test coverage in
`test/js/bun/test/expect.test.js`
- [x] Tests cover: null, undefined, numbers, strings, booleans, symbols,
BigInt, arrays, functions
- [x] All existing jest-extended tests continue to pass
- [x] Debug build compiles and all tests pass
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
### What does this PR do?
Given pattern input "../." we might collapse all path components.
### How did you verify your code works?
Manually and added a test.
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
### What does this PR do?
Adds a max-concurrency flag to limit the amount of concurrent tests that
run at once. Defaults to 20. Jest and Vitest both default to 5.
### How did you verify your code works?
Tests
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
### What does this PR do?
When we added "happy eyeballs" support to fetch(), it meant that
`onOpen` would not be called potentially for awhile. If the AbortSignal
is aborted between `connect()` and the socket becoming
readable/writable, then we would delay closing the connection until the
connection opens. Fixing that fixes#18536.
Separately, the `isHTTPS()` function used in abort and in request body
streams was not thread safe. This caused a crash when many redirects
happen simultaneously while either AbortSignal or request body messages
are in-flight.
This PR fixes https://github.com/oven-sh/bun/issues/14137
### How did you verify your code works?
There are tests
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
### What does this PR do?
Resume work on https://github.com/oven-sh/bun/pull/21898
### How did you verify your code works?
Manually tested on MacOS, Windows 11 and Ubuntu 25.04. CI changes are
needed for the tests
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
### What does this PR do?
Uprevs `libuv` to version `1.51.0`.
### How did you verify your code works?
CI passes.
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This is feature flagged and will not activate until Bun 1.3
- Makes `test.only()` throw an error in CI
- Unless `--update-snapshots` is passed:
- Makes `expect.toMatchSnapshot()` throw an error instead of adding a
new snapshot in CI
- Makes `expect.toMatchInlineSnapshot()` throw an error instead of
filling in the snapshot value in CI
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Adds a new `test.serial()` API that forces tests to run serially even
when the `--concurrent` flag is passed. This is the opposite of
`test.concurrent()` which forces parallel execution.
## Motivation
Some tests genuinely need to run serially even in CI environments with
`--concurrent`:
- Database migration tests that must run in order
- Tests that modify shared global state
- Tests that use fixed ports or file system resources
- Tests that depend on timing or resource constraints
## Implementation
Changed `self_concurrent` from `bool` to `?bool`:
- `null` = default behavior (inherit from parent or use default)
- `true` = force concurrent execution
- `false` = force serial execution
## API Surface
```javascript
// Force serial execution
test.serial("database migration", async () => {
// This runs serially even with --concurrent flag
});
// All modifiers work
test.serial.skip("skip this serial test", () => {});
test.serial.todo("implement this serial test");
test.serial.only("only run this serial test", () => {});
test.serial.each([[1], [2]])("serial test %i", (n) => {});
test.serial.if(condition)("conditional serial", () => {});
// Works with describe too
describe.serial("serial test suite", () => {
test("test 1", () => {}); // runs serially
test("test 2", () => {}); // runs serially
});
// Explicit test-level settings override describe-level
describe.concurrent("concurrent suite", () => {
test.serial("this runs serially", () => {}); // serial wins
test("this runs concurrently", () => {});
});
```
## Test Coverage
Comprehensive tests added including:
- Basic `test.serial()` functionality
- All modifiers (skip, todo, only, each, if)
- `describe.serial()` blocks
- Mixing serial and concurrent tests in same describe block
- Nested describe blocks with conflicting settings
- Explicit overrides (test.serial in describe.concurrent and vice versa)
All 36 tests pass ✅
## Example
```javascript
// Without this PR - these tests might run in parallel with --concurrent
test("migrate database schema v1", async () => { await migrateV1(); });
test("migrate database schema v2", async () => { await migrateV2(); });
test("migrate database schema v3", async () => { await migrateV3(); });
// With this PR - guaranteed serial execution
test.serial("migrate database schema v1", async () => { await migrateV1(); });
test.serial("migrate database schema v2", async () => { await migrateV2(); });
test.serial("migrate database schema v3", async () => { await migrateV3(); });
```
🤖 Generated with [Claude Code](https://claude.ai/code)
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
This PR does two things.
First, it fixes a bug when using
[`jest-dom`](https://github.com/testing-library/jest-dom) where
expectation failures would break as `RECEIVED_COLOR` and
`EXPECTED_COLOR` are not properties of `ExpectMatcherContext`.
<img width="1216" height="139" alt="image"
src="https://github.com/user-attachments/assets/26ef87c2-f763-4a46-83a3-d96c4c534f3d"
/>
Second, it adds some existing timer mock functions that were missing
from the `vi` object.
### How did you verify your code works?
I've added a test.
### What does this PR do?
### How did you verify your code works?
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
# bun test
Fixes#8768, Fixes#14624, Fixes#20100, Fixes#19875, Fixes#14135,
Fixes#20980, Fixes#21830, Fixes#5738, Fixes#19758, Fixes#12782,
Fixes#5585, Fixes#9548, Might fix 5996
# New features:
## Concurrent tests
Concurrent tests allow running multiple async tests at the same time.
```ts
// concurrent.test.ts
test.concurrent("this takes a while 1", async () => {
await Bun.sleep(1000);
});
test.concurrent("this takes a while 2", async () => {
await Bun.sleep(1000);
});
test.concurrent("this takes a while 3", async () => {
await Bun.sleep(1000);
});
```
Without `.concurrent`, this test file takes 3 seconds to run because
each one has to wait for the one before it to finish before it can
start.
With `.concurrent`, this file takes 1 second because all three sleeps
can run at once.
```
$> bun-after test concurrent
concurrent.test.js:
✓ this takes a while 1 [1005.36ms]
✓ this takes a while 2 [1012.51ms]
✓ this takes a while 3 [1013.15ms]
3 pass
0 fail
Ran 3 tests across 1 file. [1081.00ms]
```
To run all tests as concurrent, pass the `--concurrent` flag when
running tests.
Limitations:
- concurrent tests cannot attribute `expect()` call counts to the test,
meaning `expect.assertions()` does not function
- concurrent tests cannot use `toMatchSnapshot`. `toMatchInlineSnapshot`
is still supported.
- `beforeAll`/`afterAll` will never be executed concurrently.
`beforeEach`/`afterEach` will.
## Chaining
Chaining multiple describe/test qualifiers is now allowed. Previously,
it would fail.
```ts
// chaining-test-qualifiers.test.ts
test.failing.each([1, 2, 3])("each %i", async i => {
throw new Error(i);
});
```
```
$> bun-after test chaining-test-qualifiers
a.test.js:
✓ each 1
✓ each 2
✓ each 3
```
# Breaking changes:
## Describe ordering
Previously, describe callbacks were called immediately. Now, they are
deferred until the outer callback has finished running. The previous
order matched Jest. The new order is similar to Vitest, but does not
match exactly.
```ts
// describe-ordering.test.ts
describe("outer", () => {
console.log("outer before");
describe("inner", () => {
console.log("inner");
});
console.log("outer after");
});
```
Before, this would print
```
$> bun-before test describe-ordering
outer before
inner
outer after
```
Now, this will print
```
$> bun-after test describe-ordering
outer before
outer after
inner
```
## Test ordering
Describes are no longer always called before tests. They are now in
order.
```ts
// test-ordering.test.ts
test("one", () => {});
describe("scope", () => {
test("two", () => {});
});
test("three", () => {});
```
Before, this would print
```
$> bun-before test test-ordering
✓ scope > two
✓ one
✓ three
```
Now, this will print
```
$> bun-after test test-ordering
✓ one
✓ scope > two
✓ three
```
## Preload hooks
Previously, beforeAll in a preload ran before the first file and
afterAll ran after the last file. Now, beforeAll will run at the start
of each file and afterAll will run at the end of each file. This
behaviour matches Jest and Vitest.
```ts
// preload.ts
beforeAll(() => console.log("preload: beforeAll"));
afterAll(() => console.log("preload: afterAll"));
```
```ts
// preload-ordering-1.test.ts
test("demonstration file 1", () => {});
```
```ts
// preload-ordering-2.test.ts
test("demonstration file 2", () => {});
```
```
$> bun-before test --preload=./preload preload-ordering
preload-ordering-1.test.ts:
preload: beforeAll
✓ demonstration file 1
preload-ordering-2.test.ts:
✓ demonstration file 2
preload: afterAll
```
```
$> bun-after test --preload=./preload preload-ordering
preload-ordering-1.test.ts:
preload: beforeAll
✓ demonstration file 1
preload: afterAll
preload-ordering-2.test.ts:
preload: beforeAll
✓ demonstration file 2
preload: afterAll
```
## Describe failures
Current behaviour is that when an error is thrown inside a describe
callback, none of the tests declared there will run. Now, describes
declared inside will also not run. The new behaviour matches the
behaviour of Jest and Vitest.
```ts
// describe-failures.test.ts
describe("erroring describe", () => {
test("this test does not run because its describe failed", () => {
expect(true).toBe(true);
});
describe("inner describe", () => {
console.log("does the inner describe callback get called?");
test("does the inner test run?", () => {
expect(true).toBe(true);
});
});
throw new Error("uh oh!");
});
```
Before, the inner describe callback would be called and the inner test
would run, although the outer test would not:
```
$> bun-before test describe-failures
describe-failures.test.ts:
does the inner describe callback get called?
# Unhandled error between tests
-------------------------------
11 | throw new Error("uh oh!");
^
error: uh oh!
-------------------------------
✓ erroring describe > inner describe > does the inner test run?
1 pass
0 fail
1 error
1 expect() calls
Ran 1 test across 1 file.
Exited with code [1]
```
Now, the inner describe callback is not called at all.
```
$> bun-after test describe-failures
describe-failures.test.ts:
# Unhandled error between tests
-------------------------------
11 | throw new Error("uh oh!");
^
error: uh oh!
-------------------------------
0 pass
0 fail
1 error
Ran 0 tests across 1 file.
Exited with code [1]
```
## Hook failures
Previously, a beforeAll failure would skip subsequent beforeAll()s, the
test, and the afterAll. Now, a beforeAll failure skips any subsequent
beforeAll()s and the test, but not the afterAll.
```js
beforeAll(() => {
throw new Error("before all: uh oh!");
});
test("my test", () => {
console.log("my test");
});
afterAll(() => console.log("after all"));
```
```
$> bun-before test hook-failures
Error: before all: uh oh!
$> bun-after test hook-failures
Error: before all: uh oh!
after all
```
Previously, an async beforeEach failure would still allow the test to
run. Now, an async beforeEach failure will prevent the test from running
```js
beforeEach(() => {
await 0;
throw "uh oh!";
});
it("the test", async () => {
console.log("does the test run?");
});
```
```
$> bun-before test async-beforeeach-failure
does the test run?
error: uh oh!
uh oh!
✗ the test
$> bun-after test async-beforeeach-failure
error: uh oh!
uh oh!
✗ the test
```
## Hook timeouts
Hooks will now time out, and can have their timeout configured in an
options parameter
```js
beforeAll(async () => {
await Bun.sleep(1000);
}, 500);
test("my test", () => {
console.log("ran my test");
});
```
```
$> bun-before test hook-timeouts
ran my test
Ran 1 test across 1 file. [1011.00ms]
$> bun-after test hook-timeouts
✗ my test [501.15ms]
^ a beforeEach/afterEach hook timed out for this test.
```
## Hook execution order
beforeAll will now execute before the tests in the scope, rather than
immediately when it is called.
```ts
describe("d1", () => {
beforeAll(() => {
console.log("<d1>");
});
test("test", () => {
console.log(" test");
});
afterAll(() => {
console.log("</d1>");
});
});
describe("d2", () => {
beforeAll(() => {
console.log("<d2>");
});
test("test", () => {
console.log(" test");
});
afterAll(() => {
console.log("</d2>");
});
});
```
```
$> bun-before test ./beforeall-ordering.test.ts
<d1>
<d2>
test
</d1>
test
</d2>
$> bun-after test ./beforeall-ordering.test.ts
<d1>
test
</d1>
<d2>
test
</d2>
```
## test inside test
test() inside test() now errors rather than silently failing. Support
for this may be added in the future.
```ts
test("outer", () => {
console.log("outer");
test("inner", () => {
console.log("inner");
});
});
```
```
$> bun-before test
outer
✓ outer [0.06ms]
1 pass
0 fail
Ran 1 test across 1 file. [8.00ms]
$> bun-after test
outer
1 | test("outer", () => {
2 | console.log("outer");
3 | test("inner", () => {
^
error: Cannot call test() inside a test. Call it inside describe() instead.
✗ outer [0.71ms]
0 pass
1 fail
```
## afterAll inside test
afterAll inside a test is no longer allowed
```ts
test("test 1", () => {
afterAll(() => console.log("afterAll"));
console.log("test 1");
});
test("test 2", () => {
console.log("test 2");
});
```
```
$> bun-before
test 1
✓ test 1 [0.05ms]
test 2
✓ test 2
afterAll
$> bun-after
error: Cannot call afterAll() inside a test. Call it inside describe() instead.
✗ test 1 [1.00ms]
test 2
✓ test 2 [0.20ms]
```
# Only inside only
Previously, an outer 'describe.only' would run all tests inside it even
if there was an inner 'test.only'. Now, only the innermost only tests
are executed.
```ts
describe.only("outer", () => {
test("one", () => console.log("should not run"));
test.only("two", () => console.log("should run"));
});
```
```
$> bun-before test
should not run
should run
$> bun-after test
should run
```
With no inner only, the outer only will still run all tests:
```ts
describe.only("outer", () => {
test("test 1", () => console.log("test 1 runs"));
test("test 2", () => console.log("test 2 runs"));
});
```
# Potential follow-up work
- [ ] for concurrent tests, display headers before console.log messages
saying which test it is for
- this will need async context or similar
- refActiveExecutionEntry should also be able to know the current test
even in test.concurrent
- [ ] `test("rerun me", () => { console.log("run one time!"); });`
`--rerun-each=3` <- this runs the first and third time but not the
second time. fix.
- [ ] should to cache the JSValue created from
DoneCallback.callAsFunction
- [ ] implement retry and rerun params for tests.
- [ ] Remove finalizer on ScopeFunctions.zig by storing the data in 3
jsvalues passed in bind rather than using a custom class. We should also
migrate off of the ClassGenerator for ScopeFunctions
- [ ] support concurrent limit, how many concurrent tests are allowed to
run at a time. ie `--concurrent-limit=25`
- [ ] flag to run tests in random order
- [ ] `test.failing` should have its own style in the same way
`test.todo` passing marks as 'todo' insetead of 'passing'. right now
it's `✓` which is confusing.
- [ ] remove all instances of bun.jsc.Jest.Jest.current
- [ ] test options should be in BunTestRoot
- [ ] we will need one global still, stored in the globalobject/vm/?.
but it should not be a Jest instance.
- [ ] consider allowing test() inside test(), as well as afterEach and
afterAll. could even allow describe() too. to do this we would switch
from indices to pointers and they would be in a linked list. they would
be allocated in memorypools for perf/locality. some special
consideration is needed for making sure repeated tests lose their
temporary items. this could also improve memory usage soomewhat.
- [ ] consider using a jsc Bound Function rather than CallbackWithArgs.
bound functions allow adding arguments and they are only one value for
GC instead of many. and this removes our unnecessary three copies.
- [ ] eliminate Strong.Safe. we should be using a C++ class instead.
- [ ] consider modifying the junit reporter to print the whole describe
tree at the end instead of trying to output as test results come in. and
move it into its own file.
- [ ] expect_call_count/expect_assertions is confusing. rename to
`expect_calls`, `assert_expect_calls`. or something.
- [ ] Should make line_no be an enum with a none option and a function
to get if line nombers are enabled
- [ ] looks like we don't need to use file_id anymore (remove
`bun.jsc.Jest.Jest.runner.?.getOrPutFile(file_path).file_id;`, store the
file path directly)
- [ ] 'dot' test reporter like vitest?
- [ ] `test.failing.if(false)` errors because it can't replace mode
'failing' with mode 'skip'. this should probably be allowed instead.
- [ ] trigger timeout termination exception for `while(true) {}`
- [ ] clean up unused callbacks. as soon as we advance to the next
execution group, we can fully clean out the previous one. sometimes
within an execution sequence we can do the same.
- clean by swapping held values with undefined
- [ ] structure cache for performance for donecallback/scopefunctions
- [ ] consider migrating CallbackWithArgs to be a bound function. the
length of the bound function can exclude the specified args.
- [ ] setting both result and maybe_skip is not ideal, maybe there
should be a function to do both at once?
- [ ] try using a linked list rather than arraylist for describe/test
children, see how it affects performance
- [ ] consider a memory pool for describescope/executionentry. test if
it improves performance.
- [ ] consider making RefDataValue methods return the reason for failure
rather than ?value. that way we can improve error messages. the reason
could be a string or it could be a defined error set
- [ ] instead of 'description orelse (unnamed)', let's have description
default to 'unnamed' and not free it if it === the global that defines
that
- [ ] Add a phase before ordering results that inherits properties to
the parents. (eg inherit only from the child and inherit has_callback
from the child. and has_callback can be on describe/test individually
rather than on base). then we won't have that happening in an init()
function (terrible!)
- [ ] this test was incidentally passing because resolves.pass() wasn't
waiting for promise
```
test("fetching with Request object - issue #1527", async () => {
const server = createServer((req, res) => {
res.end();
}).listen(0);
try {
await once(server, "listening");
const body = JSON.stringify({ foo: "bar" });
const request = new Request(`http://localhost:${server.address().port}`,
{
method: "POST",
body,
});
expect(fetch(request)).resolves.pass();
} finally {
server.closeAllConnections();
}
});
```
- [ ] the error "expect.assertions() is not supported in the describe
phase, in concurrent tests, between tests, or after test execution has
completed" is not very good. we should be able to identify which of
those it is and print the right error for the context
- [ ] consider: instead of storing weak pointers to BunTest, we can
instead give the instance an id and check that it is correct when
getting the current bun test instance from the ref
- [ ] auto_killer: add three layers of auto_killer:
- preload (includes file & test)
- file (includes test)
- test
- that way at the end of the test, we kill the test processes. at the
end of the file, we kill the file processes. at the end of all, we kill
anything remaining.
AsyncLocalStorage
- store active_id & refdatavalue. active_id is a replacement for the
above weak pointers thing. refdatavalue is for determining which test it
is. this probably fits in 2×u64
- use for auto_killer so timeouts can kill even in concurrent tests
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
This PR migrates all Docker container usage in tests from individual
`docker run` commands to a centralized Docker Compose setup. This makes
tests run **10x faster**, eliminates port conflicts, and provides a much
better developer experience.
## What is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker
applications. Instead of each test file managing its own containers with
complex `docker run` commands, we define all services once in a YAML
file and Docker Compose handles the orchestration.
## The Problem (Before)
```javascript
// Each test file managed its own container
const container = await Bun.spawn({
cmd: ["docker", "run", "-d", "-p", "0:5432", "postgres:15"],
// ... complex setup
});
```
**Issues:**
- Each test started its own container (30+ seconds for PostgreSQL tests)
- Containers were killed after each test (wasteful!)
- Random port conflicts between tests
- No coordination between test suites
- Docker configuration scattered across dozens of test files
## The Solution (After)
```javascript
// All tests share managed containers
const pg = await dockerCompose.ensure("postgres_plain");
// Container starts only if needed, returns connection info
```
**Benefits:**
- Containers start once and stay running (3 seconds for PostgreSQL tests
- **10x faster!**)
- Automatic port management (no conflicts)
- All services defined in one place
- Lazy loading (services only start when needed)
- Same setup locally and in CI
## What Changed
### New Infrastructure
- `test/docker/docker-compose.yml` - Defines all test services
- `test/docker/index.ts` - TypeScript API for managing services
- `test/docker/README.md` - Comprehensive documentation
- Configuration files and init scripts for services
### Services Migrated
| Service | Status | Tests |
|---------|--------|--------|
| PostgreSQL (plain, TLS, auth) | ✅ | All passing |
| MySQL (plain, native_password, TLS) | ✅ | All passing |
| S3/MinIO | ✅ | 276 passing |
| Redis/Valkey | ✅ | 25/26 passing* |
| Autobahn WebSocket | ✅ | 517 available |
*One Redis test was already broken before migration (reconnection test
times out)
### Key Features
- **Dynamic Ports**: Docker assigns available ports automatically (no
conflicts!)
- **Unix Sockets**: Proxy support for PostgreSQL and Redis Unix domain
sockets
- **Persistent Data**: Volumes for services that need data to survive
restarts
- **Health Checks**: Proper readiness detection for all services
- **Backward Compatible**: Fallback to old Docker method if needed
## Performance Improvements
| Test Suite | Before | After | Improvement |
|------------|--------|-------|-------------|
| PostgreSQL | ~30s | ~3s | **10x faster** |
| MySQL | ~25s | ~3s | **8x faster** |
| Redis | ~20s | ~2s | **10x faster** |
The improvements come from container reuse - containers start once and
stay running instead of starting/stopping for each test.
## How to Use
```typescript
import * as dockerCompose from "../../docker/index.ts";
test("database test", async () => {
// Ensure service is running (starts if needed)
const pg = await dockerCompose.ensure("postgres_plain");
// Connect using provided info
const client = new PostgresClient({
host: pg.host,
port: pg.ports[5432], // Mapped to random available port
});
});
```
## Testing
All affected test suites have been run and verified:
- `bun test test/js/sql/sql.test.ts` ✅
- `bun test test/js/sql/sql-mysql*.test.ts` ✅
- `bun test test/js/bun/s3/s3.test.ts` ✅
- `bun test test/js/valkey/valkey.test.ts` ✅
- `bun test test/js/web/websocket/autobahn.test.ts` ✅
## Documentation
Comprehensive documentation added in `test/docker/README.md` including:
- Detailed explanation of Docker Compose for beginners
- Architecture overview
- Usage examples
- Debugging guide
- Migration guide for adding new services
## Notes
- The Redis reconnection test that's skipped was already broken before
this migration. It's a pre-existing issue with the Redis client's
reconnection logic, not related to Docker changes.
- All tests that were passing before continue to pass after migration.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
### What does this PR do?
Enables async stack traces
### How did you verify your code works?
Added tests
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?
Fixes a TLS corruption bug in CONNECT proxy tunneling for HTTPS uploads.
When a large request body is sent over a tunneled TLS connection, the
client could interleave direct socket writes with previously buffered
encrypted bytes, causing TLS records to be emitted out-of-order. Some
proxies/upstreams detect this as a MAC mismatch and terminate with
SSLV3_ALERT_BAD_RECORD_MAC, which surfaced to users as ECONNRESET ("The
socket connection was closed unexpectedly").
This change makes `ProxyTunnel.write` preserve strict FIFO ordering of
encrypted bytes: if any bytes are already buffered, we enqueue new bytes
instead of calling `socket.write` directly. Flushing continues
exclusively via `onWritable`, which writes the buffered stream in order.
This eliminates interleaving and restores correctness for large proxied
HTTPS POST requests.
### How did you verify your code works?
- Local reproduction using a minimal script that POSTs ~20MB over HTTPS
via an HTTP proxy (CONNECT):
- Before: frequent ECONNRESET. With detailed SSL logs, upstream sent
`SSLV3_ALERT_BAD_RECORD_MAC`.
- After: requests complete successfully. Upstream responds as expected
- Verified small bodies and non-proxied HTTPS continue to work.
- Verified no linter issues and no unrelated code changes. The edit is
isolated to `src/http/ProxyTunnel.zig` and only affects the write path
to maintain TLS record ordering.
Rationale: TLS record boundaries must be preserved; mixing buffered data
with immediate writes risks fragmenting or reordering records under
backpressure. Enqueuing while buffered guarantees FIFO semantics and
avoids record corruption.
fixes:
#17434#18490 (false fix in corresponding pr)
---------
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>