@exodus/test 1.0.0-rc.102 → 1.0.0-rc.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/find-binary.js +2 -0
- package/bin/index.js +22 -17
- package/package.json +7 -5
- package/src/benchmark.js +54 -0
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
|
|
|
67
67
|
- `brave:puppeteer` — Brave
|
|
68
68
|
- `msedge:puppeteer` — Microsoft Edge
|
|
69
69
|
- Barebone engines (system-provided or installed with `npx jsvu` / `npx esvu`):
|
|
70
|
-
- `
|
|
70
|
+
- `v8:bundle` — [v8 CLI](https://v8.dev/docs/d8) (Chrome/Blink/Node.js JavaScript engine)
|
|
71
71
|
- `jsc:bundle` — [JavaScriptCore](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html) (Safari/WebKit JavaScript engine)
|
|
72
72
|
- `hermes:bundle` — [Hermes](https://hermesengine.dev) (React Native JavaScript engine)
|
|
73
73
|
- `spidermonkey:bundle` — [SpiderMonkey](https://spidermonkey.dev/) (Firefox/Gecko JavaScript engine)
|
package/bin/find-binary.js
CHANGED
|
@@ -65,6 +65,8 @@ function findBinaryOnce(name) {
|
|
|
65
65
|
return findFile([jsvu, esvu], false)
|
|
66
66
|
case 'electron':
|
|
67
67
|
return require('electron')
|
|
68
|
+
case 'workerd':
|
|
69
|
+
return require.resolve('workerd/bin/workerd')
|
|
68
70
|
case 'c8':
|
|
69
71
|
return require.resolve('c8/bin/c8.js')
|
|
70
72
|
case 'chrome':
|
package/bin/index.js
CHANGED
|
@@ -21,23 +21,25 @@ const bundleOpts = { pure: true, bundle: true, esbuild: true, ts: 'auto' }
|
|
|
21
21
|
const bareboneOpts = { ...bundleOpts, barebone: true }
|
|
22
22
|
const hermesA = ['-Og', '-Xmicrotask-queue']
|
|
23
23
|
const denoA = ['run', '--allow-all'] // also will set DENO_COMPAT=1 env flag below
|
|
24
|
+
const denoT = ['test', '--allow-all']
|
|
25
|
+
const nodeTS = process.features.typescript ? 'auto' : 'flag'
|
|
24
26
|
const ENGINES = new Map(
|
|
25
27
|
Object.entries({
|
|
26
|
-
'node:test': { binary: 'node',
|
|
27
|
-
'node:pure': { binary: 'node', pure: true, loader: '--import', ts:
|
|
28
|
-
'node:bundle': { binary: 'node', ...bundleOpts },
|
|
28
|
+
'node:test': { binary: 'node', loader: '--import', ts: nodeTS, haveIsOk: true },
|
|
29
|
+
'node:pure': { binary: 'node', pure: true, loader: '--import', ts: nodeTS, haveIsOk: true },
|
|
30
|
+
'node:bundle': { binary: 'node', binaryArgs: ['--expose-gc'], ...bundleOpts },
|
|
29
31
|
'bun:test': { binary: 'bun', ts: 'auto' },
|
|
30
32
|
'bun:pure': { binary: 'bun', pure: true, ts: 'auto' },
|
|
31
33
|
'bun:bundle': { binary: 'bun', ...bundleOpts },
|
|
32
|
-
'electron-as-node:test': { binary: 'electron',
|
|
34
|
+
'electron-as-node:test': { binary: 'electron', loader: '--import', ts: 'flag' },
|
|
33
35
|
'electron-as-node:pure': { binary: 'electron', pure: true, loader: '--import', ts: 'flag' },
|
|
34
|
-
'electron-as-node:bundle': { binary: 'electron', ...bundleOpts },
|
|
36
|
+
'electron-as-node:bundle': { binary: 'electron', binaryArgs: ['--expose-gc'], ...bundleOpts },
|
|
35
37
|
'electron:bundle': { binary: 'electron', electron: true, ...bundleOpts },
|
|
36
|
-
'deno:test': { binary: 'deno',
|
|
38
|
+
'deno:test': { binary: 'deno', binaryArgs: denoT, loader: '--preload', ts: 'auto' },
|
|
37
39
|
'deno:pure': { binary: 'deno', binaryArgs: denoA, pure: true, loader: '--preload', ts: 'auto' },
|
|
38
40
|
'deno:bundle': { binary: 'deno', binaryArgs: ['run'], target: 'deno1', ...bundleOpts },
|
|
39
41
|
// Barebone engines
|
|
40
|
-
'
|
|
42
|
+
'v8:bundle': { binary: 'd8', binaryArgs: ['--expose-gc'], ...bareboneOpts },
|
|
41
43
|
'jsc:bundle': { binary: 'jsc', target: 'safari13', ...bareboneOpts },
|
|
42
44
|
'hermes:bundle': { binary: 'hermes', binaryArgs: hermesA, target: 'es2018', ...bareboneOpts },
|
|
43
45
|
'spidermonkey:bundle': { binary: 'spidermonkey', ...bareboneOpts },
|
|
@@ -59,7 +61,7 @@ const ENGINES = new Map(
|
|
|
59
61
|
'msedge:playwright': { binary: 'msedge', browsers: 'playwright', ...bundleOpts },
|
|
60
62
|
})
|
|
61
63
|
)
|
|
62
|
-
const barebonesOk = ['d8', 'spidermonkey', 'quickjs', 'xs', 'hermes']
|
|
64
|
+
const barebonesOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes']
|
|
63
65
|
const barebonesUnhandled = ['jsc', 'escargot', 'boa', 'graaljs', 'engine262']
|
|
64
66
|
|
|
65
67
|
const getEnvFlag = (name) => {
|
|
@@ -272,6 +274,7 @@ const { options, patterns } = parseOptions()
|
|
|
272
274
|
|
|
273
275
|
const engineName = `${options.engine} engine` // used for warnings to user
|
|
274
276
|
const engineFlagError = (flag) => `${engineName} does not support --${flag}`
|
|
277
|
+
if (options.engine === 'd8:bundle') options.engine = 'v8:bundle' // compat
|
|
275
278
|
const engineOptions = ENGINES.get(options.engine)
|
|
276
279
|
assert(engineOptions, `Unknown engine: ${options.engine}`)
|
|
277
280
|
Object.assign(options, engineOptions)
|
|
@@ -331,7 +334,6 @@ if (options.pure) {
|
|
|
331
334
|
|
|
332
335
|
args.push('--expose-internals') // this is unoptimal and hopefully temporary, see rationale in src/dark.cjs
|
|
333
336
|
} else if (options.engine === 'deno:test') {
|
|
334
|
-
args.push('test', '--allow-all')
|
|
335
337
|
assert(!options.jest, 'deno:test engine does not support --jest yet')
|
|
336
338
|
} else if (options.engine === 'bun:test') {
|
|
337
339
|
args.push('test')
|
|
@@ -340,6 +342,8 @@ if (options.pure) {
|
|
|
340
342
|
throw new Error('Unreachable')
|
|
341
343
|
}
|
|
342
344
|
|
|
345
|
+
if (!options.bundle && ['node', 'electron'].includes(options.platform)) args.push('--expose-gc') // for benchmarks
|
|
346
|
+
|
|
343
347
|
const ignore = ['**/node_modules']
|
|
344
348
|
let filter
|
|
345
349
|
if (process.env.EXODUS_TEST_IGNORE) {
|
|
@@ -421,13 +425,16 @@ if (options.jest) {
|
|
|
421
425
|
}
|
|
422
426
|
}
|
|
423
427
|
|
|
428
|
+
const cpus = availableParallelism()
|
|
429
|
+
// increase from default cpus - 1 on default GH CI runners, but not on browsers which already use other processes
|
|
430
|
+
if (!options.concurrency && isCI && !isBrowserLike && cpus === 2) options.concurrency = cpus
|
|
424
431
|
if (options.concurrency) {
|
|
425
432
|
const raw = options.concurrency
|
|
426
433
|
let concurrency = raw
|
|
427
434
|
if (typeof raw === 'string') {
|
|
428
435
|
if (/^\d{1,15}%$/u.test(raw)) {
|
|
429
436
|
const perc = Number(raw.slice(0, -1))
|
|
430
|
-
concurrency = Math.max(1, Math.round((perc *
|
|
437
|
+
concurrency = Math.max(1, Math.round((perc * cpus) / 100))
|
|
431
438
|
} else {
|
|
432
439
|
assert(/^\d{1,15}$/u.test(raw), `Wrong concurrency: ${raw}`)
|
|
433
440
|
concurrency = Number(raw)
|
|
@@ -591,7 +598,7 @@ if (options.binary === 'electron') {
|
|
|
591
598
|
}
|
|
592
599
|
}
|
|
593
600
|
|
|
594
|
-
if (options.barebone || options.binary
|
|
601
|
+
if (options.barebone || ['electron', 'workerd'].includes(options.binary)) {
|
|
595
602
|
options.binary = findBinary(options.binary)
|
|
596
603
|
options.binaryCanBeAbsolute = true
|
|
597
604
|
}
|
|
@@ -648,7 +655,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
|
|
|
648
655
|
}
|
|
649
656
|
|
|
650
657
|
const barebones = [...barebonesOk, ...barebonesUnhandled]
|
|
651
|
-
assertBinary(binary, ['node', 'bun', 'deno', 'electron', ...barebones
|
|
658
|
+
assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', ...barebones])
|
|
652
659
|
if (binary === c8 && process.platform === 'win32') {
|
|
653
660
|
;[binary, args] = ['node', [binary, ...args]]
|
|
654
661
|
}
|
|
@@ -690,13 +697,12 @@ if (options.pure) {
|
|
|
690
697
|
|
|
691
698
|
const failedBare = 'EXODUS_TEST_FAILED_EXIT_CODE_1'
|
|
692
699
|
const cleanOut = (out) => out.replaceAll(`\n${failedBare}\n`, '\n').replaceAll(failedBare, '')
|
|
693
|
-
const { binaryArgs = [] } = options
|
|
694
700
|
// Timeout is fallback if timeout in script hangs, 50x as it can be adjusted per-script inside them
|
|
695
701
|
// Do we want to extract timeouts from script code instead? Also, hermes might be slower, so makes sense to increase
|
|
696
702
|
const timeout = (options.testTimeout || jestConfig?.testTimeout || 5000) * 50
|
|
697
703
|
const start = process.hrtime.bigint()
|
|
698
704
|
try {
|
|
699
|
-
const fullArgs = [...binaryArgs, ...args, file]
|
|
705
|
+
const fullArgs = [...(options.binaryArgs ?? []), ...args, file]
|
|
700
706
|
const { code = 0, stdout, stderr } = await launch(options.binary, fullArgs, { timeout }, true)
|
|
701
707
|
const ms = Number(process.hrtime.bigint() - start) / 1e6
|
|
702
708
|
if (stdout.includes(failedBare)) return { ok: false, output: [cleanOut(stdout), stderr], ms }
|
|
@@ -722,7 +728,7 @@ if (options.pure) {
|
|
|
722
728
|
}
|
|
723
729
|
|
|
724
730
|
const { Queue } = await import('@chalker/queue')
|
|
725
|
-
const queue = new Queue(options.concurrency ||
|
|
731
|
+
const queue = new Queue(options.concurrency || cpus - 1)
|
|
726
732
|
const runConcurrent = async (file) => {
|
|
727
733
|
await queue.claim()
|
|
728
734
|
try {
|
|
@@ -758,7 +764,6 @@ if (options.pure) {
|
|
|
758
764
|
assert(['node:test', 'electron-as-node:test', 'deno:test', 'bun:test'].includes(options.engine))
|
|
759
765
|
setEnv('EXODUS_TEST_CONTEXT', 'node:test') // The context is always node:test in this branch
|
|
760
766
|
assert(files.length > 0) // otherwise we can run recursively
|
|
761
|
-
assert(!options.binaryArgs)
|
|
762
767
|
if (options.concurrency) args.push('--test-concurrency', options.concurrency)
|
|
763
768
|
if (['--inspect', '--inspect-brk', '--inspect-wait'].includes(options.devtools)) {
|
|
764
769
|
args.push(options.devtools)
|
|
@@ -770,7 +775,7 @@ if (options.pure) {
|
|
|
770
775
|
)
|
|
771
776
|
}
|
|
772
777
|
|
|
773
|
-
const { code } = await launch(options.binary, [...args, ...files])
|
|
778
|
+
const { code } = await launch(options.binary, [...(options.binaryArgs ?? []), ...args, ...files])
|
|
774
779
|
process.exitCode = code
|
|
775
780
|
}
|
|
776
781
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/test",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.104",
|
|
4
4
|
"author": "Exodus Movement, Inc.",
|
|
5
5
|
"description": "A test suite runner",
|
|
6
6
|
"homepage": "https://github.com/ExodusMovement/test",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"exports": {
|
|
29
29
|
"./node-test-reporter": "./bin/reporter.js",
|
|
30
30
|
"./loader/jest": "./loader/jest.js",
|
|
31
|
+
"./benchmark": "./src/benchmark.js",
|
|
31
32
|
"./expect": "./src/expect.cjs",
|
|
32
33
|
"./jest": "./src/jest.js",
|
|
33
34
|
"./node": "./src/node.js",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"loader/node-test.js",
|
|
55
56
|
"loader/typescript.js",
|
|
56
57
|
"loader/typescript.loader.js",
|
|
58
|
+
"src/benchmark.js",
|
|
57
59
|
"src/dark.cjs",
|
|
58
60
|
"src/engine.js",
|
|
59
61
|
"src/engine.node.cjs",
|
|
@@ -112,9 +114,8 @@
|
|
|
112
114
|
"test:chromium:playwright": "EXODUS_TEST_ENGINE=chromium:playwright npm run test:_bundle --",
|
|
113
115
|
"test:firefox:playwright": "EXODUS_TEST_ENGINE=firefox:playwright npm run test:_bundle --",
|
|
114
116
|
"test:webkit:playwright": "EXODUS_TEST_ENGINE=webkit:playwright npm run test:_bundle --",
|
|
115
|
-
"test:v8": "npm run test:d8 --",
|
|
116
117
|
"test:javascriptcore": "npm run test:jsc --",
|
|
117
|
-
"test:
|
|
118
|
+
"test:v8": "EXODUS_TEST_ENGINE=v8:bundle npm run test:_bundle --",
|
|
118
119
|
"test:jsc": "EXODUS_TEST_ENGINE=jsc:bundle npm run test:_bundle --",
|
|
119
120
|
"test:hermes": "EXODUS_TEST_ENGINE=hermes:bundle npm run test:_bundle --",
|
|
120
121
|
"test:spidermonkey": "EXODUS_TEST_ENGINE=spidermonkey:bundle npm run test:_bundle --",
|
|
@@ -154,7 +155,7 @@
|
|
|
154
155
|
"@types/jest-when": "^3.5.2",
|
|
155
156
|
"@types/node": "^24.0.11",
|
|
156
157
|
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
|
157
|
-
"electron": "^
|
|
158
|
+
"electron": "^37.3.1",
|
|
158
159
|
"eslint": "^8.44.0",
|
|
159
160
|
"esvu": "^1.2.16",
|
|
160
161
|
"jest": "^29.7.0",
|
|
@@ -163,7 +164,8 @@
|
|
|
163
164
|
"jest-when": "^3.6.0",
|
|
164
165
|
"jsdom": "^26.1.0",
|
|
165
166
|
"jsvu": "^3.0.0",
|
|
166
|
-
"prettier": "^3.0.3"
|
|
167
|
+
"prettier": "^3.0.3",
|
|
168
|
+
"workerd": "^1.20250826.0"
|
|
167
169
|
},
|
|
168
170
|
"peerDependencies": {
|
|
169
171
|
"@babel/register": "^7.0.0",
|
package/src/benchmark.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const fRps = (rps) => (rps > 10 ? Math.round(rps).toLocaleString() : rps.toPrecision(2))
|
|
2
|
+
const fTime = (ns) => {
|
|
3
|
+
const us = ns / 10n ** 3n
|
|
4
|
+
if (us < 2n) return `${ns}ns`
|
|
5
|
+
const ms = us / 10n ** 3n
|
|
6
|
+
if (ms < 2n) return `${us}μs`
|
|
7
|
+
const s = ms / 10n ** 3n
|
|
8
|
+
if (s < 10n) return `${ms}ms`
|
|
9
|
+
const min = s / 60n
|
|
10
|
+
return min < 5n ? `${s}s` : `${min}min`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const getTime = (() => {
|
|
14
|
+
if (globalThis.process) return () => process.hrtime.bigint()
|
|
15
|
+
if (globalThis.performance) return () => BigInt(Math.round(performance.now() * 1e6))
|
|
16
|
+
return () => BigInt(Math.round(Date.now() * 1e6))
|
|
17
|
+
})()
|
|
18
|
+
|
|
19
|
+
let gcWarned = false
|
|
20
|
+
export async function benchmark(name, options, fn) {
|
|
21
|
+
if (typeof options === 'function') [fn, options] = [options, undefined]
|
|
22
|
+
if (options?.skip) return
|
|
23
|
+
const { args, timeout = 1000 } = options ?? {}
|
|
24
|
+
|
|
25
|
+
if (globalThis.gc) {
|
|
26
|
+
for (let i = 0; i < 4; i++) globalThis.gc()
|
|
27
|
+
} else if (!gcWarned) {
|
|
28
|
+
gcWarned = true
|
|
29
|
+
console.log('Warning: no gc() available\n')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let min, max
|
|
33
|
+
let total = 0n
|
|
34
|
+
let count = 0
|
|
35
|
+
while (true) {
|
|
36
|
+
const arg = args ? args[count % args.length] : count
|
|
37
|
+
count++
|
|
38
|
+
const start = getTime()
|
|
39
|
+
const val = fn(arg)
|
|
40
|
+
if (val instanceof Promise) await val
|
|
41
|
+
const stop = getTime()
|
|
42
|
+
const diff = stop - start
|
|
43
|
+
total += diff
|
|
44
|
+
if (min === undefined || min > diff) min = diff
|
|
45
|
+
if (max === undefined || max < diff) max = diff
|
|
46
|
+
if (total >= BigInt(timeout)) break
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const mean = total / BigInt(count)
|
|
50
|
+
const rps = 1e9 / Number(mean)
|
|
51
|
+
console.log(`${name} x ${fRps(rps)} ops/sec @ ${fTime(mean)}/op (${fTime(min)}..${fTime(max)})`)
|
|
52
|
+
|
|
53
|
+
if (globalThis.gc) for (let i = 0; i < 4; i++) globalThis.gc()
|
|
54
|
+
}
|