| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 12 | 8 | 0% |
| Commands | 0 | 5 | 8 | 0% |
| Section tags | 1 | 3 | 2 | 17% |
What each file covers
Sections
0 shared · 12 only in A · 8 only in B- − Testing style
- − Spawning processes
- − No timeouts
- − Use port 0 to get a random port
- − Creating temporary files
- − Strings
- − Test Organization
- − Nested/complex object equality
- − Common Imports from `harness`
- − Error Testing
- − Avoid dynamic import & require
- − Test Utilities
- + JavaScript Builtins in Bun
- + Directory Structure
- + Writing Modules
- + Writing Builtin Functions
- + $ Globals and Special Syntax
- + Validation and Errors
- + Build Process
- + Key Rules
Commands
0 shared · 5 only in A · 8 only in B- − bun bd test <...test file>
- − bun bd <...cmd>
- − bun:test
- − bun bd <file>
- − bun bd test <file>
- + bun bd
- + node/
- + node:fs
- + node:path
- + bun/
- + bun:ffi
- + bun:sqlite
- + node-fetch
Section tags
1 shared · 3 only in A · 2 only in B- − test
- − code-style
- − testing-strategy
- + build
- + architecture
- do-not
Line diff
oven-sh/bun · test/CLAUDE.md
@@ −1 @@
1To run tests:
2
3```sh
4bun bd test <...test file>
5```
6
7To run a command with your debug build of Bun:
8
9```sh
10bun bd <...cmd>
11```
12
13Note that compiling Bun may take up to 2.5 minutes. It is slow!
14
15**CRITICAL**: Do not use `bun test` to run tests. It will not have your changes. `bun bd test <...test file>` is the correct command, which compiles your code automatically.
16
17## Testing style
18
19Use `bun:test` with files that end in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. If it's a test/js/node/test/{parallel,sequential}/\*.js without a .test.extension, use `bun bd <file>` instead of `bun bd test <file>` since those expect exit code 0 and don't use bun's test runner.
20
21- **Do not write flaky tests**. Unless explicitly asked, **never wait for time to pass in tests**. Always wait for the condition to be met instead of waiting for an arbitrary amount of time. **Never use hardcoded port numbers**. Always use `port: 0` to get a random port.
22- **Prefer concurrent tests over sequential tests**: When multiple tests in the same file spawn processes or write files, make them concurrent with `test.concurrent` or `describe.concurrent` unless it's very difficult to make them concurrent.
23
24### Spawning processes
25
26#### Spawning Bun in tests
27
28When spawning Bun processes, use `bunExe` and `bunEnv` from `harness`. This ensures the same build of Bun is used to run the test and ensures debug logging is silenced.
29
30##### Use `-e` for single-file tests
31
32```ts
33import { bunEnv, bunExe, tempDir } from "harness";
34import { test, expect } from "bun:test";
35
36test("single-file test spawns a Bun process", async () => {
37 await using proc = Bun.spawn({
38 cmd: [bunExe(), "-e", "console.log('Hello, world!')"],
39 env: bunEnv,
40 });
41
42 const [stdout, stderr, exitCode] = await Promise.all([
43 proc.stdout.text(),
44 proc.stderr.text(),
45 proc.exited,
46 ]);
47
48 expect(stderr).toBe("");
49 expect(stdout).toBe("Hello, world!\n");
50 expect(exitCode).toBe(0);
51});
52```
53
54##### When multi-file tests are required:
55
56```ts
57import { bunEnv, bunExe, tempDir } from "harness";
58import { test, expect } from "bun:test";
59
60test("multi-file test spawns a Bun process", async () => {
61 // If a test MUST use multiple files:
62 using dir = tempDir("my-test-prefix", {
63 "my.fixture.ts": `
64 import { foo } from "./foo.ts";
65 foo();
66 `,
67 "foo.ts": `
68 export function foo() {
69 console.log("Hello, world!");
70 }
71 `,
72 });
73
74 await using proc = Bun.spawn({
75 cmd: [bunExe(), "my.fixture.ts"],
76 env: bunEnv,
77 cwd: String(dir),
78 });
79
80 const [stdout, stderr, exitCode] = await Promise.all([
81
82 // ReadableStream in Bun supports:
83 // - `await stream.text()`
84 // - `await stream.json()`
85 // - `await stream.bytes()`
86 // - `await stream.blob()`
87 proc.stdout.text(),
88 proc.stderr.text(),
89
90 proc.exitCode,
91 ]);
92
93 expect(stdout).toBe("Hello, world!");
94 expect(stderr).toBe("");
95 expect(exitCode).toBe(0);
96```
97
98When a test file spawns a Bun process, we like for that file to end in `*-fixture.ts`. This is a convention that helps us identify the file as a test fixture and not a test itself.
99
100Generally, `await using` or `using` is a good idea to ensure proper resource cleanup. This works in most Bun APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc.
101
102#### Async/await in tests
103
104Prefer async/await over callbacks.
105
106When callbacks must be used and it's just a single callback, use `Promise.withResolvers` to create a promise that can be resolved or rejected from a callback.
107
108```ts
109const ws = new WebSocket("ws://localhost:8080");
110const { promise, resolve, reject } = Promise.withResolvers<void>(); // Can specify any type here for resolution value
111ws.onopen = resolve;
112ws.onclose = reject;
113await promise;
114```
115
116If it's several callbacks, it's okay to use callbacks. We aren't a stickler for this.
117
118### No timeouts
119
120**CRITICAL**: Do not set a timeout on tests. Bun already has timeouts.
121
122### Use port 0 to get a random port
123
124Most APIs in Bun support `port: 0` to get a random port. Never hardcode ports. Avoid using your own random port number function.
125
126### Creating temporary files
127
128Use `tempDirWithFiles` to create a temporary directory with files.
129
130```ts
131import { tempDir } from "harness";
132import path from "node:path";
133
134test("creates a temporary directory with files", () => {
135 using dir = tempDir("my-test-prefix", {
136 "file.txt": "Hello, world!",
137 });
138
139 expect(await Bun.file(path.join(String(dir), "file.txt")).text()).toBe(
140 "Hello, world!",
141 );
142});
143```
144
145### Strings
146
147To create a repetitive string, use `Buffer.alloc(count, fill).toString()` instead of `"A".repeat(count)`. "".repeat is very slow in debug JavaScriptCore builds.
148
149### Test Organization
150
151- Use `describe` blocks for grouping related tests
152- **Add tests to the existing test file for the code you're changing** — do not create a new file. Tests are organized by module (e.g., `/test/js/bun/`, `/test/js/node/`, `/test/js/web/`).
153- `/test/regression/issue/${issueNumber}.test.ts` is **only** for bugs that have a GitHub issue number **and** are true regressions (worked in a previous release, then broke). An issue number alone does not qualify — if it was never correct, put the test in the module's existing test file instead.
154- Integration tests are in `/test/integration/`
155
156### Nested/complex object equality
157
158Prefer usage of `.toEqual` rather than many `.toBe` assertions for nested or complex objects.
159
160<example>
161
162BAD (try to avoid doing this):
163
164```ts
165expect(result).toHaveLength(3);
166expect(result[0].optional).toBe(null);
167expect(result[1].optional).toBe("middle-value"); // CRITICAL: middle item's value must be preserved
168expect(result[2].optional).toBe(null);
169```
170
171**GOOD (always prefer this):**
172
173```ts
174expect(result).toEqual([
175 { optional: null },
176 { optional: "middle-value" }, // CRITICAL: middle item's value must be preserved
177 { optional: null },
178]);
179```
180
181</example>
182
183### Common Imports from `harness`
184
185```ts
186import {
187 bunExe, // Path to Bun executable
188 bunEnv, // Environment variables for Bun
189 tempDirWithFiles, // Create temporary test directories with files
190 tmpdirSync, // Create empty temporary directory
191 isMacOS, // Platform checks
192 isWindows,
193 isPosix,
194 gcTick, // Trigger garbage collection
195 withoutAggressiveGC, // Disable aggressive GC for performance tests
196} from "harness";
197```
198
199### Error Testing
200
201Always check exit codes and test error scenarios:
202
203```ts
204test("handles errors", async () => {
205 await using proc = Bun.spawn({
206 cmd: [bunExe(), "run", "invalid.js"],
207 env: bunEnv,
208 });
209
210 const exitCode = await proc.exited;
211 expect(exitCode).not.toBe(0);
212
213 // For synchronous errors
214 expect(() => someFunction()).toThrow("Expected error message");
215});
216```
217
218### Avoid dynamic import & require
219
220**Only** use dynamic import or require when the test is specifically testing something relataed to dynamic import or require. Otherwise, **always use module-scope import statements**.
221
222**BAD, do not do this**:
223
224```ts
225test("foo", async () => {
226 // BAD: Unnecessary usage of dynamic import.
227 const { readFile } = await import("node:fs");
228
229 expect(await readFile("ok.txt")).toBe("");
230});
231```
232
233**GOOD, do this:**
234
235```ts
236import { readFile } from "node:fs";
237test("foo", async () => {
238 expect(await readFile("ok.txt")).toBe("");
239});
240```
241
242### Test Utilities
243
244- Use `describe.each()` for parameterized tests
245- Use `toMatchSnapshot()` for snapshot testing
246- Use `beforeAll()`, `afterEach()`, `beforeEach()` for setup/teardown
247- Track resources (servers, clients) in arrays for cleanup in `afterEach()`
248
oven-sh/bun · src/js/CLAUDE.md
@@ +1 @@
1# JavaScript Builtins in Bun
2
3Write JS builtins for Bun's Node.js compatibility and APIs. Run `bun bd` after changes.
4
5## Directory Structure
6
7- `builtins/` - Individual functions (`*CodeGenerator(vm)` in C++)
8- `node/` - Node.js modules (`node:fs`, `node:path`)
9- `bun/` - Bun modules (`bun:ffi`, `bun:sqlite`)
10- `thirdparty/` - NPM replacements (`ws`, `node-fetch`)
11- `internal/` - Internal modules
12
13## Writing Modules
14
15Modules are NOT ES modules:
16
17```typescript
18const EventEmitter = require("node:events"); // String literals only
19const { validateFunction } = require("internal/validators");
20
21export default {
22 myFunction() {
23 if (!$isCallable(callback)) {
24 throw $ERR_INVALID_ARG_TYPE("cb", "function", callback);
25 }
26 },
27};
28```
29
30## Writing Builtin Functions
31
32```typescript
33// Fifo.ts
34export function createFIFO<T>(): Dequeue<T> {
35 const Dequeue = require("internal/fifo");
36 return new Dequeue();
37}
38```
39
40C++ access:
41
42```cpp
43object->putDirectBuiltinFunction(vm, globalObject, identifier,
44 fifoCreateFIFOCodeGenerator(vm), 0);
45```
46
47## $ Globals and Special Syntax
48
49**CRITICAL**: Use `.$call` and `.$apply`, never `.call` or `.apply`:
50
51```typescript
52// ✗ WRONG - User can tamper
53callback.call(undefined, arg1);
54fn.apply(undefined, args);
55
56// ✓ CORRECT - Tamper-proof
57callback.$call(undefined, arg1);
58fn.$apply(undefined, args);
59
60// $ prefix for private APIs
61const arr = $Array.from(...); // Private globals
62map.$set(key, value); // Private methods
63const newArr = $newArrayWithSize(5); // JSC intrinsics
64$debug("Module loaded:", name); // Debug (stripped in release)
65$assert(condition, "message"); // Assertions (stripped in release)
66```
67
68**Platform detection**: `process.platform` and `process.arch` are inlined and dead-code eliminated
69
70## Validation and Errors
71
72```typescript
73const { validateFunction } = require("internal/validators");
74
75function myAPI(callback) {
76 if (!$isCallable(callback)) {
77 throw $ERR_INVALID_ARG_TYPE("callback", "function", callback);
78 }
79}
80```
81
82## Build Process
83
84`Source TS/JS → Preprocessor → Bundler → C++ Headers`
85
861. Assign numeric IDs (A-Z sorted)
872. Replace `$` with `__intrinsic__`, `require("x")` with `$requireId(n)`
883. Bundle, convert `export default` to `return`
894. Replace `__intrinsic__` with `@`, inline into C++
90
91ModuleLoader.rs loads modules by numeric ID via `InternalModuleRegistry.cpp`.
92
93## Key Rules
94
95- Use `.$call`/`.$apply` not `.call`/`.apply`
96- String literal `require()` only
97- Export via `export default {}`
98- Use JSC intrinsics for performance
99- Run `bun bd` after changes
100
@@ −1 +1 @@
1−To run tests:
1+# JavaScript Builtins in Bun
22
3−```sh
4−bun bd test <...test file>
5−```
3+Write JS builtins for Bun's Node.js compatibility and APIs. Run `bun bd` after changes.
64
7−To run a command with your debug build of Bun:
5+## Directory Structure
86
9−```sh
10−bun bd <...cmd>
11−```
7+- `builtins/` - Individual functions (`*CodeGenerator(vm)` in C++)
8+- `node/` - Node.js modules (`node:fs`, `node:path`)
9+- `bun/` - Bun modules (`bun:ffi`, `bun:sqlite`)
10+- `thirdparty/` - NPM replacements (`ws`, `node-fetch`)
11+- `internal/` - Internal modules
1212
13−Note that compiling Bun may take up to 2.5 minutes. It is slow!
13+## Writing Modules
1414
15−**CRITICAL**: Do not use `bun test` to run tests. It will not have your changes. `bun bd test <...test file>` is the correct command, which compiles your code automatically.
15+Modules are NOT ES modules:
1616
17−## Testing style
17+```typescript
18+const EventEmitter = require("node:events"); // String literals only
19+const { validateFunction } = require("internal/validators");
1820
19−Use `bun:test` with files that end in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. If it's a test/js/node/test/{parallel,sequential}/\*.js without a .test.extension, use `bun bd <file>` instead of `bun bd test <file>` since those expect exit code 0 and don't use bun's test runner.
20−
21−- **Do not write flaky tests**. Unless explicitly asked, **never wait for time to pass in tests**. Always wait for the condition to be met instead of waiting for an arbitrary amount of time. **Never use hardcoded port numbers**. Always use `port: 0` to get a random port.
22−- **Prefer concurrent tests over sequential tests**: When multiple tests in the same file spawn processes or write files, make them concurrent with `test.concurrent` or `describe.concurrent` unless it's very difficult to make them concurrent.
23−
24−### Spawning processes
25−
26−#### Spawning Bun in tests
27−
28−When spawning Bun processes, use `bunExe` and `bunEnv` from `harness`. This ensures the same build of Bun is used to run the test and ensures debug logging is silenced.
29−
30−##### Use `-e` for single-file tests
31−
32−```ts
33−import { bunEnv, bunExe, tempDir } from "harness";
34−import { test, expect } from "bun:test";
35−
36−test("single-file test spawns a Bun process", async () => {
37− await using proc = Bun.spawn({
38− cmd: [bunExe(), "-e", "console.log('Hello, world!')"],
39− env: bunEnv,
40− });
41−
42− const [stdout, stderr, exitCode] = await Promise.all([
43− proc.stdout.text(),
44− proc.stderr.text(),
45− proc.exited,
46− ]);
47−
48− expect(stderr).toBe("");
49− expect(stdout).toBe("Hello, world!\n");
50− expect(exitCode).toBe(0);
51−});
21+export default {
22+ myFunction() {
23+ if (!$isCallable(callback)) {
24+ throw $ERR_INVALID_ARG_TYPE("cb", "function", callback);
25+ }
26+ },
27+};
5228 ```
5329
54−##### When multi-file tests are required:
30+## Writing Builtin Functions
5531
56−```ts
57−import { bunEnv, bunExe, tempDir } from "harness";
58−import { test, expect } from "bun:test";
59−
60−test("multi-file test spawns a Bun process", async () => {
61− // If a test MUST use multiple files:
62− using dir = tempDir("my-test-prefix", {
63− "my.fixture.ts": `
64− import { foo } from "./foo.ts";
65− foo();
66− `,
67− "foo.ts": `
68− export function foo() {
69− console.log("Hello, world!");
70− }
71− `,
72− });
73−
74− await using proc = Bun.spawn({
75− cmd: [bunExe(), "my.fixture.ts"],
76− env: bunEnv,
77− cwd: String(dir),
78− });
79−
80− const [stdout, stderr, exitCode] = await Promise.all([
81−
82− // ReadableStream in Bun supports:
83− // - `await stream.text()`
84− // - `await stream.json()`
85− // - `await stream.bytes()`
86− // - `await stream.blob()`
87− proc.stdout.text(),
88− proc.stderr.text(),
89−
90− proc.exitCode,
91− ]);
92−
93− expect(stdout).toBe("Hello, world!");
94− expect(stderr).toBe("");
95− expect(exitCode).toBe(0);
32+```typescript
33+// Fifo.ts
34+export function createFIFO<T>(): Dequeue<T> {
35+ const Dequeue = require("internal/fifo");
36+ return new Dequeue();
37+}
9638 ```
9739
98−When a test file spawns a Bun process, we like for that file to end in `*-fixture.ts`. This is a convention that helps us identify the file as a test fixture and not a test itself.
40+C++ access:
9941
100−Generally, `await using` or `using` is a good idea to ensure proper resource cleanup. This works in most Bun APIs like Bun.listen, Bun.connect, Bun.spawn, Bun.serve, etc.
101−
102−#### Async/await in tests
103−
104−Prefer async/await over callbacks.
105−
106−When callbacks must be used and it's just a single callback, use `Promise.withResolvers` to create a promise that can be resolved or rejected from a callback.
107−
108−```ts
109−const ws = new WebSocket("ws://localhost:8080");
110−const { promise, resolve, reject } = Promise.withResolvers<void>(); // Can specify any type here for resolution value
111−ws.onopen = resolve;
112−ws.onclose = reject;
113−await promise;
42+```cpp
43+object->putDirectBuiltinFunction(vm, globalObject, identifier,
44+ fifoCreateFIFOCodeGenerator(vm), 0);
11445 ```
11546
116−If it's several callbacks, it's okay to use callbacks. We aren't a stickler for this.
47+## $ Globals and Special Syntax
11748
118−### No timeouts
49+**CRITICAL**: Use `.$call` and `.$apply`, never `.call` or `.apply`:
11950
120−**CRITICAL**: Do not set a timeout on tests. Bun already has timeouts.
51+```typescript
52+// ✗ WRONG - User can tamper
53+callback.call(undefined, arg1);
54+fn.apply(undefined, args);
12155
122−### Use port 0 to get a random port
56+// ✓ CORRECT - Tamper-proof
57+callback.$call(undefined, arg1);
58+fn.$apply(undefined, args);
12359
124−Most APIs in Bun support `port: 0` to get a random port. Never hardcode ports. Avoid using your own random port number function.
125−
126−### Creating temporary files
127−
128−Use `tempDirWithFiles` to create a temporary directory with files.
129−
130−```ts
131−import { tempDir } from "harness";
132−import path from "node:path";
133−
134−test("creates a temporary directory with files", () => {
135− using dir = tempDir("my-test-prefix", {
136− "file.txt": "Hello, world!",
137− });
138−
139− expect(await Bun.file(path.join(String(dir), "file.txt")).text()).toBe(
140− "Hello, world!",
141− );
142−});
60+// $ prefix for private APIs
61+const arr = $Array.from(...); // Private globals
62+map.$set(key, value); // Private methods
63+const newArr = $newArrayWithSize(5); // JSC intrinsics
64+$debug("Module loaded:", name); // Debug (stripped in release)
65+$assert(condition, "message"); // Assertions (stripped in release)
14366 ```
14467
145−### Strings
68+**Platform detection**: `process.platform` and `process.arch` are inlined and dead-code eliminated
14669
147−To create a repetitive string, use `Buffer.alloc(count, fill).toString()` instead of `"A".repeat(count)`. "".repeat is very slow in debug JavaScriptCore builds.
70+## Validation and Errors
14871
149−### Test Organization
72+```typescript
73+const { validateFunction } = require("internal/validators");
15074
151−- Use `describe` blocks for grouping related tests
152−- **Add tests to the existing test file for the code you're changing** — do not create a new file. Tests are organized by module (e.g., `/test/js/bun/`, `/test/js/node/`, `/test/js/web/`).
153−- `/test/regression/issue/${issueNumber}.test.ts` is **only** for bugs that have a GitHub issue number **and** are true regressions (worked in a previous release, then broke). An issue number alone does not qualify — if it was never correct, put the test in the module's existing test file instead.
154−- Integration tests are in `/test/integration/`
155−
156−### Nested/complex object equality
157−
158−Prefer usage of `.toEqual` rather than many `.toBe` assertions for nested or complex objects.
159−
160−<example>
161−
162−BAD (try to avoid doing this):
163−
164−```ts
165−expect(result).toHaveLength(3);
166−expect(result[0].optional).toBe(null);
167−expect(result[1].optional).toBe("middle-value"); // CRITICAL: middle item's value must be preserved
168−expect(result[2].optional).toBe(null);
75+function myAPI(callback) {
76+ if (!$isCallable(callback)) {
77+ throw $ERR_INVALID_ARG_TYPE("callback", "function", callback);
78+ }
79+}
16980 ```
17081
171−**GOOD (always prefer this):**
82+## Build Process
17283
173−```ts
174−expect(result).toEqual([
175− { optional: null },
176− { optional: "middle-value" }, // CRITICAL: middle item's value must be preserved
177− { optional: null },
178−]);
179−```
84+`Source TS/JS → Preprocessor → Bundler → C++ Headers`
18085
181−</example>
86+1. Assign numeric IDs (A-Z sorted)
87+2. Replace `$` with `__intrinsic__`, `require("x")` with `$requireId(n)`
88+3. Bundle, convert `export default` to `return`
89+4. Replace `__intrinsic__` with `@`, inline into C++
18290
183−### Common Imports from `harness`
91+ModuleLoader.rs loads modules by numeric ID via `InternalModuleRegistry.cpp`.
18492
185−```ts
186−import {
187− bunExe, // Path to Bun executable
188− bunEnv, // Environment variables for Bun
189− tempDirWithFiles, // Create temporary test directories with files
190− tmpdirSync, // Create empty temporary directory
191− isMacOS, // Platform checks
192− isWindows,
193− isPosix,
194− gcTick, // Trigger garbage collection
195− withoutAggressiveGC, // Disable aggressive GC for performance tests
196−} from "harness";
197−```
93+## Key Rules
19894
199−### Error Testing
200−
201−Always check exit codes and test error scenarios:
202−
203−```ts
204−test("handles errors", async () => {
205− await using proc = Bun.spawn({
206− cmd: [bunExe(), "run", "invalid.js"],
207− env: bunEnv,
208− });
209−
210− const exitCode = await proc.exited;
211− expect(exitCode).not.toBe(0);
212−
213− // For synchronous errors
214− expect(() => someFunction()).toThrow("Expected error message");
215−});
216−```
217−
218−### Avoid dynamic import & require
219−
220−**Only** use dynamic import or require when the test is specifically testing something relataed to dynamic import or require. Otherwise, **always use module-scope import statements**.
221−
222−**BAD, do not do this**:
223−
224−```ts
225−test("foo", async () => {
226− // BAD: Unnecessary usage of dynamic import.
227− const { readFile } = await import("node:fs");
228−
229− expect(await readFile("ok.txt")).toBe("");
230−});
231−```
232−
233−**GOOD, do this:**
234−
235−```ts
236−import { readFile } from "node:fs";
237−test("foo", async () => {
238− expect(await readFile("ok.txt")).toBe("");
239−});
240−```
241−
242−### Test Utilities
243−
244−- Use `describe.each()` for parameterized tests
245−- Use `toMatchSnapshot()` for snapshot testing
246−- Use `beforeAll()`, `afterEach()`, `beforeEach()` for setup/teardown
247−- Track resources (servers, clients) in arrays for cleanup in `afterEach()`
95+- Use `.$call`/`.$apply` not `.call`/`.apply`
96+- String literal `require()` only
97+- Export via `export default {}`
98+- Use JSC intrinsics for performance
99+- Run `bun bd` after changes
248100
