Skip to content

Conversation

@pfgithub
Copy link
Collaborator

@pfgithub pfgithub commented Sep 24, 2025

What does this PR do?

Adds a maxConcurrency flag to limit the amount of concurrent tests that run at once. Defaults to 20. Jest and Vitest both default to 5.

How did you verify your code works?

Tests

@robobun
Copy link
Collaborator

robobun commented Sep 24, 2025

Updated 4:41 PM PT - Sep 26th, 2025

@pfgithub, your commit 5d215a0 has 5 failures in Build #27196 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 22944

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

bun-22944 --bun

@pfgithub pfgithub marked this pull request as ready for review September 25, 2025 03:25
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Walkthrough

Add a max-concurrency CLI option and wiring, expose TestRunner.max_concurrency, refactor test execution to be group-centric by adding ConcurrentGroup.next_sequence_index and changing sequence-starting flow to respect reporter-configured concurrency limits, and add tests/fixture exercising max-concurrency behavior.

Changes

Cohort / File(s) Summary of changes
Execution engine concurrency control
src/bun.js/test/Execution.zig
Add ConcurrentGroup.next_sequence_index: usize and initialize it; refactor stepping to start remaining sequences via the group using next_sequence_index; change stepSequence/stepSequenceOne signatures to accept (group, sequence_index, now); enforce concurrency limits from reporter.jest.max_concurrency and track active count.
BunTest and TestRunner structs
src/bun.js/test/bun_test.zig, src/bun.js/test/jest.zig
Remove default initializer for BunTest.default_concurrent (now required) and expand reporter comment; add TestRunner.max_concurrency: u32.
CLI wiring and parsing
src/cli.zig, src/cli/Arguments.zig, src/cli/test_command.zig
Add TestOptions.max_concurrency: u32 = 20; add --max-concurrency <NUMBER> CLI parameter with parsing/validation and error handling; wire ctx.test_options.max_concurrency into TestRunner.max_concurrency during TestCommand.exec.
Tests and fixtures
test/js/bun/test/concurrent-max.fixture.ts, test/js/bun/test/concurrent.test.ts
Add fixture that records execution pattern for 100 concurrent tests; add three tests asserting behavior for --max-concurrency 3, default (20), and --max-concurrency 0 (unlimited).
Unrelated test tweak
test/js/sql/sql-mysql.test.ts
Add local SQL instance (max: 1) shadowing outer sql variable inside three tests.

Possibly related PRs

Suggested reviewers

  • nektro
  • Jarred-Sumner

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly identifies the introduction of the --max-concurrency flag and its default value of 20, directly reflecting the primary purpose of the changeset in a clear and concise manner.
Description Check ✅ Passed The pull request description adheres to the repository template by including both required headings and supplying a concise summary of the feature and the verification approach, satisfying the documentation guidelines.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pfg/concurrent-limit

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 3

🧹 Nitpick comments (9)
src/bun.js/test/Execution.zig (4)

47-49: Clarify/align next_sequence_index semantics.

Doc says “next sequence that has not been started yet”, but it’s used as a scan cursor that may pass over executing sequences. Consider clarifying the comment (or rename to next_scan_index) and ensure it’s reset on group start/creation (currently set in init, which is fine).

Is there any path where a ConcurrentGroup is reused across files/runs without reinit? If so, resetting next_sequence_index in onGroupStarted() would be safer.

Also applies to: 61-61


254-266: Guard against over-subscribing when backfilling after a completion.

This loop unconditionally starts one more sequence. It should respect max_concurrency to avoid accidental overfill if invariants change. Recommend checking current executing count before starting a new sequence.

Apply this diff:

