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