Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
220 commits
Select commit Hold shift + click to select a range
87b4005
test: run process.on('exit') handlers in `bun test`
cirospaciari Jul 17, 2026
5ec1092
node:test: implement run()
cirospaciari Jul 17, 2026
9c7ab47
node:test: expectFailure, skipped-suite semantics, and run() event fi…
cirospaciari Jul 17, 2026
f4887dd
node:test: read serialized error fields once when rebuilding the event
cirospaciari Jul 17, 2026
a7e40f2
node:test: address review comments
cirospaciari Jul 17, 2026
f9677c8
node:test: lazy TestsStream, reuse module-scope assert
cirospaciari Jul 17, 2026
fddbcb9
test: don't assert empty stdout for the exit-handler run (Windows ban…
cirospaciari Jul 18, 2026
a894d04
test: drain the event loop for node tests; fix addAbortListener and S…
cirospaciari Jul 18, 2026
6117530
Merge branch 'main' into claude/node-test-run-api
cirospaciari Jul 18, 2026
ff4a202
addAbortListener: keep listenerCount observable; clippy + run() gates
robobun Jul 18, 2026
38b1ccd
events: null-prototype disposable from addAbortListener
cirospaciari Jul 18, 2026
b767c77
test: rename suite-body markers to avoid the diff-hygiene grep
robobun Jul 18, 2026
f544f17
node:test: tag expectFailure validation mismatch as testCodeFailure
robobun Jul 18, 2026
e2eeadb
Merge branch 'main' into claude/node-test-run-api
cirospaciari Jul 18, 2026
385f7a5
test: cover an expectFailure error that misses the validator
cirospaciari Jul 18, 2026
4991483
node:test run(): apply serialized error.name on the parent side; net:…
robobun Jul 18, 2026
2de9193
addAbortListener: remove the algorithm before the counted listener on…
robobun Jul 18, 2026
d7ec6b8
node:test: `--test` CLI mode, node:test/reporters, and standalone exe…
cirospaciari Jul 17, 2026
5ba8f31
node:test: run({ isolation: 'none' })
cirospaciari Jul 17, 2026
cb1744e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 18, 2026
4f715d9
runner: skip vendored node fixtures in discovery; satisfy duplicate-p…
cirospaciari Jul 18, 2026
e3f4998
review: drain flag for run() children, null-proto descriptors, drop d…
robobun Jul 18, 2026
b9b8cfd
node:test run(): validate concurrency/timeout/signal; events: cache t…
robobun Jul 18, 2026
e9db61b
ci: retrigger
robobun Jul 18, 2026
09f31c6
node:test run(): null-proto at the fileNode spread sites
robobun Jul 18, 2026
0a0162a
test_command: use on_before_exit() for the opt-in drain; node:test: i…
robobun Jul 18, 2026
4854fdf
node:test run(): use makeTestFailure for the file-died error; Error.i…
robobun Jul 18, 2026
16f3806
node:test: match node's pass() expectFailure check; null-proto republ…
robobun Jul 18, 2026
ea84bd7
Merge branch 'main' into claude/node-test-run-api
cirospaciari Jul 19, 2026
93e8682
Merge branch 'claude/node-test-run-api' into claude/node-test-cli-mode
robobun Jul 19, 2026
ec985a1
test.reporters: build the TAP todo directive via kTodoDirective
robobun Jul 19, 2026
c9fc677
node:test: check child's own options.todo in scheduleSubtest rollup
robobun Jul 19, 2026
e8e7778
ci: retrigger
robobun Jul 19, 2026
a1d1194
node:test: track own-todo (options.todo or test.todo()) for the subte…
robobun Jul 19, 2026
dd51fcf
node:test: reporter-output parity with node
cirospaciari Jul 20, 2026
767d046
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 20, 2026
5b7af13
test-runner-misc: drop upstream's future-work note
robobun Jul 20, 2026
d6d7bf4
node-test.test: size the pending-body hang guard for debug builds
robobun Jul 20, 2026
bca3a55
lint: read conditionally-checked properties into locals in node:test …
cirospaciari Jul 20, 2026
d2ca000
test: convert fixture file URLs with fileURLToPath before passing to …
cirospaciari Jul 20, 2026
96357f6
test: quarantine worker-terminate ASAN crashes, matching main
cirospaciari Jul 20, 2026
acbf62d
node:test: plan({wait:true}) keepalive; tapEscape order; Windows test…
robobun Jul 20, 2026
1b1cfe0
node:test: per-run interrupt state; settle suites on sync throw; exac…
robobun Jul 20, 2026
30fcb3a
node:test: surface suite.error; cancel children on setup failure; per…
robobun Jul 20, 2026
5a423b7
test: quarantine musl-only onGC timing flakes, matching #34631
cirospaciari Jul 20, 2026
4b4c7b3
node:test: bubble expectFailure suite verdict; forward todo reason; d…
robobun Jul 21, 2026
a80ea90
gate: retrigger (release build infra failure)
robobun Jul 21, 2026
560eaeb
node:test: recurse reportCancelledNode into suite children; sequentia…
robobun Jul 21, 2026
6b16384
node:test: surface reporter-destination write errors; TestContext for…
robobun Jul 21, 2026
c99981f
node:test: record async-describe rejection; chain events for empty su…
robobun Jul 21, 2026
e1a70e1
node:test: await reporter attachment; wire run({signal}); gate latche…
robobun Jul 21, 2026
ecca446
node:test: swallow run-child todo-suite describe/after failures as ad…
robobun Jul 21, 2026
55586fc
node:test: onlyFlag for test.only spelling; await suite builds before…
robobun Jul 21, 2026
7cd73d0
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 21, 2026
fa0787a
node:test: sync-throw cancels children; settle inline suites; gate ho…
robobun Jul 21, 2026
e566c8e
test: take main's mimalloc page-count bound in node-net.test.ts
cirospaciari Jul 21, 2026
308eb76
node:test: uninstall attribution listeners on in-process restore; unc…
robobun Jul 21, 2026
26af07c
node:test: resolve all --test reporters before piping; per-file wrapp…
robobun Jul 21, 2026
5dc1e0e
test: take main's socket-end-drain fix (#34678)
cirospaciari Jul 21, 2026
acbb6b1
node:test: --test driver writes exitCode only on failure; directive-o…
robobun Jul 21, 2026
955b811
node:test: resolve reporters before run(); renumber test:complete at …
robobun Jul 21, 2026
710b36a
node:test: preserve evaluation-time reporter errors; standalone repor…
robobun Jul 21, 2026
6d44794
node:test: fail the run on a suite-only test:fail; file-wrapper testN…
robobun Jul 21, 2026
0e7cdf9
node:test: guard jsToYaml assertion recursion; wrap failed-import err…
robobun Jul 21, 2026
df13c24
ci: allow binary size growth for the node-v26 compat stack [allow size]
cirospaciari Jul 21, 2026
0afc5fb
run_command: merge positionals into passthrough in exec_as_if_node's …
robobun Jul 21, 2026
42d6a6e
run_command: delegate exec_as_if_node's eval branch to exec_eval; att…
robobun Jul 21, 2026
6ab1749
node:test: use the captured callerRoot throughout runFilesInProcess
robobun Jul 21, 2026
9096d92
ci: allow binary size growth for the node-v26 compat stack [allow size]
cirospaciari Jul 21, 2026
51cf321
node:test: reportCancelledNode each queued entry on root before-hook …
robobun Jul 21, 2026
657cc68
ci: allow binary size growth for the node-v26 compat stack [allow size]
cirospaciari Jul 21, 2026
f5b1414
node:test: let .only fall through to options.skip/todo in effectiveMode
robobun Jul 21, 2026
f11d351
node:test: schedule inline-subtest skip directive onto subtestChain
robobun Jul 22, 2026
c0c078b
Merge branch 'main' into claude/node-test-run-api
cirospaciari Jul 22, 2026
fb017b7
ci: allow binary size growth for the node-v26 compat stack [allow size]
cirospaciari Jul 22, 2026
4d3b110
node:test: emit test:start before file-level test:fail; read .message…
robobun Jul 22, 2026
d0e60e7
node:test: widen process-error attribution to cover beforeEach/afterE…
robobun Jul 22, 2026
0cc9752
ci: allow binary size growth for the node-v26 compat stack [allow size]
cirospaciari Jul 22, 2026
d10faf6
Merge remote-tracking branch 'origin/main' into claude/node-test-run-api
robobun Jul 22, 2026
10ee1b6
node:test: hoist arrows and anonymous callbacks into named functions
cirospaciari Jul 23, 2026
48c65c5
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
1afebf0
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
da3187a
Merge origin/main into claude/node-test-cli-mode [allow size]
robobun Jul 23, 2026
cc53012
Merge remote-tracking branch 'origin/main' into claude/node-test-run-api
robobun Jul 23, 2026
a647e0f
Merge claude/node-test-run-api (synced with main) into claude/node-te…
robobun Jul 23, 2026
dc1c713
node:test: route t.diagnostic() through the reporter stream; emit roo…
robobun Jul 23, 2026
f5eef45
node:test: use presence-based todo detection for inline t.test ownTod…
robobun Jul 23, 2026
60f0f8f
node:test: do not leak the run-child stdout protocol in the standalon…
robobun Jul 23, 2026
9d83fd8
node:test: recurse pruneToOnly into an only-marked suite with only-ma…
robobun Jul 23, 2026
ce0f534
node:test: cover nested .only narrowing under isolation none
cirospaciari Jul 23, 2026
d90ed93
node:test: stamp suite startedAtMs before its first child runs; junit…
robobun Jul 23, 2026
0771bce
node:test: cover the suite duration span under isolation none
cirospaciari Jul 23, 2026
59e7e51
node:test: account suite hook failures in the run-child verdict
cirospaciari Jul 23, 2026
c84de36
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
3172058
ci: keep the binary size allowance on the stack tip [allow size]
robobun Jul 23, 2026
0889d36
Merge branch 'main' into claude/node-test-run-api
cirospaciari Jul 23, 2026
853214d
node:test: implement the deferred run() fidelity and option-gating fo…
robobun Jul 23, 2026
2136128
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
a9fc164
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
7dbabb7
node:test: fix oxlint destructure warnings, ownTodo rollup for inline…
robobun Jul 23, 2026
2df3702
Merge branch 'claude/node-test-run-api' of https://github.com/oven-sh…
robobun Jul 23, 2026
1ae66a4
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
fadda01
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
9b1e4be
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
8e70112
node:test: stamp todoFlag on describe.todo suites at collection time
robobun Jul 23, 2026
6273e79
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
534bb16
node:test: restore the shape guard on parsed run-child events [allow …
robobun Jul 23, 2026
e5e1b1b
node:test: skip malformed run-child events instead of erroring the ru…
cirospaciari Jul 23, 2026
fc7369b
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
7646a1b
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 23, 2026
c9bfa6c
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
6c8b67f
node:test: skip nested suite before/after in run-child mode when an a…
robobun Jul 23, 2026
833c313
node:test: skip-wins for .todo + {skip}, preserve --todo verdict unde…
robobun Jul 23, 2026
f18f3a0
node:test: match node's run() event fidelity for numbering, causes, a…
cirospaciari Jul 23, 2026
030ecad
node:test: restore __proto__:null on the TestsStream super() options …
robobun Jul 23, 2026
a774b88
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
53a27fd
node:test: fix junit escape test fixture newline escaping
cirospaciari Jul 23, 2026
45afe76
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
d20bd16
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 23, 2026
bdb4ebe
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
2a38752
node:test: also skip later same-suite before() hooks once the first f…
robobun Jul 23, 2026
fc6b142
node:test: share the run() verdict counter with failed imports and ba…
cirospaciari Jul 23, 2026
b1394dc
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
77d0a59
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 23, 2026
b35b967
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
a91a1dc
node:test run(): stop spawning after abort, defuse stderr drain, and …
robobun Jul 23, 2026
c253f1d
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
9e7f106
node:test: settle the run-child suite completion asynchronously like …
robobun Jul 23, 2026
f92c861
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
c5a6822
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 23, 2026
d160caa
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 23, 2026
0f7f4a0
test(node:test): run the new run()/reporter tests concurrently [allow…
robobun Jul 23, 2026
616af8f
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
eac9dab
node:test run(): gate concurrency, emit file-level pass for zero-test…
robobun Jul 23, 2026
72cd9f6
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 23, 2026
e3eef3c
node:test: zero-test run() files report a file-level pass like node
cirospaciari Jul 24, 2026
98c0a16
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
783dfe5
node:test: keep serializeRunCause pipe-safe for non-JSON-safe causes …
robobun Jul 24, 2026
4d844c5
node:test: keep run() events flowing when a cause cannot cross the JS…
cirospaciari Jul 24, 2026
38ad993
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
c277d8d
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
9751880
test(node:test): double-escape the Windows path separator in the zero…
robobun Jul 24, 2026
e809f20
node:test run(): carry todo on inline-suite completion and count the …
robobun Jul 24, 2026
d729cd8
Merge claude/node-test-run-api into claude/node-test-cli-mode [allow …
robobun Jul 24, 2026
436e556
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
a49f3a2
node:test: chain execution-phase skip directive emits onto subtestChain
robobun Jul 24, 2026
c3b2d73
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
2e368e6
node:test: wrap every hook failure at runHook and carry non-finite nu…
cirospaciari Jul 24, 2026
4fb7ef5
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 24, 2026
cd86400
node:test: wrap hook failures at runHook; gate NaN/Infinity on the JS…
robobun Jul 24, 2026
147166b
Merge claude/node-test-run-api into claude/node-test-cli-mode [allow …
robobun Jul 24, 2026
2d0ee5e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
22d1844
node:test run(): drop dead subtestsFailed branch in runOneFile
robobun Jul 24, 2026
65f719c
node:test: tag non-finite numbers on the serializer side too
cirospaciari Jul 24, 2026
5cfa22d
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
e56fb61
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 24, 2026
071f361
Merge claude/node-test-run-api into claude/node-test-cli-mode [allow …
robobun Jul 24, 2026
b054373
node:test: carry object assertion extras over the run() pipe and drop…
cirospaciari Jul 24, 2026
cdd3554
node:test: inspect() fallback for object-valued error extras on the p…
robobun Jul 24, 2026
e8b6064
Merge branch 'main' into claude/node-test-run-api
dylan-conway Jul 24, 2026
3dd3131
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
50e6cf5
node:test: emit runStandalone's root plan direct; defer failing-descr…
robobun Jul 24, 2026
f07091e
node:test: declaration-order events for throwing describe bodies, dri…
cirospaciari Jul 24, 2026
9ab4918
node:test run(): report remaining files as cancelledByParent on abort…
robobun Jul 24, 2026
0e119ce
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
10c2a31
Merge claude/node-test-run-api into claude/node-test-cli-mode
cirospaciari Jul 24, 2026
851f9e1
Merge claude/node-test-run-api into claude/node-test-cli-mode [allow …
robobun Jul 24, 2026
840858d
node:test: emit abort-skipped file verdicts only for a pre-aborted si…
cirospaciari Jul 24, 2026
c471ac0
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
355bc75
Merge main into claude/node-test-cli-mode [allow size]
robobun Jul 24, 2026
96eefbb
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
e7ebb55
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
39564fb
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
b66c322
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
de0dd5e
test_command: suppress the --bail stderr message for node:test run() …
robobun Jul 24, 2026
7395cf8
test runner: suppress the bail notice in node:test run() children
cirospaciari Jul 24, 2026
6a040e2
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
5a052ac
Merge origin/claude/node-test-cli-mode
cirospaciari Jul 24, 2026
a44a46a
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
3e18aa4
node:test: make preAborted and mid-run interrupt paths mutually exclu…
robobun Jul 24, 2026
47550f7
Merge main into claude/node-test-cli-mode
cirospaciari Jul 24, 2026
4353794
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 24, 2026
99d0a7a
node:test: use a collision-free _bunTag envelope for non-primitive er…
robobun Jul 24, 2026
08e5952
node:test: emit a cancelled suite's test:complete post-order like nod…
robobun Jul 24, 2026
1ec8b7f
node:test: round-trip BigInt actual/expected over the JSON pipe via t…
robobun Jul 24, 2026
fb8bb3a
node:test: run a run-child suite's after() hooks from its settleSuite…
robobun Jul 24, 2026
35063b7
node:test: unwrap every serializeRunError extra in rebuildError and s…
robobun Jul 24, 2026
90df8f0
test(node:test): branch the file-level timeout on isASAN too so the r…
robobun Jul 24, 2026
9f8274b
test(node:test): include stderr in the marker-inject assertion so chi…
robobun Jul 24, 2026
d8906f4
node:test: share one _bunTag envelope for causes and extras so the ne…
robobun Jul 24, 2026
f68bd0f
test(node:test): assert stderr and exitCode at the five driver tests …
robobun Jul 24, 2026
18e1ebd
node:test: honor opts.signal under isolation:'none' and queue a faile…
robobun Jul 24, 2026
497041a
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
f24120e
node:test: destructure entry.importError into a local for the oxlint …
robobun Jul 24, 2026
77e81c8
node:test: boot the eval driver for bun run --test, include inProcess…
robobun Jul 24, 2026
dec9949
node:test: harden rebuildError/reviveSerializedValue against hostile-…
robobun Jul 24, 2026
408f3aa
node:test: capture preAborted in runFilesInProcess like its runFiles …
robobun Jul 24, 2026
d31df30
cli: drop RunCommand from the --test eval-driver gate; exec_auto_or_r…
robobun Jul 24, 2026
90ba5ca
node:test: report a Bun.spawn sync throw in runOneFile as a file-leve…
robobun Jul 24, 2026
f04f2ea
node:test: restore the acceptedXfail guard at the three post-body cat…
robobun Jul 24, 2026
bfc0a98
ci: retrigger
robobun Jul 24, 2026
6c9023a
node:test: emit per-file testAborted verdicts on a pre-aborted signal…
robobun Jul 24, 2026
d90ad36
node:test: isolation 'none' ignores the run signal for scheduling lik…
cirospaciari Jul 25, 2026
8cd98c8
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 25, 2026
5cff563
node:test: finish removing the in-process scheduling signal wiring
cirospaciari Jul 25, 2026
42380b5
node:test: correct the isolation:'none' signal tests to node's observ…
cirospaciari Jul 25, 2026
e7b48be
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 25, 2026
59f19e1
Merge main into claude/node-test-cli-mode
cirospaciari Jul 25, 2026
dcc8661
ci: keep the binary size allowance on the stack tip [allow size]
cirospaciari Jul 25, 2026
42478f1
node:test: zero a skipped suite's childrenFailed like isTodo, fall ba…
robobun Jul 25, 2026
91e30b7
Merge remote-tracking branch 'origin/main' into claude/node-test-cli-…
robobun Aug 3, 2026
2ed2603
trim comments to <=3 lines, cite spec/node source
robobun Aug 3, 2026
c0cd4ea
node:test/reporters: inline the junit-only require('node:os').hostnam…
robobun Aug 3, 2026
fe59930
Merge remote-tracking branch 'origin/main' into claude/node-test-cli-…
robobun Aug 4, 2026
0f43d83
node:test: prune on .only in pure standalone mode like the runFilesIn…
robobun Aug 4, 2026
cd8fd15
Merge branch 'main' into node-test-cli-mode
cirospaciari Aug 5, 2026
60c2f9a
Merge remote-tracking branch 'origin/main' into claude/node-test-cli-…
robobun Aug 7, 2026
976a2e2
Merge branch 'main' into node-test-cli-mode
cirospaciari Aug 7, 2026
b58d8bf
Trim comments to node-source/spec references
robobun Aug 7, 2026
8045b1d
node:test: gate the five skip sites on the node's presence-based skip…
robobun Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions scripts/runner.node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2292,6 +2292,11 @@ function isTest(path) {
* @returns {boolean}
*/
function isTestStrict(path) {
// Vendored node fixtures keep upstream names like `two.test.js` but only
// work when driven by their parallel/ test (e.g. run({ isolation:'none' })).
if (path.replaceAll(sep, "/").includes("js/node/test/fixtures/")) {
return false;
}
return isJavaScript(path) && /\.test|spec\./.test(basename(path));
}

Expand Down
1 change: 1 addition & 0 deletions src/bun_core/env_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ platform_specific_new!(pub LIBRARY_PATH: string, posix = "LIBRARY_PATH", windows
// Opt-in for the vendored node:test suite and run() children.
new!(pub BUN_TEST_DRAIN_EVENT_LOOP: boolean, "BUN_TEST_DRAIN_EVENT_LOOP", { default: false });
new!(pub BUN_TMPDIR: string, "BUN_TMPDIR", {});
new!(pub NODE_TEST_CONTEXT: string, "NODE_TEST_CONTEXT", {});
new!(pub BUN_WATCHER_TRACE: string, "BUN_WATCHER_TRACE", {});
new!(pub CI: boolean, "CI", {});
new!(pub CI_COMMIT_SHA: string, "CI_COMMIT_SHA", {});
Expand Down
349 changes: 349 additions & 0 deletions src/js/eval/node_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
import { createWriteStream } from "node:fs";
import { resolve, sep } from "node:path";
import { PassThrough } from "node:stream";
import { run } from "node:test";
import reporters from "node:test/reporters";
import { debuglog } from "node:util";

const debug = debuglog("test_runner");

const kBooleanFlags = new Set([
"--test",
"--test-only",
"--test-force-exit",
"--test-randomize",
"--test-update-snapshots",
"--experimental-test-coverage",
"--experimental-test-module-mocks",
"--experimental-test-snapshots",
]);

function parseExecArgv() {
const single = new Map<string, string>();
const multi = new Map<string, string[]>();
const bools = new Set<string>();
const argv = process.execArgv;
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (!arg.startsWith("--")) continue;
const eq = arg.indexOf("=");
let name: string;
let value: string | undefined;
if (eq !== -1) {
name = arg.slice(0, eq);
value = arg.slice(eq + 1);
} else {
name = arg;
if (!kBooleanFlags.has(name) && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
value = argv[++i];
}
}
if (value === undefined) {
bools.add(name);
} else {
single.set(name, value);
let list = multi.get(name);
if (list === undefined) {
list = [];
multi.set(name, list);
}
list.push(value);
}
}
return { single, multi, bools };
}

const flags = parseExecArgv();

function getFlag(name: string) {
return flags.single.get(name);
}

function getFlagList(name: string) {
return flags.multi.get(name) ?? [];
}

function hasFlag(name: string) {
return flags.bools.has(name) || flags.single.has(name);
}

function fatal(err: unknown): never {
console.error(err);
process.exit(1);
}

// File discovery — node's createTestFileList / kDefaultPattern:
// https://github.com/nodejs/node/blob/main/lib/internal/test_runner/runner.js
// Split into two globs: Bun.Glob mis-parses `test/**/*` nested in a brace group.
const kDefaultPatterns = ["**/{test,test-*,*[._-]test}.{js,mjs,cjs}", "**/test/**/*.{js,mjs,cjs}"];
const kGlobMagic = /[*?[\]{}!]/;
function hasNoGlobMagic(pattern) {
return !kGlobMagic.test(pattern);
}

function createTestFileList(patterns: string[], cwd: string): string[] {
Comment thread
robobun marked this conversation as resolved.
const { statSync } = require("node:fs");
const usingDefault = patterns.length === 0;
if (usingDefault) patterns = kDefaultPatterns;

const results = new Set<string>();
for (const pattern of patterns) {
if (!kGlobMagic.test(pattern)) {
const absolute = resolve(cwd, pattern);
let stat;
try {
stat = statSync(absolute);
} catch (err) {
if ((err as { code?: string })?.code === "ENOENT") continue;
throw err;
}
Comment thread
robobun marked this conversation as resolved.
if (stat.isFile()) {
results.add(absolute);
} else if (stat.isDirectory()) {
for (const defaultPattern of kDefaultPatterns) {
for (const match of new Bun.Glob(defaultPattern).scanSync({ cwd: absolute, onlyFiles: true })) {
if (hasNodeModulesSegment(match)) continue;
results.add(resolve(absolute, match));
}
}
}
continue;
}
for (const match of new Bun.Glob(pattern).scanSync({ cwd, onlyFiles: true })) {
if (hasNodeModulesSegment(match)) continue;
results.add(resolve(cwd, match));
}
}

if (!usingDefault && results.size === 0 && patterns.every(hasNoGlobMagic)) {
console.error(`Could not find '${patterns.join(", ")}'`);
process.exit(1);
}

return Array.from(results).sort();
}

function hasNodeModulesSegment(match: string) {
return match.split(sep).includes("node_modules") || match.split("/").includes("node_modules");
}

const kBuiltinReporters = {
__proto__: null,
dot: reporters.dot,
junit: reporters.junit,
spec: reporters.spec,
tap: reporters.tap,
lcov: reporters.lcov,
};

async function resolveReporter(name: string) {
let reporter: unknown = kBuiltinReporters[name];
if (reporter === undefined) {
const specifier = name.startsWith(".") ? resolve(process.cwd(), name) : name;
let mod;
try {
mod = await import(specifier);
} catch (err) {
if ((err as { name?: string })?.name === "ResolveMessage") {
const error = new Error((err as Error)?.message ?? String(err));
(error as { code?: string }).code = (err as { code?: string })?.code ?? "ERR_MODULE_NOT_FOUND";
throw error;
}
throw err;
}
Comment thread
robobun marked this conversation as resolved.
reporter = mod.default ?? mod;
}
// The own-constructor identity check keeps bundled async generators (whose
// shared prototype carries an AsyncGeneratorFunction constructor) as-is.
if (
(reporter as { prototype?: object })?.prototype &&
Object.getOwnPropertyDescriptor((reporter as { prototype: object }).prototype, "constructor")?.value === reporter
) {
reporter = new (reporter as new () => unknown)();
}
if (typeof reporter !== "function" && !(reporter && typeof (reporter as any).pipe === "function")) {
const error = new TypeError(
`The "Reporter" argument must be a function or a stream. Received ${reporter === undefined ? "undefined" : typeof reporter}`,
);
(error as { code?: string }).code = "ERR_INVALID_ARG_TYPE";
throw error;
}
return reporter;
}

function destinationFor(dest: string) {
if (dest === "stdout") return process.stdout;
if (dest === "stderr") return process.stderr;
return createWriteStream(resolve(process.cwd(), dest));
}

function attachReporter(reporter, source, destination): Promise<void> {
const { compose } = require("node:stream");
const endDestination = destination !== process.stdout && destination !== process.stderr;
function reporterExecutor(resolvePromise, rejectPromise) {
const composed = compose(source, reporter);
composed.on("error", rejectPromise);
const out = composed.pipe(destination, { end: endDestination });
out.on("error", rejectPromise);
if (endDestination) {
destination.on("finish", resolvePromise);
destination.on("error", rejectPromise);
} else {
composed.on("end", resolvePromise);
}
}
return new Promise(reporterExecutor);
}

async function main() {
const cwd = process.cwd();
const patterns = process.argv.slice(1);

const reporterNames = getFlagList("--test-reporter");
const destinationNames = getFlagList("--test-reporter-destination");
if (reporterNames.length === 0 && destinationNames.length === 0) {
reporterNames.push("spec");
destinationNames.push("stdout");
} else if (reporterNames.length === 1 && destinationNames.length === 0) {
destinationNames.push("stdout");
} else if (reporterNames.length !== destinationNames.length) {
const { inspect } = require("node:util");
const error = new TypeError(
`The argument '--test-reporter' must match the number of specified '--test-reporter-destination'. ` +
`Received ${inspect(reporterNames)}`,
);
(error as { code?: string }).code = "ERR_INVALID_ARG_VALUE";
fatal(error);
}

let files = createTestFileList(patterns, cwd);

const shard = getFlag("--test-shard");
if (shard !== undefined) {
const match = /^(\d+)\/(\d+)$/.exec(shard);
if (match === null) {
const error = new TypeError(
`The argument '--test-shard' must be in the form of <index>/<total>. Received '${shard}'`,
);
(error as { code?: string }).code = "ERR_INVALID_ARG_VALUE";
fatal(error);
}
const index = Number(match[1]);
const total = Number(match[2]);
if (index < 1 || index > total) {
const error = new RangeError(
`The value of "index" is out of range. It must be >= 1 && <= ${total}. Received ${index}`,
);
(error as { code?: string }).code = "ERR_OUT_OF_RANGE";
fatal(error);
}
function isThisShard(_, i: number) {
return i % total === index - 1;
}
files = files.filter(isThisShard);
}

const runOptions: Record<string, unknown> = { __proto__: null, files, cwd };

const isolation = getFlag("--test-isolation") ?? getFlag("--experimental-test-isolation");
const concurrencyFlag = getFlag("--test-concurrency");
if (isolation === "none") {
runOptions.concurrency = 1;
} else if (concurrencyFlag !== undefined) {
runOptions.concurrency = Number(concurrencyFlag);
} else {
runOptions.concurrency = true;
}

const timeout = getFlag("--test-timeout");
runOptions.timeout = timeout !== undefined ? Number(timeout) : Infinity;

runOptions.only = hasFlag("--test-only");
runOptions.forceExit = hasFlag("--test-force-exit");

if (getFlagList("--test-name-pattern").length > 0) {
fatal(new Error("--test-name-pattern is not yet implemented in Bun's node:test CLI mode"));
}
if (getFlagList("--test-skip-pattern").length > 0) {
fatal(new Error("--test-skip-pattern is not yet implemented in Bun's node:test CLI mode"));
}
if (hasFlag("--test-only")) {
fatal(new Error("--test-only is not yet implemented in Bun's node:test CLI mode"));
}
const tagFilters = getFlagList("--experimental-test-tag-filter");
if (tagFilters.length > 0) runOptions.testTagFilters = tagFilters;

if (hasFlag("--experimental-test-coverage")) runOptions.coverage = true;
if (hasFlag("--test-randomize") || getFlag("--test-random-seed") !== undefined) {
fatal(new Error("--test-randomize is not yet implemented in Bun's node:test CLI mode"));
}
const globalSetup = getFlag("--test-global-setup");
if (globalSetup !== undefined) runOptions.globalSetupPath = resolve(cwd, globalSetup);
if (isolation !== undefined) runOptions.isolation = isolation;

debug("run options: %o", runOptions);

let resolved: unknown[];
try {
resolved = await Promise.all(reporterNames.map(resolveReporter));
} catch (err) {
console.error(require("node:util").inspect(err));
process.exit(7);
}

const abortController = new AbortController();
runOptions.signal = abortController.signal;

// node's harness installs process signal handlers only under --test
// https://github.com/nodejs/node/blob/main/lib/internal/test_runner/harness.js
function onRunnerSignal() {
abortController.abort();
if (runOptions.isolation === "none") {
process.exit(1);
}
}
process.on("SIGINT", onRunnerSignal);
process.on("SIGTERM", onRunnerSignal);

let stream;
try {
stream = run(runOptions);
} catch (err) {
console.error(err);
process.exitCode = 1;
return;
}

let success = true;
function onTestSummary(data) {
if (data.file === undefined) success = data.success;
}
stream.on("test:summary", onTestSummary);

const reporterPromises: Promise<void>[] = [];
for (let i = 0; i < resolved.length; i++) {
const destination = destinationFor(destinationNames[i]);
const copy = new PassThrough({ objectMode: true });
stream.pipe(copy);
reporterPromises.push(attachReporter(resolved[i], copy, destination));
}

try {
await Promise.all(reporterPromises);
} catch (err) {
// A reporter that errors mid-stream: node's unfinished-TLA exit code.
abortController.abort();
console.error((err as Error)?.stack ?? err);
process.exit(7);
} finally {
process.off("SIGINT", onRunnerSignal);
process.off("SIGTERM", onRunnerSignal);
}

if (!success) process.exitCode = 1;
if (hasFlag("--test-force-exit")) {
process.exit(process.exitCode ?? 0);
}
}

await main();
Loading