-            // this sequence is complete; execute the next sequence
-            while (group.next_sequence_index < group.sequences(this).len) : (group.next_sequence_index += 1) {
+            // this sequence is complete; execute the next sequence (respecting concurrency cap)
+            const concurrent_limit: usize = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else 20;
+            var executing_count: usize = 0;
+            for (group.sequences(this)) |*seq| {
+                if (seq.executing) executing_count += 1;
+            }
+            while (group.next_sequence_index < group.sequences(this).len) : (group.next_sequence_index += 1) {
+                if (concurrent_limit != 0 and executing_count >= concurrent_limit) break;
                 const target_sequence = &group.sequences(this)[group.next_sequence_index];
                 if (target_sequence.executing) continue;
                 const sequence_status = try stepSequence(buntest_strong, globalThis, group, group.next_sequence_index, now);
                 switch (sequence_status) {
                     .done => continue,
                     .execute => |exec| {
+                        executing_count += 1;
                         return .{ .waiting = .{ .timeout = exec.timeout } };
                     },
                 }
             }

323-335: Minor: avoid repeated slice calls in hot path.

Cache group.sequences(this) once before the loop to reduce repeated slicing.

Apply this diff:

-    for (0..group.sequences(this).len) |sequence_index| {
-        const sequence_status = try stepSequence(buntest_strong, globalThis, group, sequence_index, now);
+    const sequences = group.sequences(this);
+    for (0..sequences.len) |sequence_index| {
+        const sequence_status = try stepSequence(buntest_strong, globalThis, group, sequence_index, now);

346-350: Group-centric sequencing refactor LGTM.

stepSequence/stepSequenceOne now operate via the group to resolve sequences. The use of &group.sequences(this)[sequence_index] is sound.

You can also cache const sequences = group.sequences(this); locally in stepSequenceOne() before indexing.

Also applies to: 352-359

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

61-62: Set a safe default for max_concurrency at the struct level.

Currently max_concurrency has no default initializer. If any code constructs TestRunner{} without setting this field, it will be uninitialized. Since CLI defaults to 20, mirror that default here to avoid UB in other construction paths.

Apply this diff:

-    max_concurrency: u32,
+    max_concurrency: u32 = 20,
src/cli/Arguments.zig (1)

204-205: Clarify help text: document 0 as “unlimited”.

Tests rely on --maxConcurrency 0 meaning “no cap”. Reflect that in the flag description to avoid confusion.

Apply this diff:

-    clap.parseParam("--maxConcurrency <NUMBER>        Maximum number of concurrent tests to execute at once. Default is 20.") catch unreachable,
+    clap.parseParam("--maxConcurrency <NUMBER>        Maximum number of concurrent tests to execute at once (0 = unlimited). Default is 20.") catch unreachable,
test/js/bun/test/concurrent.test.ts (3)

61-82: Dispose spawned process with await using.

Use await using for Bun resources to ensure cleanup per test guidelines. Also, await on Bun.spawn() is unnecessary.

Apply this diff:

-test("maxConcurrency limits concurrent tests", async () => {
-  // Test with maxConcurrency=3
-  const result = await Bun.spawn({
+test("maxConcurrency limits concurrent tests", async () => {
+  // Test with maxConcurrency=3
+  await using result = Bun.spawn({
     cmd: [bunExe(), "test", "--maxConcurrency", "3", import.meta.dir + "/concurrent-max.fixture.ts"],
     stdout: "pipe",
     stderr: "pipe",
     env: bunEnv,
   });
   const exitCode = await result.exited;
   const stdout = await result.stdout.text();

84-104: Dispose spawned process with await using (repeat).

Same suggestion as above to ensure cleanup.

Apply this diff:

-test("maxConcurrency default is 20", async () => {
-  const result = await Bun.spawn({
+test("maxConcurrency default is 20", async () => {
+  await using result = Bun.spawn({
     cmd: [bunExe(), "test", import.meta.dir + "/concurrent-max.fixture.ts"],
     stdout: "pipe",
     stderr: "pipe",
     env: bunEnv,
   });

106-126: Dispose spawned process with await using and fix comment.

Use await using and correct the comment to reflect unlimited behavior.

Apply this diff:

-test("zero removes maxConcurrency", async () => {
-  const result = await Bun.spawn({
+test("zero removes maxConcurrency", async () => {
+  await using result = Bun.spawn({
     cmd: [bunExe(), "test", "--maxConcurrency", "0", import.meta.dir + "/concurrent-max.fixture.ts"],
     stdout: "pipe",
     stderr: "pipe",
     env: bunEnv,
   });
@@
-  // Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
+  // Should be 1,2,3,4,5,... (no cap)
   const expected = Array.from({ length: 100 }, (_, i) => i + 1);
📜 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 fee28ca and d2d5f30.

📒 Files selected for processing (8)
  • src/bun.js/test/Execution.zig (5 hunks)
  • src/bun.js/test/bun_test.zig (1 hunks)
  • src/bun.js/test/jest.zig (1 hunks)
  • src/cli.zig (1 hunks)
  • src/cli/Arguments.zig (2 hunks)
  • src/cli/test_command.zig (1 hunks)
  • test/js/bun/test/concurrent-max.fixture.ts (1 hunks)
  • test/js/bun/test/concurrent.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
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/jest.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/test/bun_test.zig
  • src/cli/Arguments.zig
  • src/cli.zig
  • src/cli/test_command.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/jest.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/test/bun_test.zig
  • src/cli/Arguments.zig
  • src/cli.zig
  • src/cli/test_command.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/jest.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/test/bun_test.zig
test/**

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

Place all tests under the test/ directory

Files:

  • test/js/bun/test/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.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/concurrent.test.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.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/concurrent.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • test/js/bun/test/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.ts
🧠 Learnings (17)
📚 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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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/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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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 set explicit test timeouts; rely on Bun’s built-in timeouts

Applied to files:

  • test/js/bun/test/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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} : 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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.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/concurrent.test.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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/bake-harness.ts : Provide and maintain shared test utilities: devTest, prodTest, devAndProductionTest, Dev, Client, and helpers in the harness

Applied to files:

  • test/js/bun/test/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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 : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)

Applied to files:

  • test/js/bun/test/concurrent.test.ts
  • test/js/bun/test/concurrent-max.fixture.ts
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
PR: oven-sh/bun#22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
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/concurrent.test.ts
  • test/js/bun/test/concurrent-max.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} : Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead

Applied to files:

  • test/js/bun/test/concurrent.test.ts
📚 Learning: 2025-09-24T05:48:59.851Z
Learnt from: nektro
PR: oven-sh/bun#22806
File: scripts/runner.node.mjs:687-689
Timestamp: 2025-09-24T05:48:59.851Z
Learning: In the Bun codebase, the `startGroup` utility function in scripts/runner.node.mjs automatically closes any previously open group when called, so `startGroup("End")` correctly closes the final test group and keeps subsequent output ungrouped.

Applied to files:

  • src/bun.js/test/Execution.zig
📚 Learning: 2025-09-02T18:29:58.304Z
Learnt from: taylordotfish
PR: oven-sh/bun#22227
File: src/memory.zig:37-43
Timestamp: 2025-09-02T18:29:58.304Z
Learning: In bun's `src/memory.zig`, the `initDefault` function intentionally calls `T.init()` without parameter validation to cause compile-time errors when `init` requires arguments or returns an error union. This design forces developers to add explicit `initDefault` methods for complex types rather than silently falling back to `{}`. The `{}` fallback is only intended for types that have no `init` function at all.

Applied to files:

  • 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/client-fixture.mjs : Client subprocess logic (page load, IPC, error/reload coordination) should live in client-fixture.mjs and be driven by Client

Applied to files:

  • test/js/bun/test/concurrent-max.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/**/*-fixture.ts : Name test fixture files that are spawned by tests with the suffix `-fixture.ts`

Applied to files:

  • test/js/bun/test/concurrent-max.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/concurrent-max.fixture.ts
🧬 Code graph analysis (1)
test/js/bun/test/concurrent-max.fixture.ts (1)
src/js/node/test.ts (2)
  • beforeEach (151-160)
  • afterEach (162-171)
⏰ 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 (5)
src/bun.js/test/Execution.zig (1)

249-253: Signature change usage looks correct.

stepSequence now takes (group, sequence_index, now); call sites updated accordingly.

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

117-125: Confirm all call sites now pass default_concurrent explicitly.

Field default_concurrent no longer has a default; enterFile() now requires it. I see test_command.zig passes should_run_concurrent, which is good. Please verify there are no other BunTestRoot.enterFile invocations or BunTest initializations that omit this argument.

If helpful, I can generate a repo scan to confirm there are no missed call sites.

Also applies to: 160-193

src/cli.zig (1)

347-348: LGTM on default surface.

Adding max_concurrency: u32 = 20 to TestOptions is clear and consistent with PR intent.

src/cli/Arguments.zig (1)

416-423: Validation path looks good.

u32 parse with explicit error is fine; negative values will error out as expected. No changes requested.

Consider rejecting extremely large values with a friendlier message if needed (optional).

src/cli/test_command.zig (1)

1308-1309: Wiring looks correct.

TestRunner.max_concurrency = ctx.test_options.max_concurrency is correctly plumbed.

Comment on lines +155 to 156
/// null if the runner has moved on to the next file but a strong reference to BunTest is stll keeping it alive
reporter: ?*test_command.CommandLineReporter,
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

Fix typo in doc comment.

"stll" -> "still".

Apply this diff:

-    /// null if the runner has moved on to the next file but a strong reference to BunTest is stll keeping it alive
+    /// null if the runner has moved on to the next file but a strong reference to BunTest is still keeping it alive
📝 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
/// null if the runner has moved on to the next file but a strong reference to BunTest is stll keeping it alive
reporter: ?*test_command.CommandLineReporter,
/// null if the runner has moved on to the next file but a strong reference to BunTest is still keeping it alive
reporter: ?*test_command.CommandLineReporter,
🤖 Prompt for AI Agents
In src/bun.js/test/bun_test.zig around lines 155 to 156, the doc comment
contains a typo "stll" — replace it with "still" so the comment reads "still
keeping it alive"; update the comment text only, preserving punctuation and
surrounding whitespace.

Comment on lines +318 to +321
const concurrent_limit = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else blk: {
bun.assert(false); // probably can't get here because reporter is only set null when the file is exited
break :blk 20;
};
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

Don’t assert on null reporter; use a safe default.

bun.assert(false) will crash in debug builds. Fall back to the default (20) without asserting.

Apply this diff:

-    const concurrent_limit = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else blk: {
-        bun.assert(false); // probably can't get here because reporter is only set null when the file is exited
-        break :blk 20;
-    };
+    const concurrent_limit: usize = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else 20;
📝 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 concurrent_limit = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else blk: {
bun.assert(false); // probably can't get here because reporter is only set null when the file is exited
break :blk 20;
};
const concurrent_limit: usize = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else 20;
🤖 Prompt for AI Agents
In src/bun.js/test/Execution.zig around lines 318 to 321, the current code
asserts on a null reporter with bun.assert(false) which will crash in debug
builds; replace the assert branch with a safe fallback so when reporter is null
the expression returns the default value 20 instead. Modify the if/else block so
the else path simply yields 20 (no assert or break needed) and ensure the
overall expression still assigns to concurrent_limit.

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: 0

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

62-62: Document max_concurrency semantics and verify all initializations set it.

Add a brief comment (e.g., “0 = unlimited”) next to the field, and confirm every TestRunner literal assigns it (besides TestCommand).

-    max_concurrency: u32,
+    /// Maximum concurrent test sequences per group (0 = unlimited)
+    max_concurrency: u32,
src/cli/Arguments.zig (1)

208-209: Clarify help text: 0 means unlimited.

Make the CLI description explicit to match runtime behavior.

-    clap.parseParam("--maxConcurrency <NUMBER>        Maximum number of concurrent tests to execute at once. Default is 20.") catch unreachable,
+    clap.parseParam("--maxConcurrency <NUMBER>        Maximum number of concurrent tests to execute at once (0 = unlimited). Default is 20.") catch unreachable,
src/bun.js/test/Execution.zig (1)

352-359: Minor readability nit (optional).

Cache sequences slice once to avoid repeated calls in tight paths.

-    const sequence = &group.sequences(this)[sequence_index];
+    const seqs = group.sequences(this);
+    const sequence = &seqs[sequence_index];
📜 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 d2d5f30 and 8729dff.

📒 Files selected for processing (6)
  • src/bun.js/test/Execution.zig (5 hunks)
  • src/bun.js/test/bun_test.zig (1 hunks)
  • src/bun.js/test/jest.zig (1 hunks)
  • src/cli.zig (1 hunks)
  • src/cli/Arguments.zig (2 hunks)
  • src/cli/test_command.zig (1 hunks)
🧰 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/jest.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/cli.zig
  • src/bun.js/test/bun_test.zig
  • src/cli/Arguments.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/jest.zig
  • src/cli/test_command.zig
  • src/bun.js/test/Execution.zig
  • src/cli.zig
  • src/bun.js/test/bun_test.zig
  • src/cli/Arguments.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/jest.zig
  • src/bun.js/test/Execution.zig
  • src/bun.js/test/bun_test.zig
🧠 Learnings (1)
📚 Learning: 2025-09-24T05:48:59.851Z
Learnt from: nektro
PR: oven-sh/bun#22806
File: scripts/runner.node.mjs:687-689
Timestamp: 2025-09-24T05:48:59.851Z
Learning: In the Bun codebase, the `startGroup` utility function in scripts/runner.node.mjs automatically closes any previously open group when called, so `startGroup("End")` correctly closes the final test group and keeps subsequent output ungrouped.

Applied to files:

  • src/bun.js/test/Execution.zig
🔇 Additional comments (10)
src/bun.js/test/bun_test.zig (2)

155-156: Fix typo in doc comment.

"stll" -> "still".

-    /// null if the runner has moved on to the next file but a strong reference to BunTest is stll keeping it alive
+    /// null if the runner has moved on to the next file but a strong reference to BunTest is still keeping it alive

160-160: LGTM: explicit initialization

Removing the default initializer on default_concurrent makes construction explicit and avoids hidden defaults.

src/cli.zig (1)

348-349: LGTM: default set to 20

Public TestOptions gains max_concurrency with sensible default.

src/cli/Arguments.zig (1)

420-427: LGTM: parsing and validation

Parses unsigned int, errors on invalid input, and leaves default when omitted.

src/cli/test_command.zig (1)

1309-1310: Wiring looks correct

TestRunner.max_concurrency is initialized from parsed test options.

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

47-49: Track next_sequence_index: verify extension path.

Initialization is fine. If tryExtend() can be called after some sequences have started, consider whether next_sequence_index needs adjusting; otherwise, this is safe when only used pre-execution.

Also applies to: 61-62


249-265: Start one new sequence after completion (keeps the cap steady).

This loop correctly finds the next non-executing sequence and starts at most one, preserving the concurrency limit established earlier.


323-333: LGTM: cap enforcement logic

active_count + early break enforces the configured limit; zero means unbounded due to the guard.


346-349: Signature/loop refactor looks good

stepSequence delegates via stepSequenceOne with the new parameters and continues until a stable state.


318-323: Do not assert on null reporter; fix integer type mismatch.

  • Asserting here can crash debug builds; default to 20 safely.
  • concurrent_limit (u32) compared with active_count (usize) will cause a type mismatch. Cast to usize.
-    const concurrent_limit = if (buntest.reporter) |reporter| reporter.jest.max_concurrency else blk: {
-        bun.assert(false); // probably can't get here because reporter is only set null when the file is exited
-        break :blk 20;
-    };
+    const concurrent_limit: usize = @intCast(if (buntest.reporter) |reporter| reporter.jest.max_concurrency else 20);

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: 0

🧹 Nitpick comments (1)
src/cli/Arguments.zig (1)

421-429: Validate non-zero to avoid deadlock.

If a user passes 0, scheduling may stall. Enforce >= 1 at parse time.

Apply:

-        if (args.option("--maxConcurrency")) |max_concurrency| {
-            if (max_concurrency.len > 0) {
-                ctx.test_options.max_concurrency = std.fmt.parseInt(u32, max_concurrency, 10) catch {
-                    Output.prettyErrorln("<r><red>error<r>: Invalid maxConcurrency: \"{s}\"", .{max_concurrency});
-                    Global.exit(1);
-                };
-            }
-        }
+        if (args.option("--maxConcurrency")) |max_concurrency| {
+            if (max_concurrency.len > 0) {
+                ctx.test_options.max_concurrency = std.fmt.parseInt(u32, max_concurrency, 10) catch {
+                    Output.prettyErrorln("<r><red>error<r>: Invalid maxConcurrency: \"{s}\"", .{max_concurrency});
+                    Global.exit(1);
+                };
+                if (ctx.test_options.max_concurrency == 0) {
+                    Output.prettyErrorln("<r><red>error<r>: --maxConcurrency must be >= 1", .{});
+                    Global.exit(1);
+                }
+            }
+        }

Also confirm whether downstream execution clamps or relies on this being >= 1.

📜 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 1aba4ad and 185cf8b.

📒 Files selected for processing (5)
  • src/bun.js/test/bun_test.zig (1 hunks)
  • src/bun.js/test/jest.zig (1 hunks)
  • src/cli.zig (1 hunks)
  • src/cli/Arguments.zig (2 hunks)
  • src/cli/test_command.zig (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/bun.js/test/jest.zig
  • src/cli.zig
🧰 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/cli/Arguments.zig
  • src/bun.js/test/bun_test.zig
  • src/cli/test_command.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/cli/Arguments.zig
  • src/bun.js/test/bun_test.zig
  • src/cli/test_command.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/bun_test.zig
🧠 Learnings (1)
📚 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/test/bun_test.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 (4)
src/bun.js/test/bun_test.zig (2)

155-156: Fix typo in doc comment.

"stll" -> "still".

Apply this diff:

-    /// null if the runner has moved on to the next file but a strong reference to BunTest is stll keeping it alive
+    /// null if the runner has moved on to the next file but a strong reference to BunTest is still keeping it alive

160-160: LGTM: field default removal is safe.

default_concurrent is now set explicitly in init; removal of the default initializer is fine.

src/cli/Arguments.zig (1)

209-209: Add option: looks good.

The flag name and help text are clear and consistent.

src/cli/test_command.zig (1)

1315-1315: max_concurrency wiring is correct Verified that TestRunner declares max_concurrency: u32 and TestOptions defaults it to 20.

@pfgithub pfgithub changed the title Concurrent limit --maxConcurrency, defaults to 20 Concurrent limit --max-concurrency, defaults to 20 Sep 26, 2025
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

📜 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 9a86f50 and 5d215a0.

📒 Files selected for processing (4)
  • docs/cli/test.md (1 hunks)
  • src/cli.zig (1 hunks)
  • src/cli/Arguments.zig (2 hunks)
  • test/js/bun/test/concurrent.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli/Arguments.zig
🧰 Additional context used
📓 Path-based instructions (10)
test/**

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

Place all tests under the test/ directory

Files:

  • test/js/bun/test/concurrent.test.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/concurrent.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/concurrent.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/concurrent.test.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/concurrent.test.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/concurrent.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/concurrent.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • test/js/bun/test/concurrent.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/cli.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/cli.zig
🧠 Learnings (14)
📚 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/concurrent.test.ts
  • docs/cli/test.md
📚 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/concurrent.test.ts
  • docs/cli/test.md
📚 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/concurrent.test.ts
  • docs/cli/test.md
📚 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/concurrent.test.ts
  • docs/cli/test.md
📚 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/concurrent.test.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/concurrent.test.ts
  • docs/cli/test.md
📚 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/concurrent.test.ts
  • docs/cli/test.md
📚 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/concurrent.test.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 : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)

Applied to files:

  • test/js/bun/test/concurrent.test.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/bake-harness.ts : Provide and maintain shared test utilities: devTest, prodTest, devAndProductionTest, Dev, Client, and helpers in the harness

Applied to files:

  • test/js/bun/test/concurrent.test.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/concurrent.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/concurrent.test.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: Run tests with `bun bd test <...test file>`; do not use `bun test`

Applied to files:

  • docs/cli/test.md
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
PR: oven-sh/bun#22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
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:

  • docs/cli/test.md
🪛 markdownlint-cli2 (0.18.1)
docs/cli/test.md

121-121: Dollar signs used before commands without showing output

(MD014, commands-show-output)

Comment on lines +138 to +139
This helps prevent resource exhaustion when running many concurrent tests. The default value is 20.

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 | 🟡 Minor

Document that 0 disables the concurrency cap

The implementation treats 0 as “no limit”, but the docs don’t mention it. Calling this out avoids confusion for users who want to remove the default cap.

-This helps prevent resource exhaustion when running many concurrent tests. The default value is 20.
+This helps prevent resource exhaustion when running many concurrent tests. The default value is 20; set it to 0 to remove the cap entirely and let Bun schedule as many concurrent tests as possible.
🤖 Prompt for AI Agents
In docs/cli/test.md around lines 138 to 139, the docs state the default
concurrency is 20 but do not mention that setting the concurrency to 0 disables
the cap; update the sentence to explicitly document that using 0 means “no
limit” (no concurrency cap) and keep the default value note, e.g. add a clause
like “(use 0 to disable the cap)” so users know 0 is treated as unlimited
concurrency.

Comment on lines +75 to +123
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1]);

// Should be 1,2,3,3,3,3,3,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 3));
expect(executionPattern).toEqual(expected);
});

test("max-concurrency default is 20", async () => {
const result = await Bun.spawn({
cmd: [bunExe(), "test", import.meta.dir + "/concurrent-max.fixture.ts"],
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await result.exited;
const stdout = await result.stdout.text();

expect(exitCode).toBe(0);

// Extract max concurrent value from output
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1]);

// Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 20));
expect(executionPattern).toEqual(expected);
});

test("zero removes max-concurrency", async () => {
const result = await Bun.spawn({
cmd: [bunExe(), "test", "--max-concurrency", "0", import.meta.dir + "/concurrent-max.fixture.ts"],
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await result.exited;
const stdout = await result.stdout.text();

expect(exitCode).toBe(0);

// Extract max concurrent value from output
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1]);

// Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
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 | 🟠 Major

Trim execution pattern before JSON.parse

On Windows the log line ends with \r\n, so the capture group includes \r. JSON.parse("[...]\\r") throws, making these tests fail cross-platform. Trim the capture before parsing (and apply the same fix to each test).

-  const executionPattern = JSON.parse(maxMatch![1]);
+  const executionPattern = JSON.parse(maxMatch![1].trim());
📝 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 maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1]);
// Should be 1,2,3,3,3,3,3,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 3));
expect(executionPattern).toEqual(expected);
});
test("max-concurrency default is 20", async () => {
const result = await Bun.spawn({
cmd: [bunExe(), "test", import.meta.dir + "/concurrent-max.fixture.ts"],
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await result.exited;
const stdout = await result.stdout.text();
expect(exitCode).toBe(0);
// Extract max concurrent value from output
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1]);
// Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 20));
expect(executionPattern).toEqual(expected);
});
test("zero removes max-concurrency", async () => {
const result = await Bun.spawn({
cmd: [bunExe(), "test", "--max-concurrency", "0", import.meta.dir + "/concurrent-max.fixture.ts"],
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await result.exited;
const stdout = await result.stdout.text();
expect(exitCode).toBe(0);
// Extract max concurrent value from output
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1]);
// Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1].trim());
// Should be 1,2,3,3,3,3,3,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 3));
expect(executionPattern).toEqual(expected);
});
test("max-concurrency default is 20", async () => {
const result = await Bun.spawn({
cmd: [bunExe(), "test", import.meta.dir + "/concurrent-max.fixture.ts"],
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await result.exited;
const stdout = await result.stdout.text();
expect(exitCode).toBe(0);
// Extract max concurrent value from output
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1].trim());
// Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 20));
expect(executionPattern).toEqual(expected);
});
test("zero removes max-concurrency", async () => {
const result = await Bun.spawn({
cmd: [bunExe(), "test", "--max-concurrency", "0", import.meta.dir + "/concurrent-max.fixture.ts"],
stdout: "pipe",
stderr: "pipe",
env: bunEnv,
});
const exitCode = await result.exited;
const stdout = await result.stdout.text();
expect(exitCode).toBe(0);
// Extract max concurrent value from output
const maxMatch = stdout.match(/Execution pattern: ([^\n]+)/);
expect(maxMatch).toBeTruthy();
const executionPattern = JSON.parse(maxMatch![1].trim());
// Should be 1,2,3,...,18,19,20,20,20,20,20,20,...
const expected = Array.from({ length: 100 }, (_, i) => Math.min(i + 1, 20));
expect(executionPattern).toEqual(expected);
});
🤖 Prompt for AI Agents
test/js/bun/test/concurrent.test.ts around lines 75 to 123: the captured
execution pattern string can include trailing CR on Windows so
JSON.parse(maxMatch![1]) can throw; fix by trimming the captured group before
parsing (e.g. const executionPattern = JSON.parse(maxMatch![1].trim())); apply
the same .trim() change to every test in this block where
JSON.parse(maxMatch![1]) is 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.

JSON.parse seems to trim whitespace: this works without error JSON.parse(" {}\r\n\r"). It passed in CI already and that includes windows.

@pfgithub pfgithub merged commit 250d30e into main Sep 26, 2025
60 of 65 checks passed
@pfgithub pfgithub deleted the pfg/concurrent-limit branch September 26, 2025 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants