Skip to content

Conversation

pfgithub
Copy link
Collaborator

@pfgithub pfgithub commented Sep 9, 2025

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.

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

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

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

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

// preload.ts
beforeAll(() => console.log("preload: beforeAll"));
afterAll(() => console.log("preload: afterAll"));
// preload-ordering-1.test.ts
test("demonstration file 1", () => {});
// 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.

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

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

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

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.

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.

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

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.

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:

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

Comment on lines 1852 to 1854
_ = file_start;
_ = file_end;
_ = is_last;
Copy link
Member

Choose a reason for hiding this comment

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

were these meant to be used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These used to be used

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (13)
src/bun.js/DeprecatedStrong.zig (3)

71-75: Optional.swap() inherits the swap() GC window.

This forwards to Strong.swap(), so fixing Strong.swap() will fix this too. No change needed here after the upstream fix.


26-30: Fix GC window and avoid double-protect in swap().

You unprotect the old value before the new value is protected, creating a gap where neither is protected. Also, calling .init() after protecting next would double‑protect it. Protect next first, tear down the current state manually, then install the new state without .init().

 pub fn swap(this: *Strong, safety_gpa: std.mem.Allocator, next: jsc.JSValue) jsc.JSValue {
-    const prev = this.#raw;
-    this.deinit();
-    this.* = .init(safety_gpa, next);
-    return prev;
+    const prev = this.#raw;
+
+    // 1) Close GC window by protecting the new value first.
+    next.protect();
+
+    // 2) Tear down current protection/safety (equivalent to deinit, but after next is protected).
+    this.#raw.unprotect();
+    if (enable_safety) if (this.#safety) |safety| {
+        bun.assert(@intFromEnum(safety.ptr.*.#raw) == 0xAEBCFA);
+        safety.ptr.*.#raw = @enumFromInt(0xFFFFFF);
+        bun.assert(safety.ref_count == 1);
+        safety.gpa.destroy(safety.ptr);
+    };
+
+    // 3) Install new state without calling .init() to avoid double-protect.
+    const new_safety: Safety = if (enable_safety)
+        .{ .ptr = bun.create(safety_gpa, Strong, .{ .#raw = @enumFromInt(0xAEBCFA), .#safety = null }), .gpa = safety_gpa, .ref_count = 1 };
+    this.* = .{ .#raw = next, .#safety = new_safety };
+    return prev;
 }

41-51: unref(): use pointer binding and clear #safety to avoid UAF.

Same copy-mutation bug as in ref(). Additionally, after destroying safety.ptr, this.#safety still points at freed memory.

 pub fn unref(this: *Strong) void {
     this.#raw.unprotect();
-    if (enable_safety) if (this.#safety) |safety| {
-        if (safety.ref_count == 1) {
+    if (enable_safety) if (this.#safety) |*safety| {
+        if (safety.ref_count == 1) {
             bun.assert(@intFromEnum(safety.ptr.*.#raw) == 0xAEBCFA);
             safety.ptr.*.#raw = @enumFromInt(0xFFFFFF);
             safety.gpa.destroy(safety.ptr);
-            return;
+            this.#safety = null; // prevent dangling reference
+            return;
         }
         safety.ref_count -= 1;
     };
 }
test/js/bun/test/bun_test.fixture.ts (2)

155-159: Test assertion count with timeout is contradictory

This always times out before the assertion can run; the expect.assertions(1) never gets verified. Adjust timeout/sleep so the assertion executes.

 test("expect.assertions combined with timeout", async () => {
   expect.assertions(1);
-  await Bun.sleep(100);
-}, 1);
+  await Bun.sleep(1);
+  expect(true).toBe(true);
+}, 10);

247-248: Empty test intentionally triggers afterEach failure — mark as failing for clarity

Make intent explicit so CI output is clearer.

-    test("fails when completion is not incremented", () => {});
+    test.failing("expected to fail: completion not incremented", () => {});
src/bun.js/bindings/JSValue.zig (1)

2375-2379: Wrap throwable host calls; don’t expose raw C++ functions

bindings.cpp uses throw scopes here; call through fromJSHostCall{Generic} to propagate exceptions, and add brief docs.

-    pub fn bind(this: JSValue, globalObject: *JSGlobalObject, bindThisArg: JSValue, name: *const bun.String, length: f64, args: []JSValue) bun.JSError!JSValue {
-        return bun.cpp.Bun__JSValue__bind(this, globalObject, bindThisArg, name, length, args.ptr, args.len);
-    }
-    pub const setPrototypeDirect = bun.cpp.Bun__JSValue__setPrototypeDirect;
+    /// Bind a callable to a receiver. May throw on allocation or if not callable.
+    pub fn bind(
+        this: JSValue,
+        globalObject: *JSGlobalObject,
+        bindThisArg: JSValue,
+        name: *const bun.String,
+        length: f64,
+        args: []JSValue,
+    ) bun.JSError!JSValue {
+        return bun.jsc.fromJSHostCallGeneric(
+            globalObject,
+            @src(),
+            bun.cpp.Bun__JSValue__bind,
+            .{ this, globalObject, bindThisArg, name, length, args.ptr, args.len },
+        );
+    }
+    /// Set object prototype directly. May throw.
+    pub fn setPrototypeDirect(this: JSValue, globalObject: *JSGlobalObject, proto: JSValue) bun.JSError!void {
+        return bun.jsc.fromJSHostCallGeneric(globalObject, @src(), bun.cpp.Bun__JSValue__setPrototypeDirect, .{ this, globalObject, proto });
+    }
#!/bin/bash
# Verify C++ sides can throw; if so, wrappers are required.
rg -n -C3 'Bun__JSValue__bind|Bun__JSValue__setPrototypeDirect' src/bun.js/bindings/bindings.cpp | rg -n 'DECLARE_THROW_SCOPE|RETURN_IF_EXCEPTION|propagate'
src/bun.js/api/Timer/EventLoopTimer.zig (1)

220-224: Use explicit @alignCast + @as for parent pointer; add a compile-time guard.

@fieldParentPtr("timer", self) should follow the established pattern with @alignCast + explicit cast to avoid misalignment UB and improve type safety. Also assert that BunTest.timer exists and is EventLoopTimer.

Apply this diff:

-            var container_strong = jsc.Jest.bun_test.BunTestPtr.cloneFromRawUnsafe(@fieldParentPtr("timer", self));
-            defer container_strong.deinit();
-            return jsc.Jest.bun_test.BunTest.bunTestTimeoutCallback(container_strong, now, vm);
+            comptime {
+                const BT = jsc.Jest.bun_test.BunTest;
+                if (!@hasField(BT, "timer")) @compileError("BunTest must have a 'timer' field");
+                if (@FieldType(BT, "timer") != Self) @compileError("BunTest.timer must be EventLoopTimer");
+            }
+            const bt = @as(*jsc.Jest.bun_test.BunTest, @alignCast(@fieldParentPtr("timer", self)));
+            var container_strong = jsc.Jest.bun_test.BunTestPtr.cloneFromRawUnsafe(bt);
+            defer container_strong.deinit();
+            return jsc.Jest.bun_test.BunTest.bunTestTimeoutCallback(container_strong, now, vm);
src/bun.js/bindings/bindings.cpp (2)

6760-6775: Require callable target and sanitize length before creating bound function

Creating a JSBoundFunction from a non-callable target risks assertion/crash in JSC; throw a TypeError if not callable.
Clamp and cast the double length to a non-negative unsigned to avoid UB/overflow from negative values.

Apply:

 CPP_DECL [[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue Bun__JSValue__bind(JSC::EncodedJSValue functionToBindEncoded, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue bindThisArgEncoded, const BunString* name, double length, JSC::EncodedJSValue* args, size_t args_len)
 {
     auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
 
     JSC::JSValue value = JSC::JSValue::decode(functionToBindEncoded);
-    if (!value.isCallable() || !value.isObject()) {
-        throwTypeError(globalObject, scope, "bind() called on non-callable"_s);
+    if (!value.isCallable()) {
+        throwTypeError(globalObject, scope, "bind() target must be callable"_s);
         RELEASE_AND_RETURN(scope, {});
     }
 
     SourceCode bindSourceCode = makeSource("bind"_s, SourceOrigin(), SourceTaintedOrigin::Untainted);
-    JSC::JSObject* valueObject = value.getObject();
+    JSC::JSObject* valueObject = value.getObject();
     JSC::JSValue bound = JSC::JSValue::decode(bindThisArgEncoded);
-    auto boundFunction = JSBoundFunction::create(globalObject->vm(), globalObject, valueObject, bound, ArgList(args, args_len), length, jsString(globalObject->vm(), name->toWTFString()), bindSourceCode);
+    const unsigned lengthUnsigned = length < 0 ? 0u : static_cast<unsigned>(length);
+    auto nameStr = name ? name->toWTFString() : WTF::emptyString();
+    auto boundFunction = JSBoundFunction::create(globalObject->vm(), globalObject, valueObject, bound, ArgList(args, args_len), lengthUnsigned, jsString(globalObject->vm(), nameStr), bindSourceCode);
+    RETURN_IF_EXCEPTION(scope, {});
     RELEASE_AND_RETURN(scope, JSC::JSValue::encode(boundFunction));
 }

6777-6787: Guard inputs to avoid null deref and enforce prototype type

Calling setPrototypeDirect on a non-object will dereference a null JSObject. Also ensure prototype is object or null.

Apply:

 CPP_DECL [[ZIG_EXPORT(check_slow)]] void Bun__JSValue__setPrototypeDirect(JSC::EncodedJSValue valueEncoded, JSC::EncodedJSValue prototypeEncoded, JSC::JSGlobalObject* globalObject)
 {
     auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
 
     JSC::JSValue value = JSC::JSValue::decode(valueEncoded);
     JSC::JSValue prototype = JSC::JSValue::decode(prototypeEncoded);
-    JSC::JSObject* valueObject = value.getObject();
-    valueObject->setPrototypeDirect(globalObject->vm(), prototype);
-    RELEASE_AND_RETURN(scope, );
-    return;
+    if (!value.isObject()) {
+        throwTypeError(globalObject, scope, "setPrototypeDirect() target must be an object"_s);
+        RELEASE_AND_RETURN(scope, );
+    }
+    if (!(prototype.isNull() || prototype.isObject())) {
+        throwTypeError(globalObject, scope, "prototype must be an object or null"_s);
+        RELEASE_AND_RETURN(scope, );
+    }
+    JSC::JSObject* valueObject = value.getObject();
+    valueObject->setPrototypeDirect(globalObject->vm(), prototype);
+    RELEASE_AND_RETURN(scope, );
 }
src/bun.js/test/Collection.zig (1)

86-97: Fix: scope reset uses the wrong value (no-op today).

runOneCompleted should restore active_scope from data.collection.active_scope, but it assigns prev_scope = this.active_scope, making the reset a no-op.

Apply:

-    const prev_scope: *DescribeScope = switch (data) {
-        .collection => this.active_scope,
+    const prev_scope: *DescribeScope = switch (data) {
+        .collection => |c| c.active_scope,
         else => {
             bun.assert(false); // this probably can't happen
             return;
         },
     };
src/bun.js/test/DoneCallback.zig (2)

1-3: [Duplicate] Add struct declaration and document ref field.

Same concern as previously noted: wrap fields in a struct and document ref ownership/lifetime.


1-3: Blocker: Missing struct wrapper for DoneCallback fields.

The file declares fields at top-level; these must live inside a struct. Wrap them and export the type to avoid undefined type usage.

Apply:

-/// value = not called yet. null = done already called, no-op.
-ref: ?*bun_test.BunTest.RefData,
-called: bool = false,
+pub const DoneCallback = struct {
+  /// value = not called yet. null = done already called, no-op.
+  ref: ?*bun_test.BunTest.RefData,
+  called: bool = false,
+};
src/bun.js/test/ScopeFunctions.zig (1)

106-117: Pre-allocate and avoid OOM crashing in .each spread.

Propagate allocation errors instead of handleOom and use appendAssumeCapacity after reserving.

Apply:

-                // Spread array as args_list (matching Jest & Vitest)
-                bun.handleOom(args_list.ensureUnusedCapacity(try item.getLength(globalThis)));
+                // Spread array as args_list (matching Jest & Vitest)
+                const item_len = try item.getLength(globalThis);
+                try args_list.ensureUnusedCapacity(item_len);
 
                 var item_iter = try item.arrayIterator(globalThis);
                 var idx: usize = 0;
                 while (try item_iter.next()) |array_item| : (idx += 1) {
-                    bun.handleOom(args_list.append(.init(bunTest.gpa, array_item)));
+                    args_list.appendAssumeCapacity(.init(bunTest.gpa, array_item));
                 }
🧹 Nitpick comments (13)
src/bun.js/DeprecatedStrong.zig (1)

35-39: Optional guard (non-blocking): skip protect/unprotect for non-cells.

If #raw can be non-cell (via initNonCell/.zero), consider early-returning in ref()/unref() to avoid no-op JSC calls.

 pub fn ref(this: *Strong) void {
+    if (!this.#raw.isCell()) return;
     this.#raw.protect();
     if (enable_safety) if (this.#safety) |*safety| {
         safety.ref_count += 1;
     };
 }
 
 pub fn unref(this: *Strong) void {
+    if (!this.#raw.isCell()) return;
     this.#raw.unprotect();
     if (enable_safety) if (this.#safety) |*safety| {
         if (safety.ref_count == 1) {
             bun.assert(@intFromEnum(safety.ptr.*.#raw) == 0xAEBCFA);
             safety.ptr.*.#raw = @enumFromInt(0xFFFFFF);
             safety.gpa.destroy(safety.ptr);
             this.#safety = null;
             return;
         }
         safety.ref_count -= 1;
     };
 }

Also applies to: 41-43

test/harness.ts (2)

906-919: Unused option: archs

The archs option is accepted but never used. Either enforce an architecture allowlist (skip or todo when not matched) or drop the parameter to avoid dead API.


1000-1003: Include supported images in error for faster triage

Augment the error with the known keys to guide test authors.

-    throw new Error(
-      `Image "${image}" is not configured in docker-compose.yml. All test containers must use docker-compose.`,
-    );
+    throw new Error(
+      `Image "${image}" is not configured in docker-compose.yml. All test containers must use docker-compose. Known images: ${Object.keys(services).join(", ")}`
+    );
test/js/bun/test/bun_test.fixture.ts (1)

95-98: Fix typo in comment

“thisa” → “this”.

-    // this is a technical limitation of not using async context. in the future, we could allow thisa
+    // this is a technical limitation of not using async context. In the future, we could allow this.
src/bun.js/test/Collection.zig (1)

110-122: Consider using move semantics instead of copy+clear pattern

While the current implementation correctly calls deinit() on items from failed scopes (line 116), the pattern of copying items and clearing the source list could be more idiomatic. Consider using pop/move semantics to make ownership transfer clearer.

-    // append queued callbacks, in reverse order because items will be pop()ed from the end
-    var i: usize = this.current_scope_callback_queue.items.len;
-    while (i > 0) {
-        i -= 1;
-        const item = &this.current_scope_callback_queue.items[i];
-        if (item.new_scope.failed) { // if there was an error in the describe callback, don't run any describe callbacks in this scope
-            item.deinit();
-        } else {
-            bun.handleOom(this.describe_callback_queue.append(item.*));
-        }
-    }
-    this.current_scope_callback_queue.clearRetainingCapacity();
+    // Move queued callbacks (reverse order preserved by processing from end).
+    while (this.current_scope_callback_queue.popOrNull()) |item| {
+        if (item.new_scope.failed) { // do not queue callbacks from a failed scope
+            var moved = item;
+            moved.deinit();
+        } else {
+            bun.handleOom(this.describe_callback_queue.append(item));
+        }
+    }
src/bun.js/test/DoneCallback.zig (1)

23-29: Initialize all fields explicitly.

Ensure called defaults to false on init for clarity.

Apply:

-    done_callback.* = .{ .ref = null };
+    done_callback.* = .{ .ref = null, .called = false };
src/bun.js/test/Order.zig (1)

125-135: Edge-case: extending previous group requires contiguous sequences only.

Guard is good. Consider asserting contiguity to catch planner bugs in debug builds.

Apply:

         if (this.previous_group_was_concurrent) {
             // extend the previous group to include this sequence
-            if (previous_group.tryExtend(sequences_start, sequences_end)) return;
+            if (previous_group.tryExtend(sequences_start, sequences_end)) return
+            else bun.debugAssert(previous_group.sequence_end == sequences_start);
         }
src/cli/test_command.zig (2)

42-61: Cosmetic: reset styles for skip glyphs.

Two branches leave <d> active. Close with <r> for consistent formatting.

Apply:

-            .skip => Output.prettyFmt("<r><yellow>»<d>", emoji_or_color),
+            .skip => Output.prettyFmt("<r><yellow>»<r>", emoji_or_color),
             .todo => Output.prettyFmt("<r><magenta>✎<r>", emoji_or_color),
         },
         else => switch (status.basicResult()) {
             .pending => Output.prettyFmt("<r><d>(pending)<r>", emoji_or_color),
             .pass => Output.prettyFmt("<r><green>(pass)<r>", emoji_or_color),
             .fail => Output.prettyFmt("<r><red>(fail)<r>", emoji_or_color),
-            .skip => Output.prettyFmt("<r><yellow>(skip)<d>", emoji_or_color),
+            .skip => Output.prettyFmt("<r><yellow>(skip)<r>", emoji_or_color),
             .todo => Output.prettyFmt("<r><magenta>(todo)<r>", emoji_or_color),
         },

715-833: JUnit nesting logic: ensure suites stack stays in sync under errors.

When closing/creating suites, failures in beginTestSuiteWithLine/endTestSuite could desync the stack. Consider guarding with try/errdefer and short-circuiting on OOM to avoid malformed XML.

src/bun.js/test/Execution.zig (1)

192-219: Timeout kill policy: limit to single-sequence groups only.

Pragmatic safeguard; align with concurrent semantics. Consider logging which test was terminated for easier debugging.

src/bun.js/test/ScopeFunctions.zig (2)

116-123: Avoid handleOom for single append too.

Propagate instead of crashing.

Apply:

-                bun.handleOom(args_list.append(.init(bunTest.gpa, item)));
+                try args_list.append(.init(bunTest.gpa, item));

164-242: Enforce collection-phase only usage with clearer errors.

Messages are good. Consider including file/scope name for easier triage.

src/bun.js/test/bun_test.zig (1)

398-416: Timer callback: ensure exceptions don’t suppress re-arming.

You correctly disarm and re-run; the err paths call onUncaughtException. Consider a finally-style re-arm check if future changes early-return before run().

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 99b41b5 and 3a89f78.

⛔ Files ignored due to path filters (1)
  • test/js/bun/test/__snapshots__/bun_test.fixture.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (19)
  • src/allocators/allocation_scope.zig (1 hunks)
  • src/bun.js/DeprecatedStrong.zig (1 hunks)
  • src/bun.js/Strong.zig (1 hunks)
  • src/bun.js/api/Timer/EventLoopTimer.zig (5 hunks)
  • src/bun.js/bindings/JSValue.zig (1 hunks)
  • src/bun.js/bindings/bindings.cpp (1 hunks)
  • src/bun.js/bindings/generated_classes_list.zig (1 hunks)
  • src/bun.js/test/Collection.zig (1 hunks)
  • src/bun.js/test/DoneCallback.zig (1 hunks)
  • src/bun.js/test/Execution.zig (1 hunks)
  • src/bun.js/test/Order.zig (1 hunks)
  • src/bun.js/test/ScopeFunctions.zig (1 hunks)
  • src/bun.js/test/bun_test.zig (1 hunks)
  • src/bun.js/test/debug.zig (1 hunks)
  • src/bun.js/test/expect.zig (14 hunks)
  • src/cli/test_command.zig (15 hunks)
  • test/harness.ts (4 hunks)
  • test/js/bun/test/bun_test.fixture.ts (1 hunks)
  • test/js/bun/test/bun_test.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/bun.js/Strong.zig
  • src/bun.js/bindings/generated_classes_list.zig
🧰 Additional context used
📓 Path-based instructions (16)
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • test/harness.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
test/js/bun/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • test/harness.ts
test/js/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests for specific features under test/js/ by module

Files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • test/harness.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/js/bun/test/bun_test.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/js/bun/test/bun_test.test.ts
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/bun.js/test/debug.zig
  • src/bun.js/test/Order.zig
  • src/bun.js/test/Collection.zig
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/test/expect.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/Timer/EventLoopTimer.zig
  • src/bun.js/test/bun_test.zig
  • src/allocators/allocation_scope.zig
  • src/bun.js/test/DoneCallback.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/bun.js/test/debug.zig
  • src/bun.js/test/Order.zig
  • src/bun.js/test/Collection.zig
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/test/expect.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/Timer/EventLoopTimer.zig
  • src/bun.js/test/bun_test.zig
  • src/allocators/allocation_scope.zig
  • src/bun.js/test/DoneCallback.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/test/debug.zig
  • src/bun.js/test/Order.zig
  • src/bun.js/test/Collection.zig
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/test/expect.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/Timer/EventLoopTimer.zig
  • src/bun.js/test/bun_test.zig
  • src/bun.js/test/DoneCallback.zig
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/bindings.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/bindings.cpp
**/*.{cpp,cc,cxx,h,hpp,hxx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format C/C++ sources/headers with clang-format (bun run clang-format)

Files:

  • src/bun.js/bindings/bindings.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: For JS classes with a public constructor, implement Foo, FooPrototype, and FooConstructor (JSC patterns)
Define class properties using HashTableValue arrays in JSC bindings
Add iso subspaces for classes with C++ fields in JSC bindings
Cache structures in ZigGlobalObject for JSC-bound classes

Files:

  • src/bun.js/bindings/bindings.cpp
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/Timer/EventLoopTimer.zig
🧠 Learnings (58)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Name test files `*.test.ts` and use `bun:test`
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: For platform-specific changes, run bun run zig:check-all to compile on all platforms
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/bun.js/test/expect.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/api/Timer/EventLoopTimer.zig
  • src/bun.js/test/bun_test.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*-fixture.ts : Name test fixture files that are spawned by tests with the suffix `-fixture.ts`

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
  • src/bun.js/test/bun_test.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/test/bun_test.zig
  • test/harness.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Name test files `*.test.ts` and use `bun:test`

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Do not set explicit test timeouts; rely on Bun’s built-in timeouts

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Use `describe` blocks for grouping, `describe.each` for parameterized tests, snapshots with `toMatchSnapshot`, and lifecycle hooks (`beforeAll`, `beforeEach`, `afterEach`); track resources for cleanup in `afterEach`

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • src/bun.js/test/expect.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
📚 Learning: 2025-09-20T03:39:41.748Z
Learnt from: pfgithub
PR: oven-sh/bun#22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.748Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/bun.js/test/expect.zig
  • src/cli/test_command.zig
  • src/bun.js/test/bun_test.zig
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Do not write flaky tests: never wait for arbitrary time; wait for conditions instead

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-09-03T01:30:58.001Z
Learnt from: markovejnovic
PR: oven-sh/bun#21728
File: test/js/valkey/valkey.test.ts:264-271
Timestamp: 2025-09-03T01:30:58.001Z
Learning: For test/js/valkey/valkey.test.ts PUB/SUB tests, avoid arbitrary sleeps and async-forEach. Instead, resolve a Promise from the subscriber callback when the expected number of messages is observed and await it with a bounded timeout (e.g., withTimeout + Promise.withResolvers) to account for Redis server→subscriber propagation.

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/{dev/*.test.ts,dev-and-prod.ts} : Explicitly assert client console logs with c.expectMessage; unasserted logs should not appear

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : In error tests, assert non-zero exit codes for failing processes and use `toThrow` for synchronous errors

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • src/bun.js/test/Execution.zig
📚 Learning: 2025-09-20T00:58:38.021Z
Learnt from: markovejnovic
PR: oven-sh/bun#22568
File: test/js/valkey/valkey.test.ts:561-564
Timestamp: 2025-09-20T00:58:38.021Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods (like ctx.redis.set(), ctx.redis.unsubscribe(), etc.) - Bun's Redis client implementation differs from Node.js and can throw synchronously even for async methods. The maintainer has explicitly requested to stop looking at this error pattern.

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Use `using`/`await using` with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • src/cli/test_command.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Prefer `async/await` in tests; for a single callback, use `Promise.withResolvers()`

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Prefer data-driven tests (e.g., test.each) to reduce boilerplate

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : Never write tests that merely assert absence of "panic" or "uncaught exception" in output

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/node/test/** : Vendored Node.js tests under test/js/node/test/ are exceptions and do not follow Bun’s test style

Applied to files:

  • test/js/bun/test/bun_test.fixture.ts
  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, use normalizeBunSnapshot when asserting snapshots

Applied to files:

  • test/js/bun/test/bun_test.test.ts
  • src/bun.js/test/expect.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/test/bun_test.zig
  • test/harness.ts
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • test/js/bun/test/bun_test.test.ts
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead

Applied to files:

  • test/js/bun/test/bun_test.test.ts
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : Prefer snapshot tests over exact stdout equality assertions

Applied to files:

  • test/js/bun/test/bun_test.test.ts
  • src/bun.js/test/expect.zig
📚 Learning: 2025-08-30T00:07:54.573Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-08-30T00:07:54.573Z
Learning: Applies to src/**/*.zig : Implement debug logs in Zig using `const log = bun.Output.scoped(.${SCOPE}, false);` and invoking `log("...", .{})`

Applied to files:

  • src/bun.js/test/debug.zig
  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/test/debug.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/cli/test_command.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/bun_test.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to **/*.zig : In Zig, manage memory carefully with allocators and use defer for cleanup

Applied to files:

  • src/bun.js/test/Collection.zig
  • src/bun.js/test/ScopeFunctions.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JSC-bound classes

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : For JS classes with a public constructor, implement Foo, FooPrototype, and FooConstructor (JSC patterns)

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for classes with C++ fields in JSC bindings

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Ensure all public V8 API functions are marked with BUN_EXPORT for symbol visibility

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add new V8 API mangled symbols (without leading underscore) to src/symbols.txt

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/bun_test.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add new V8 API mangled symbols (with leading underscore and semicolons) to src/symbols.dyn

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/**/*.{h,cpp} : Use localToJSValue() when converting V8 handles to JSC values before performing JSC operations

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/bun_test.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/bun_test.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/bun_test.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.cpp : Provide an extern "C" Bun__<Type>__toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In finalize() for objects holding JS references, release them using .deref() before destroy

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
  • src/bun.js/test/bun_test.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
  • src/bun.js/DeprecatedStrong.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • src/bun.js/test/ScopeFunctions.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Always implement proper cleanup in deinit() and finalize() for JS-exposed types

Applied to files:

  • src/bun.js/DeprecatedStrong.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: For platform-specific changes, run bun run zig:check-all to compile on all platforms

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/test/DoneCallback.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Define class properties using HashTableValue arrays in JSC bindings

Applied to files:

  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-09-02T18:18:00.835Z
Learnt from: taylordotfish
PR: oven-sh/bun#22227
File: src/allocators/allocation_scope.zig:104-118
Timestamp: 2025-09-02T18:18:00.835Z
Learning: In AllocationScope code, accessing self.allocations from LockedState results in a compile-time error, not a runtime panic, because LockedState doesn't have an allocations field - the field exists on the History struct which is accessed via self.history.allocations.

Applied to files:

  • src/allocators/allocation_scope.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
PR: oven-sh/bun#21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun’s Zig code, `.js_undefined` is a valid and preferred JSValue literal for “undefined” (e.g., resolving JSPromise). Do not refactor usages to `jsc.JSValue.jsUndefined()`, especially in src/valkey/js_valkey_functions.zig unsubscribe().

Applied to files:

  • src/bun.js/test/DoneCallback.zig
🧬 Code graph analysis (3)
test/js/bun/test/bun_test.fixture.ts (1)
src/js/node/test.ts (2)
  • beforeEach (151-160)
  • afterEach (162-171)
test/js/bun/test/bun_test.test.ts (1)
test/harness.ts (1)
  • normalizeBunSnapshot (1802-1833)
src/bun.js/bindings/bindings.cpp (2)
src/bun.js/bindings/BunString.cpp (15)
  • ZIG_EXPORT (47-50)
  • ZIG_EXPORT (52-55)
  • ZIG_EXPORT (56-59)
  • ZIG_EXPORT (61-66)
  • ZIG_EXPORT (68-73)
  • ZIG_EXPORT (75-85)
  • ZIG_EXPORT (87-106)
  • ZIG_EXPORT (137-150)
  • ZIG_EXPORT (198-206)
  • ZIG_EXPORT (358-367)
  • ZIG_EXPORT (369-378)
  • ZIG_EXPORT (402-413)
  • ZIG_EXPORT (415-429)
  • ZIG_EXPORT (431-441)
  • ZIG_EXPORT (443-451)
src/bun.js/bindings/JSDOMExceptionHandling.cpp (2)
  • throwTypeError (220-223)
  • throwTypeError (220-220)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format

Comment on lines +35 to +39
pub fn ref(this: *Strong) void {
this.#raw.protect();
if (enable_safety) if (this.#safety) |safety| {
safety.ref_count += 1;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

ref(): mutating a copy; use pointer binding for #safety.

|safety| binds by value; safety.ref_count += 1 updates a copy and is lost. Use |*safety|.

 pub fn ref(this: *Strong) void {
     this.#raw.protect();
-    if (enable_safety) if (this.#safety) |safety| {
+    if (enable_safety) if (this.#safety) |*safety| {
         safety.ref_count += 1;
     };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn ref(this: *Strong) void {
this.#raw.protect();
if (enable_safety) if (this.#safety) |safety| {
safety.ref_count += 1;
};
pub fn ref(this: *Strong) void {
this.#raw.protect();
if (enable_safety) if (this.#safety) |*safety| {
safety.ref_count += 1;
};
}
🤖 Prompt for AI Agents
In src/bun.js/DeprecatedStrong.zig around lines 35 to 39, the closure `|safety|`
binds the optional by value so `safety.ref_count += 1` mutates a copy and is
lost; change the optional binding to a pointer (`|*safety|`) and then increment
the pointed-to ref_count (use the pointer binding form in the if-statement and
update the ref_count through that pointer) so the mutation affects the original
safety struct.

return runner.bun_test_root.cloneActiveFile();
}

pub const DoneCallback = @import("./DoneCallback.zig");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Import pattern may cause cycles.

pub const DoneCallback = @import("./DoneCallback.zig"); combined with DoneCallback.zig referencing bun_test.DoneCallback risks an import cycle. After adopting the struct wrapper and removing the alias in DoneCallback.zig, this should be safe. Please confirm build on all platforms.


🏁 Script executed:

#!/bin/bash
# Verify no import cycles and that generated symbols resolve
fd -t f -e zig | xargs -I{} sed -n '1,80p' {} >/dev/null
# Build headers of the two modules to force semantic check (lightweight)
rg -nP '(@import\(".*DoneCallback\.zig"\)|bun_test\.DoneCallback)' -n -C2

Length of output: 1061


Break import cycle: DoneCallback ↔ bun_test

src/bun.js/test/bun_test.zig:6 declares pub const DoneCallback = @import("./DoneCallback.zig"); while src/bun.js/test/DoneCallback.zig:45 contains const DoneCallback = bun_test.DoneCallback; — this creates a circular import. Remove the alias in DoneCallback.zig (export the struct wrapper or have DoneCallback.zig directly expose the type) to break the cycle and verify with bun run zig:check-all on all platforms.

🤖 Prompt for AI Agents
In src/bun.js/test/bun_test.zig around line 6, the line `pub const DoneCallback
= @import("./DoneCallback.zig");` participates in a circular import because
src/bun.js/test/DoneCallback.zig aliases `const DoneCallback =
bun_test.DoneCallback;`; to fix, remove the alias from DoneCallback.zig and
instead have DoneCallback.zig directly declare and export the callback
wrapper/type (or export the struct wrapper under a concrete name) so
bun_test.zig can import it without referencing bun_test, update any callsites to
use the exported name from DoneCallback.zig, and then run `bun run
zig:check-all` on all platforms to verify the cycle is resolved.

Comment on lines +9 to +17
pub fn finalize(
this: *DoneCallback,
) callconv(.C) void {
groupLog.begin(@src());
defer groupLog.end();

if (this.ref) |ref| ref.deref();
VirtualMachine.get().allocator.destroy(this);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Finalize should call deinit() and use bun.destroy().

Avoid direct allocator.destroy(). Ensure ref is cleared safely.

Apply:

 pub fn finalize(
     this: *DoneCallback,
 ) callconv(.C) void {
     groupLog.begin(@src());
     defer groupLog.end();

-    if (this.ref) |ref| ref.deref();
-    VirtualMachine.get().allocator.destroy(this);
+    this.deinit();
+    bun.destroy(this);
 }
+
+pub fn deinit(this: *DoneCallback) void {
+    if (this.ref) |ref| ref.deref();
+    this.ref = null;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn finalize(
this: *DoneCallback,
) callconv(.C) void {
groupLog.begin(@src());
defer groupLog.end();
if (this.ref) |ref| ref.deref();
VirtualMachine.get().allocator.destroy(this);
}
pub fn finalize(
this: *DoneCallback,
) callconv(.C) void {
groupLog.begin(@src());
defer groupLog.end();
this.deinit();
bun.destroy(this);
}
pub fn deinit(this: *DoneCallback) void {
if (this.ref) |ref| ref.deref();
this.ref = null;
}
🤖 Prompt for AI Agents
In src/bun.js/test/DoneCallback.zig around lines 9–17, the finalize
implementation should call the type's deinit, avoid direct allocator.destroy,
and clear ref safely: invoke the DoneCallback deinit on this, if this.ref is set
call ref.deref() and then clear this.ref (set to null/undefined), and finally
release the object via bun.destroy(...) instead of
VirtualMachine.get().allocator.destroy(this); ensure ordering so deinit runs
before bun.destroy.

Comment on lines +43 to +46
const bun_test = jsc.Jest.bun_test;
const BunTest = bun_test.BunTest;
const DoneCallback = bun_test.DoneCallback;
const groupLog = bun_test.debug.group;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove self-referential alias to avoid import cycles.

const DoneCallback = bun_test.DoneCallback; creates a circular reference with bun_test importing this file.

Apply:

-const BunTest = bun_test.BunTest;
-const DoneCallback = bun_test.DoneCallback;
+const BunTest = bun_test.BunTest;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const bun_test = jsc.Jest.bun_test;
const BunTest = bun_test.BunTest;
const DoneCallback = bun_test.DoneCallback;
const groupLog = bun_test.debug.group;
const bun_test = jsc.Jest.bun_test;
const BunTest = bun_test.BunTest;
const groupLog = bun_test.debug.group;
🤖 Prompt for AI Agents
In src/bun.js/test/DoneCallback.zig around lines 43 to 46, the line creating a
self-referential alias ("const DoneCallback = bun_test.DoneCallback;") creates
an import cycle; remove that alias and either (a) reference the type/value
directly as bun_test.DoneCallback where used, or (b) import DoneCallback from
the original module that defines it (not from bun_test) so the file no longer
re-exports/references itself. Ensure all usages are updated to the chosen
approach.

Comment on lines +31 to +38
pub fn setFailureSkipTo(aor: AllOrderResult, this: *Order) void {
if (aor.start == 0 and aor.end == 0) return;
const skip_to = this.groups.items.len;
for (this.groups.items[aor.start..aor.end]) |*group| {
group.failure_skip_to = skip_to;
}
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: beforeAll failure skip target points to children, not afterAll.

setFailureSkipTo() sets failure_skip_to to this.groups.items.len at call-time. When invoked before scheduling afterAll, failures skip to the first child instead of the first afterAll.

Apply:

-pub const AllOrderResult = struct {
+pub const AllOrderResult = struct {
     start: usize,
     end: usize,
     pub const empty: AllOrderResult = .{ .start = 0, .end = 0 };
-    pub fn setFailureSkipTo(aor: AllOrderResult, this: *Order) void {
-        if (aor.start == 0 and aor.end == 0) return;
-        const skip_to = this.groups.items.len;
+    pub fn setFailureSkipTo(aor: AllOrderResult, this: *Order, skip_to: usize) void {
+        if (aor.start == 0 and aor.end == 0) return;
         for (this.groups.items[aor.start..aor.end]) |*group| {
             group.failure_skip_to = skip_to;
         }
     }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn setFailureSkipTo(aor: AllOrderResult, this: *Order) void {
if (aor.start == 0 and aor.end == 0) return;
const skip_to = this.groups.items.len;
for (this.groups.items[aor.start..aor.end]) |*group| {
group.failure_skip_to = skip_to;
}
}
};
pub const AllOrderResult = struct {
start: usize,
end: usize,
pub const empty: AllOrderResult = .{ .start = 0, .end = 0 };
pub fn setFailureSkipTo(aor: AllOrderResult, this: *Order, skip_to: usize) void {
if (aor.start == 0 and aor.end == 0) return;
for (this.groups.items[aor.start..aor.end]) |*group| {
group.failure_skip_to = skip_to;
}
}
};
🤖 Prompt for AI Agents
In src/bun.js/test/Order.zig around lines 31-38, setFailureSkipTo currently
captures skip_to as this.groups.items.len at call-time which makes beforeAll
failures jump to the first child instead of the first afterAll; change the
calculation to point past the future afterAll entry (e.g., set skip_to to
this.groups.items.len + 1 or otherwise compute the index of the first afterAll
that will be appended) so failure_skip_to targets the afterAll group rather than
the children.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/bun.js/test/Collection.zig (2)

110-121: Memory management issue with queue operations.

The current implementation copies items from current_scope_callback_queue to describe_callback_queue without properly transferring ownership, which causes memory leaks. The items are copied via append(item.*) but the original queue is cleared without deinitializing the copied items.

Based on the defer pattern for memory management and explicit memory freeing requirements, the loop should move items rather than copy them:

-    // append queued callbacks, in reverse order because items will be pop()ed from the end
-    var i: usize = this.current_scope_callback_queue.items.len;
-    while (i > 0) {
-        i -= 1;
-        const item = &this.current_scope_callback_queue.items[i];
-        if (item.new_scope.failed) { // if there was an error in the describe callback, don't run any describe callbacks in this scope
-            item.deinit();
-        } else {
-            bun.handleOom(this.describe_callback_queue.append(item.*));
-        }
-    }
-    this.current_scope_callback_queue.clearRetainingCapacity();
+    // Move queued callbacks (reverse order preserved by popping from the end).
+    while (this.current_scope_callback_queue.popOrNull()) |moved| {
+        if (moved.new_scope.failed) { // do not queue callbacks from a failed scope
+            var tmp = moved;
+            tmp.deinit(buntest.gpa);
+        } else {
+            bun.handleOom(this.describe_callback_queue.append(moved));
+        }
+    }

86-97: Scope reset uses incorrect value.

The scope reset logic assigns prev_scope to this.active_scope, making the subsequent reset a no-op. It should read the saved scope from data.collection.active_scope instead.

Apply this fix:

-    const prev_scope: *DescribeScope = switch (data) {
-        .collection => this.active_scope,
+    const prev_scope: *DescribeScope = switch (data) {
+        .collection => |c| c.active_scope,
         else => blk: {
             bun.assert(false); // this probably can't happen
             break :blk this.active_scope;
         },
     };
src/bun.js/test/expect/toMatchSnapshot.zig (1)

15-18: LGTM: correct signature + inlined bunTest() guard.

Fixes the previously noted wrong matcher name and removes the unused binding.

🧹 Nitpick comments (3)
src/bun.js/test/Collection.zig (1)

44-55: Collection deinit has inconsistent allocator access.

The deinit method accesses buntest.gpa through the bunTest() helper, but the method signature doesn't clearly indicate this dependency. This could lead to confusion about resource ownership.

Consider making the allocator dependency explicit:

-pub fn deinit(this: *Collection) void {
-    this.root_scope.destroy(this.bunTest().gpa);
+pub fn deinit(this: *Collection, gpa: std.mem.Allocator) void {
+    this.root_scope.destroy(gpa);
     for (this.describe_callback_queue.items) |*item| {
-        item.deinit();
+        item.deinit(gpa);
     }
     this.describe_callback_queue.deinit();
     for (this.current_scope_callback_queue.items) |*item| {
-        item.deinit();
+        item.deinit(gpa);
     }
     this.current_scope_callback_queue.deinit();
     this.filter_buffer.deinit();
 }
src/bun.js/test/expect/toMatchSnapshot.zig (2)

30-31: Use getSignature on all error paths for consistent UX.

Empty signature ("") omits usage hints. Reuse the same signature as Line 35.

Apply:

@@
-            } else {
-                return this.throw(globalThis, "", "\n\nMatcher error: Expected first argument to be a string or object\n", .{});
+            } else {
+                const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
+                return this.throw(globalThis, signature, "\n\nMatcher error: Expected first argument to be a string or object\n", .{});
             }
@@
-            } else {
-                return this.throw(globalThis, "", "\n\nMatcher error: Expected second argument to be a string\n", .{});
+            } else {
+                const signature = comptime getSignature("toMatchSnapshot", "<green>properties<r><d>, <r>hint", false);
+                return this.throw(globalThis, signature, "\n\nMatcher error: Expected second argument to be a string\n", .{});
             }

Also applies to: 44-45


1-1: Param name nit: prefer globalObject per guidelines.

Minor naming consistency with src/bun.js/**/*.zig conventions.

-pub fn toMatchSnapshot(this: *Expect, globalThis: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
+pub fn toMatchSnapshot(this: *Expect, globalObject: *JSGlobalObject, callFrame: *CallFrame) bun.JSError!JSValue {
-    defer this.postMatch(globalThis);
-    const thisValue = callFrame.this();
+    defer this.postMatch(globalObject);
+    const thisValue = callFrame.this();
@@
-                try arguments[0].toZigString(&hint_string, globalThis);
+                try arguments[0].toZigString(&hint_string, globalObject);
@@
-                try arguments[1].toZigString(&hint_string, globalThis);
+                try arguments[1].toZigString(&hint_string, globalObject);
@@
-    const value: JSValue = try this.getValue(globalThis, thisValue, "toMatchSnapshot", "<green>properties<r><d>, <r>hint");
-    return this.snapshot(globalThis, value, property_matchers, hint.slice(), "toMatchSnapshot");
+    const value: JSValue = try this.getValue(globalObject, thisValue, "toMatchSnapshot", "<green>properties<r><d>, <r>hint");
+    return this.snapshot(globalObject, value, property_matchers, hint.slice(), "toMatchSnapshot");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a89f78 and 785e123.

📒 Files selected for processing (4)
  • src/bun.js/TODO.md (1 hunks)
  • src/bun.js/test/Collection.zig (1 hunks)
  • src/bun.js/test/expect/toMatchSnapshot.zig (1 hunks)
  • src/cli/test_command.zig (16 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/bun.js/TODO.md
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Collection.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Collection.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
  • src/bun.js/test/Collection.zig
🧠 Learnings (21)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Name test files `*.test.ts` and use `bun:test`
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Use shared utilities from test/harness.ts where applicable
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/bake-harness.ts : Provide and maintain shared test utilities: devTest, prodTest, devAndProductionTest, Dev, Client, and helpers in the harness
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, use normalizeBunSnapshot when asserting snapshots

Applied to files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.js/test/expect/toMatchSnapshot.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Do not set explicit test timeouts; rely on Bun’s built-in timeouts

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Name test files `*.test.ts` and use `bun:test`

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Use `using`/`await using` with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-20T03:39:41.748Z
Learnt from: pfgithub
PR: oven-sh/bun#22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.748Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/node/test/** : Vendored Node.js tests under test/js/node/test/ are exceptions and do not follow Bun’s test style

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: For platform-specific changes, run bun run zig:check-all to compile on all platforms

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:07:54.573Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/building-bun.mdc:0-0
Timestamp: 2025-08-30T00:07:54.573Z
Learning: Applies to src/**/*.zig : Implement debug logs in Zig using `const log = bun.Output.scoped(.${SCOPE}, false);` and invoking `log("...", .{})`

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/cli/test_command.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to **/*.zig : In Zig, manage memory carefully with allocators and use defer for cleanup

Applied to files:

  • src/bun.js/test/Collection.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (16)
src/bun.js/test/Collection.zig (4)

21-43: LGTM!

The initialization logic is well-structured, with proper setup of the root scope, queues, and allocator management. The debug logging integration adds good observability.


61-77: LGTM!

The callback enqueueing logic correctly validates the locked state, handles optional callbacks, and maintains proper scope relationships. Good use of debug logging for observability.


123-149: LGTM!

The step execution logic properly processes the describe callback queue, handles scope transitions, and integrates well with the broader test execution flow. The error handling for failed scopes is appropriate.


151-158: LGTM!

The exception handling correctly marks the active scope as failed and returns the appropriate result to show unhandled errors in the describe context.

src/cli/test_command.zig (11)

42-62: LGTM!

The status formatting function correctly maps bun_test.Execution.Result to appropriate display formats with proper emoji and color handling. The implementation accounts for both pending states and different display modes.


64-73: LGTM!

The test status line writing function properly handles AI agent filtering and uses compile-time status formatting for optimal performance.


379-379: LGTM!

The JUnit reporter signature update correctly uses the new bun_test.Execution.Result type, maintaining compatibility with the broader describe2-based execution model.


511-520: LGTM!

The timeout result handling in the JUnit reporter correctly handles all timeout variants (fail_because_timeout, fail_because_hook_timeout, and their done callback variants) with proper failure reporting.


604-607: Good parameter modernization.

The printTestLine function signature has been updated to use modern bun_test types (BunTest, ExecutionSequence, ExecutionEntry) which aligns with the describe2-based execution model.


839-902: LGTM!

The test completion handler correctly processes different execution results, handles output buffering efficiently, and maintains proper summary statistics. The integration with file reporters is well-structured.


1303-1316: Good integration with describe2 model.

The TestRunner initialization correctly incorporates the concurrent flag and integrates with the new bun_test_root system, replacing the previous callback-based approach.


1748-1754: LGTM!

The test execution loop correctly integrates with the new TestCommand.run interface and handles error propagation appropriately.


1794-1811: Good modernization to describe2 execution model.

The file execution logic properly integrates with the new bun_test_root system, handles file entry/exit lifecycle, and maintains compatibility with existing repeat and reporting mechanisms.


1847-1879: LGTM!

The test execution logic correctly integrates with the new bun_test system, handles automatic test result queuing, and processes the event loop appropriately while tests are running. The phase-based execution model is well-implemented.


1917-1917: LGTM!

The import statement correctly references the new bun_test.zig module, aligning with the describe2 architecture.

src/bun.js/test/expect/toMatchSnapshot.zig (1)

7-7: LGTM: instance-based expect counter.

Switching to this.incrementExpectCallCounter() matches the per-instance refactor across expect.*.

Comment on lines +16 to +18
fn deinit(this: *QueuedDescribe) void {
this.callback.deinit();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing allocator parameter in deinit method.

The deinit method in QueuedDescribe doesn't match the usage pattern where it's called with an allocator parameter (item.deinit(buntest.gpa) on line 116).

Apply this fix to match the usage pattern:

-    fn deinit(this: *QueuedDescribe) void {
+    fn deinit(this: *QueuedDescribe, gpa: std.mem.Allocator) void {
         this.callback.deinit();
     }

The calls to item.deinit() on lines 47 and 51 in the main deinit function should also be updated to pass the allocator:

-        item.deinit();
+        item.deinit(buntest.gpa);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn deinit(this: *QueuedDescribe) void {
this.callback.deinit();
}
fn deinit(this: *QueuedDescribe, gpa: std.mem.Allocator) void {
this.callback.deinit();
}
🤖 Prompt for AI Agents
In src/bun.js/test/Collection.zig around lines 16-18, the QueuedDescribe.deinit
signature lacks the allocator parameter but calls use item.deinit(buntest.gpa)
elsewhere (e.g., line 116); update the method signature to accept an allocator
(e.g., deinit(this: *QueuedDescribe, allocator: *std.mem.Allocator) void) and
inside the method use that allocator for any deallocation, then update the two
calls to item.deinit() in the main deinit function (lines ~47 and ~51) to pass
the allocator argument (the same allocator variable used where items are
deinitialized elsewhere).

Comment on lines +15 to +18
_ = this.bunTest() orelse {
const signature = comptime getSignature("toMatchSnapshot", "", true);
return this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers cannot be used outside of a test\n", .{});
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Enforce “unsupported in concurrent tests” (verify).

PR states toMatchSnapshot is unsupported in concurrent tests. If enforcement isn’t centralized, add a guard here based on the active test’s concurrency flag.

Run to confirm where concurrency is enforced:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Find concurrency flags and snapshot handling
rg -nP -C3 '(test\.concurrent|--concurrent|concurrent\s*:|isConcurrent|concurrent=)' src test
rg -nP -C3 'toMatchSnapshot|toMatchInlineSnapshot' src
# Inspect Expect/Test types for concurrency metadata
rg -nP -C5 '\b(struct|const)\s+(Test|BunTest|Expect)\b' src | sed -n '1,200p'

Length of output: 34997


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "---- Locate snapshot-related symbols ----"
rg -n --hidden -S 'pub fn (snapshot|inlineSnapshot)|pub const Snapshots|this\.snapshot\(|toMatchSnapshot' src || true
echo
echo "---- Check for 'concurrent' checks inside snapshot/expect matchers ----"
rg -n --hidden -S 'concurrent' src/bun.js/test/snapshot.zig src/bun.js/test/expect/*.zig || true
echo
echo "---- Show src/bun.js/test/expect/toMatchSnapshot.zig (full) ----"
nl -ba src/bun.js/test/expect/toMatchSnapshot.zig | sed -n '1,240p'
echo
echo "---- Show src/bun.js/test/expect/toMatchInlineSnapshot.zig (full) ----"
nl -ba src/bun.js/test/expect/toMatchInlineSnapshot.zig | sed -n '1,240p'
echo
echo "---- Show snapshot.zig header and nearby functions (1..420) ----"
nl -ba src/bun.js/test/snapshot.zig | sed -n '1,420p'
echo "---- Show snapshot.zig (420..840) ----"
nl -ba src/bun.js/test/snapshot.zig | sed -n '420,840p'

Length of output: 1921


Enforce snapshot matchers are disallowed in concurrent tests.

toMatchSnapshot currently only checks "inside a test" (this.bunTest()) and I found no concurrency guard. Add a centralized check in pub fn snapshot(...) (src/bun.js/test/expect.zig) that inspects the active test's concurrency flag (e.g., ExecutionEntry.base.concurrent / DescribeScope.base.concurrent via the BunTest active entry) and throws via getSignature/this.throw with a message like "Snapshot matchers cannot be used in concurrent tests". If you prefer not to centralize, add the same guard immediately after the this.bunTest() orelse in src/bun.js/test/expect/toMatchSnapshot.zig (and mirror for toMatchInlineSnapshot/toThrowErrorMatchingSnapshot).

🤖 Prompt for AI Agents
In src/bun.js/test/expect.zig (pub fn snapshot(...)) and related snapshot
entrypoints, add a centralized concurrency guard: inspect the active BunTest
execution entry's concurrency flag (e.g., check ExecutionEntry.base.concurrent
or DescribeScope.base.concurrent via the BunTest active entry) and if it is
true, call getSignature with the appropriate matcher name and use
this.throw(globalThis, signature, "\n\n<b>Matcher error<r>: Snapshot matchers
cannot be used in concurrent tests\n", .{}); to abort; if you prefer not to
centralize, add the same guard immediately after the this.bunTest() orelse in
src/bun.js/test/expect/toMatchSnapshot.zig (lines ~15-18) and mirror the
identical check/message in toMatchInlineSnapshot and
toThrowErrorMatchingSnapshot so all snapshot matchers disallow concurrent tests.

@Jarred-Sumner Jarred-Sumner merged commit d2201eb into main Sep 20, 2025
65 of 66 checks passed
@Jarred-Sumner Jarred-Sumner deleted the pfg/describe-2 branch September 20, 2025 07:35
Jarred-Sumner pushed a commit that referenced this pull request Sep 21, 2025
### What does this PR do?

#22534 made `--test-name-pattern` more logical and not start with empty
` ` (space), so fixing the built regex to make it work still for old and
new bun

The other main issue that that pr did was make start events for filtered
out names which means it appears to rerun them all even when in reality
it doesn't as they are skipped

Also in theory with concurrent test, if there's an error after another
started it would be assigned to the wrong test because we don't get test
id's in the error event, so its just assumed its from the last started
one which with parallel means it isn't correct.

### How did you verify your code works?
pfgithub added a commit that referenced this pull request Oct 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment