@exodus/test 1.0.0-rc.90 → 1.0.0-rc.92
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 +6 -4
- package/bin/browsers.js +1 -1
- package/bin/find-binary.js +7 -3
- package/bin/index.js +22 -5
- package/bundler/bundle.js +11 -0
- package/bundler/modules/globals.cjs +4 -2
- package/bundler/modules/url.cjs +3 -3
- package/package.json +3 -2
- package/src/engine.pure.cjs +16 -7
- package/src/exodus.js +24 -1
- package/src/jest.js +15 -2
- package/src/jest.timers.js +6 -4
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
|
|
|
14
14
|
[v8 CLI](https://v8.dev/docs/d8), JSC, [Hermes](https://hermesengine.dev), [SpiderMonkey](https://spidermonkey.dev/),
|
|
15
15
|
Chrome, Firefox, WebKit, Brave, Microsoft Edge,
|
|
16
16
|
[QuickJS](https://github.com/quickjs-ng/quickjs), [XS](https://github.com/Moddable-OpenSource/moddable-xst),
|
|
17
|
-
[GraalJS](https://github.com/oracle/graaljs)
|
|
17
|
+
[GraalJS](https://github.com/oracle/graaljs), [Escargot](https://github.com/Samsung/escargot),
|
|
18
|
+
and even [engine262](https://github.com/engine262/engine262).
|
|
18
19
|
- Testsuite-agnostic — can run any file as long as it sets exit code based on test results
|
|
19
20
|
- Built-in [Jest](https://jestjs.io) compatibility (with `--jest`), including `jest.*` global
|
|
20
21
|
- Up to ~10x faster depending on the original setup
|
|
@@ -24,7 +25,6 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
|
|
|
24
25
|
- [test.concurrent](https://jestjs.io/docs/api#testconcurrentname-fn-timeout)
|
|
25
26
|
- Module mocks, including for ESM modules (already loaded ESM modules can be mocked only on `node:test`)
|
|
26
27
|
- Loads Jest configuration
|
|
27
|
-
- It works on Hermes too!
|
|
28
28
|
- Built-in network record/replay for offline tests, mocking `fetch` and `WebSocket` sessions
|
|
29
29
|
- `--drop-network` support for guaranteed offline testing
|
|
30
30
|
- Native code coverage via v8 (Node.js or [c8](https://github.com/bcoe/c8)), with istanbul reporters
|
|
@@ -64,15 +64,17 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
|
|
|
64
64
|
- `firefox:puppeteer` — Firefox
|
|
65
65
|
- `brave:puppeteer` — Brave
|
|
66
66
|
- `msedge:puppeteer` — Microsoft Edge
|
|
67
|
-
- Barebone engines (system-provided or installed with `npx jsvu`):
|
|
67
|
+
- Barebone engines (system-provided or installed with `npx jsvu` / `npx esvu`):
|
|
68
68
|
- `d8:bundle` — [v8 CLI](https://v8.dev/docs/d8) (Chrome/Blink/Node.js JavaScript engine)
|
|
69
69
|
- `jsc:bundle` — [JavaScriptCore](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html) (Safari/WebKit JavaScript engine)
|
|
70
70
|
- `hermes:bundle` — [Hermes](https://hermesengine.dev) (React Native JavaScript engine)
|
|
71
71
|
- `spidermonkey:bundle` — [SpiderMonkey](https://spidermonkey.dev/) (Firefox/Gecko JavaScript engine)
|
|
72
|
-
- `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs)
|
|
72
|
+
- `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs)
|
|
73
73
|
- `xs:bundle` — [XS](https://github.com/Moddable-OpenSource/moddable-xst)
|
|
74
74
|
- `graaljs:bundle` — [GraalJS](https://github.com/oracle/graaljs)
|
|
75
75
|
- `escargot:bundle` — [Escargot](https://github.com/Samsung/escargot)
|
|
76
|
+
- `engine262:bundle` - [engine262](https://github.com/engine262/engine262), the per-spec implementation of ECMA-262
|
|
77
|
+
(install with [esvu](https://npmjs.com/package/esvu))
|
|
76
78
|
|
|
77
79
|
## Reporter samples
|
|
78
80
|
|
package/bin/browsers.js
CHANGED
|
@@ -72,7 +72,7 @@ export async function run(runner, args, { binary, devtools, dropNetwork, timeout
|
|
|
72
72
|
const [stdout, stderr] = [[], []]
|
|
73
73
|
|
|
74
74
|
assert(Object.hasOwn(launchers, runner), 'Unexpected runner')
|
|
75
|
-
if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools })
|
|
75
|
+
if (!launched[runner]) launched[runner] = launchers[runner]({ binary, devtools: !!devtools })
|
|
76
76
|
const { page, context } = await newPage(runner, await launched[runner], { binary, dropNetwork })
|
|
77
77
|
|
|
78
78
|
if (throttle) {
|
package/bin/find-binary.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createRequire } from 'node:module'
|
|
|
6
6
|
const require = createRequire(import.meta.url)
|
|
7
7
|
const nvm = process.env.NVM_BIN ? (x) => join(process.env.NVM_BIN, '../lib/node_modules', x) : null
|
|
8
8
|
const jsvu = (x) => join(homedir(), '.jsvu/bin', x)
|
|
9
|
+
const esvu = (x) => join(homedir(), '.esvu/bin', x)
|
|
9
10
|
|
|
10
11
|
// Can modify PATH to add the binary to it!
|
|
11
12
|
function findBinaryOnce(name) {
|
|
@@ -46,18 +47,21 @@ function findBinaryOnce(name) {
|
|
|
46
47
|
case 'jsc':
|
|
47
48
|
return findFile([
|
|
48
49
|
(bin) => jsvu(bin), // prefer jsvu
|
|
50
|
+
(bin) => esvu(bin),
|
|
49
51
|
(bin) => `/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/${bin}`,
|
|
50
52
|
(bin) => `/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/${bin}`,
|
|
51
53
|
])
|
|
52
54
|
case 'd8':
|
|
53
|
-
return findFile([() => jsvu('v8')]) // jsvu
|
|
55
|
+
return findFile([() => jsvu('v8'), () => esvu('v8')]) // jsvu/esvu name it v8
|
|
54
56
|
case 'spidermonkey':
|
|
55
57
|
case 'quickjs':
|
|
56
58
|
case 'graaljs':
|
|
57
59
|
case 'escargot':
|
|
58
|
-
|
|
60
|
+
case 'ladybird-js': // naming by esvu
|
|
61
|
+
case 'engine262':
|
|
62
|
+
return findFile([jsvu, esvu])
|
|
59
63
|
case 'xs':
|
|
60
|
-
return findFile([jsvu], false)
|
|
64
|
+
return findFile([jsvu, esvu], false)
|
|
61
65
|
case 'electron':
|
|
62
66
|
return require('electron')
|
|
63
67
|
case 'c8':
|
package/bin/index.js
CHANGED
|
@@ -40,6 +40,7 @@ const ENGINES = new Map(
|
|
|
40
40
|
'jsc:bundle': { binary: 'jsc', target: 'safari13', ...bareboneOpts },
|
|
41
41
|
'hermes:bundle': { binary: 'hermes', binaryArgs: hermesAv, target: 'es2018', ...bareboneOpts },
|
|
42
42
|
'spidermonkey:bundle': { binary: 'spidermonkey', ...bareboneOpts },
|
|
43
|
+
'engine262:bundle': { binary: 'engine262', ...bareboneOpts },
|
|
43
44
|
'quickjs:bundle': { binary: 'quickjs', binaryArgs: ['--std'], ...bareboneOpts },
|
|
44
45
|
'xs:bundle': { binary: 'xs', ...bareboneOpts },
|
|
45
46
|
'graaljs:bundle': { binary: 'graaljs', ...bareboneOpts },
|
|
@@ -56,6 +57,8 @@ const ENGINES = new Map(
|
|
|
56
57
|
'msedge:playwright': { binary: 'msedge', browsers: 'playwright', ...bundleOpts },
|
|
57
58
|
})
|
|
58
59
|
)
|
|
60
|
+
const barebonesOk = ['d8', 'spidermonkey', 'quickjs', 'xs', 'hermes']
|
|
61
|
+
const barebonesUnhandled = ['jsc', 'escargot', 'graaljs', 'engine262']
|
|
59
62
|
|
|
60
63
|
const getEnvFlag = (name) => {
|
|
61
64
|
if (!Object.hasOwn(process.env, name)) return false
|
|
@@ -185,7 +188,11 @@ function parseOptions() {
|
|
|
185
188
|
options.flagEngine = true
|
|
186
189
|
break
|
|
187
190
|
case '--devtools':
|
|
188
|
-
|
|
191
|
+
case '--inspect-brk':
|
|
192
|
+
options.devtools = '--inspect-brk'
|
|
193
|
+
break
|
|
194
|
+
case '--inspect':
|
|
195
|
+
if (!options.devtools) options.devtools = '--inspect'
|
|
189
196
|
break
|
|
190
197
|
case '--debug-files':
|
|
191
198
|
options.debug.files = true
|
|
@@ -256,6 +263,7 @@ if (!process.env.FORCE_COLOR && process.stdout.hasColors?.() && process.stderr.h
|
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
const engineName = `${options.engine} engine` // used for warnings to user
|
|
266
|
+
const engineFlagError = (flag) => `${engineName} does not support --${flag}`
|
|
259
267
|
const engineOptions = ENGINES.get(options.engine)
|
|
260
268
|
assert(engineOptions, `Unknown engine: ${options.engine}`)
|
|
261
269
|
Object.assign(options, engineOptions)
|
|
@@ -269,8 +277,8 @@ setEnv('EXODUS_TEST_IS_BROWSER', isBrowserLike ? '1' : '')
|
|
|
269
277
|
setEnv('EXODUS_TEST_IS_BAREBONE', options.barebone ? '1' : '')
|
|
270
278
|
setEnv('EXODUS_TEST_ENVIRONMENT', options.bundle ? 'bundle' : '') // perhaps switch to _IS_BUNDLED?
|
|
271
279
|
|
|
272
|
-
assert(!options.devtools || isBrowserLike, '
|
|
273
|
-
assert(!options.throttle || options.browsers,
|
|
280
|
+
assert(!options.devtools || isBrowserLike || !options.pure, engineFlagError('devtools'))
|
|
281
|
+
assert(!options.throttle || options.browsers, engineFlagError('throttle-cpu'))
|
|
274
282
|
|
|
275
283
|
const require = createRequire(import.meta.url)
|
|
276
284
|
const resolveRequire = (query) => require.resolve(query)
|
|
@@ -577,7 +585,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
|
|
|
577
585
|
return browsers.run(runner, args, { binary, devtools, dropNetwork, timeout, throttle })
|
|
578
586
|
}
|
|
579
587
|
|
|
580
|
-
const barebones = [
|
|
588
|
+
const barebones = [...barebonesOk, ...barebonesUnhandled]
|
|
581
589
|
assertBinary(binary, ['node', 'bun', 'deno', 'electron', ...barebones, 'v8']) // v8 is an alias to d8
|
|
582
590
|
if (options.dropNetwork) {
|
|
583
591
|
switch (process.platform) {
|
|
@@ -601,7 +609,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
|
|
|
601
609
|
if (options.pure) {
|
|
602
610
|
setEnv('EXODUS_TEST_CONTEXT', 'pure')
|
|
603
611
|
warnHuman(`${engineName} is experimental and may not work an expected`)
|
|
604
|
-
const missUnhandled =
|
|
612
|
+
const missUnhandled = barebonesUnhandled.includes(options.platform) || isBrowserLike
|
|
605
613
|
if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
|
|
606
614
|
|
|
607
615
|
const runOne = async (inputFile, attempt = 0) => {
|
|
@@ -690,6 +698,15 @@ if (options.pure) {
|
|
|
690
698
|
assert(files.length > 0) // otherwise we can run recursively
|
|
691
699
|
assert(!options.binaryArgs)
|
|
692
700
|
if (options.concurrency) args.push('--test-concurrency', options.concurrency)
|
|
701
|
+
if (['--inspect', '--inspect-brk'].includes(options.devtools)) {
|
|
702
|
+
args.push(options.devtools)
|
|
703
|
+
console.warn(
|
|
704
|
+
options.devtools === '--inspect-brk'
|
|
705
|
+
? 'Open chrome://inspect/ to connect devtools, waiting'
|
|
706
|
+
: 'Open chrome://inspect/ to connect devtools\nUse --inspect-brk to wait for inspector'
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
|
|
693
710
|
const { code } = await launch(options.binary, [...args, ...files])
|
|
694
711
|
process.exitCode = code
|
|
695
712
|
}
|
package/bundler/bundle.js
CHANGED
|
@@ -38,6 +38,17 @@ const loadPipeline = [
|
|
|
38
38
|
.replace(/\b(__dirname|import\.meta\.dirname)\b/g, JSON.stringify(dirname(filepath)))
|
|
39
39
|
.replace(/\b(__filename|import\.meta\.filename)\b/g, JSON.stringify(filepath))
|
|
40
40
|
|
|
41
|
+
if (filepath.endsWith('/node_modules/chalk/source/templates.js')) {
|
|
42
|
+
// It has an invalid regex on which engine262 fails
|
|
43
|
+
res = res.replace(
|
|
44
|
+
'const ESCAPE_REGEX = /\\\\(u(?:[a-f\\d]{4}|{[a-f\\d]{1,6}})|x[a-f\\d]{2}|.)|([^\\\\])/gi;',
|
|
45
|
+
'const ESCAPE_REGEX = /\\\\(u(?:[a-f\\d]{4}|\\{[a-f\\d]{1,6}\\})|x[a-f\\d]{2}|.)|([^\\\\])/giu;'
|
|
46
|
+
)
|
|
47
|
+
} else if (filepath.endsWith('/node_modules/qs/lib/parse.js')) {
|
|
48
|
+
res = res.replace('var brackets = /(\\[[^[\\]]*])/;', 'var brackets = /(\\[[^[\\]]*\\])/;')
|
|
49
|
+
res = res.replace('var child = /(\\[[^[\\]]*])/g;', 'var child = /(\\[[^[\\]]*\\])/g;')
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
// Unneded polyfills
|
|
42
53
|
for (const [a, b] of Object.entries({
|
|
43
54
|
'is-nan': 'Number.isNaN', // https://www.npmjs.com/package/is-nan description: ES2015-compliant shim for Number.isNaN
|
|
@@ -6,6 +6,7 @@ if (!globalThis.Buffer) globalThis.Buffer = require('buffer').Buffer
|
|
|
6
6
|
|
|
7
7
|
const consoleKeys = ['log', 'error', 'warn', 'info', 'debug', 'trace']
|
|
8
8
|
const { print } = globalThis
|
|
9
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'engine262') delete globalThis.console // prints [object Object] on everything
|
|
9
10
|
if (!globalThis.console) globalThis.console = Object.fromEntries(consoleKeys.map((k) => [k, print])) // eslint-disable-line no-undef
|
|
10
11
|
for (const k of consoleKeys) if (!console[k]) console[k] = console.log // SpiderMonkey has console but no console.error
|
|
11
12
|
|
|
@@ -139,7 +140,8 @@ if (
|
|
|
139
140
|
|
|
140
141
|
// TODO: use interrupt timers on jsc
|
|
141
142
|
|
|
142
|
-
const
|
|
143
|
+
const tickPromiseInterval = process.env.EXODUS_TEST_PLATFORM === 'engine262' ? 5 : 50 // engine262 is slow
|
|
144
|
+
const schedule = setTimeoutOriginal || ((x) => tickTimes(tickPromiseInterval).then(() => x())) // e.g. SpiderMonkey doesn't even have setTimeout
|
|
143
145
|
const dateNow = Date.now.bind(Date)
|
|
144
146
|
const precision = clearTimeoutOriginal ? Infinity : 10 // have to tick this fast for clearTimeout to work
|
|
145
147
|
let current = 0
|
|
@@ -197,7 +199,7 @@ if (
|
|
|
197
199
|
const queueTick = () => {
|
|
198
200
|
current++ // safeguard
|
|
199
201
|
while (queueMicrotick() !== null);
|
|
200
|
-
if (queue.length >
|
|
202
|
+
if (queue.length > 0) restartLoop()
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
globalThis.setTimeout = (callback, delay, ...args) =>
|
package/bundler/modules/url.cjs
CHANGED
|
@@ -27,6 +27,6 @@ function fileURLToPath(url, options) {
|
|
|
27
27
|
|
|
28
28
|
module.exports = { ...urlLib, pathToFileURL, fileURLToPath }
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
for (const name of ['URL', 'URLSearchParams']) {
|
|
31
|
+
Object.defineProperty(module.exports, name, { get: () => globalThis[name], enumerable: true })
|
|
32
|
+
}
|
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.92",
|
|
4
4
|
"author": "Exodus Movement, Inc.",
|
|
5
5
|
"description": "A test suite runner",
|
|
6
6
|
"homepage": "https://github.com/ExodusMovement/test",
|
|
@@ -134,6 +134,7 @@
|
|
|
134
134
|
"test:jsc": "EXODUS_TEST_ENGINE=jsc:bundle npm run test:_bundle --",
|
|
135
135
|
"test:hermes": "EXODUS_TEST_ENGINE=hermes:bundle npm run test:_bundle --",
|
|
136
136
|
"test:spidermonkey": "EXODUS_TEST_ENGINE=spidermonkey:bundle npm run test:_bundle --",
|
|
137
|
+
"test:engine262": "EXODUS_TEST_ENGINE=engine262:bundle npm run test:_bundle --",
|
|
137
138
|
"test:quickjs": "EXODUS_TEST_ENGINE=quickjs:bundle npm run test:_bundle --",
|
|
138
139
|
"test:xs": "EXODUS_TEST_ENGINE=xs:bundle npm run test:_bundle --",
|
|
139
140
|
"test:graaljs": "EXODUS_TEST_ENGINE=graaljs:bundle npm run test:_bundle --",
|
|
@@ -157,7 +158,7 @@
|
|
|
157
158
|
"@babel/plugin-transform-private-methods": "^7.0.0",
|
|
158
159
|
"@babel/register": "^7.0.0",
|
|
159
160
|
"@chalker/queue": "^1.0.1",
|
|
160
|
-
"@exodus/replay": "^1.0.0-rc.
|
|
161
|
+
"@exodus/replay": "^1.0.0-rc.9",
|
|
161
162
|
"@ungap/url-search-params": "^0.2.2",
|
|
162
163
|
"amaro": "^0.0.5",
|
|
163
164
|
"assert": "^2.1.0",
|
package/src/engine.pure.cjs
CHANGED
|
@@ -133,6 +133,12 @@ async function runContext(context) {
|
|
|
133
133
|
const guard = { id: null, failed: false }
|
|
134
134
|
const timeout = options.timeout || Number(process.env.EXODUS_TEST_TIMEOUT) || 5000
|
|
135
135
|
guard.promise = new Promise((resolve) => {
|
|
136
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'engine262') {
|
|
137
|
+
// parallel timeouts are slowing down everything on engine262
|
|
138
|
+
// so we let only the host timeout to catch us, not individual test timeout
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
guard.id = setTimeout(() => {
|
|
137
143
|
guard.failed = true
|
|
138
144
|
resolve()
|
|
@@ -304,13 +310,14 @@ class MockTimers {
|
|
|
304
310
|
}
|
|
305
311
|
|
|
306
312
|
async tickAsync(milliseconds = 1) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
this.#elapsed
|
|
311
|
-
|
|
312
|
-
if (shouldAwait) while (this.#microtick() !== null);
|
|
313
|
+
const finish = this.#elapsed + milliseconds
|
|
314
|
+
await awaitForMicrotaskQueue()
|
|
315
|
+
while (this.#queue[0] && this.#queue[0].runAt <= finish) {
|
|
316
|
+
this.#elapsed = Math.max(this.#elapsed, this.#queue[0].runAt)
|
|
317
|
+
while (this.#microtick() !== null) await awaitForMicrotaskQueue()
|
|
313
318
|
}
|
|
319
|
+
|
|
320
|
+
this.#elapsed = finish
|
|
314
321
|
}
|
|
315
322
|
|
|
316
323
|
#microtick() {
|
|
@@ -483,8 +490,10 @@ const awaitForMicrotaskQueue = async () => {
|
|
|
483
490
|
// Do not rely on setTimeout here! it will tick actual time and is terribly slow (i.e. timers no longer fake)
|
|
484
491
|
// 50_000 should be enough to flush everything that's going on in the microtask queue
|
|
485
492
|
// E.g. JSC and SpiderMonkey hit this currently
|
|
493
|
+
// engine262 is extremely slow, tick just above 100 on it
|
|
486
494
|
const promise = Promise.resolve()
|
|
487
|
-
|
|
495
|
+
const tickPromiseRounds = process.env.EXODUS_TEST_PLATFORM === 'engine262' ? 110 : 50_000
|
|
496
|
+
for (let i = 0; i < tickPromiseRounds; i++) await promise
|
|
488
497
|
}
|
|
489
498
|
|
|
490
499
|
let builtinModules = []
|
package/src/exodus.js
CHANGED
|
@@ -5,6 +5,29 @@ import { timersTrack, timersList, timersDebug, timersAssert } from './timers-tra
|
|
|
5
5
|
import { insideEsbuild } from './dark.cjs'
|
|
6
6
|
import { haveValidTimers } from './version.js'
|
|
7
7
|
|
|
8
|
+
const timersSpeedup = (rate, { apis = ['setTimeout', 'setInterval', 'Date'] } = {}) => {
|
|
9
|
+
if (!(typeof rate === 'number' && rate > 0)) throw new TypeError('Expected a positive rate')
|
|
10
|
+
const { setTimeout, setInterval, Date: OrigDate } = globalThis
|
|
11
|
+
for (const api of apis) {
|
|
12
|
+
// eslint-disable-next-line unicorn/prefer-switch
|
|
13
|
+
if (api === 'setTimeout') {
|
|
14
|
+
globalThis.setTimeout = (fn, ms, ...args) => setTimeout(fn, Math.ceil(ms / rate), ...args)
|
|
15
|
+
} else if (api === 'setInterval') {
|
|
16
|
+
globalThis.setInterval = (fn, ms, ...args) => setInterval(fn, Math.ceil(ms / rate), ...args)
|
|
17
|
+
} else if (api === 'Date') {
|
|
18
|
+
const base = OrigDate.now()
|
|
19
|
+
globalThis.Date = class Date extends OrigDate {
|
|
20
|
+
static now = () => base + Math.floor((OrigDate.now() - base) * rate)
|
|
21
|
+
constructor(first = globalThis.Date.now(), ...rest) {
|
|
22
|
+
super(first, ...rest)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error(`Unknown or unsupported API in timersSpeedup(): ${api}`)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
8
31
|
const isBundle = process.env.EXODUS_TEST_ENVIRONMENT === 'bundle' // TODO: improve mocking from bundle
|
|
9
32
|
export const exodus = {
|
|
10
33
|
__proto__: null,
|
|
@@ -21,7 +44,7 @@ export const exodus = {
|
|
|
21
44
|
concurrency: node.engine !== 'pure', // pure engine doesn't support concurrency
|
|
22
45
|
},
|
|
23
46
|
mock: {
|
|
24
|
-
...{ timersTrack, timersList, timersDebug, timersAssert }, // eslint-disable-line unicorn/no-useless-spread
|
|
47
|
+
...{ timersTrack, timersList, timersDebug, timersAssert, timersSpeedup }, // eslint-disable-line unicorn/no-useless-spread
|
|
25
48
|
...{ fetchRecord, fetchReplay }, // eslint-disable-line unicorn/no-useless-spread
|
|
26
49
|
...{ websocketRecord, websocketReplay }, // eslint-disable-line unicorn/no-useless-spread
|
|
27
50
|
},
|
package/src/jest.js
CHANGED
|
@@ -204,6 +204,19 @@ node.afterEach(() => {
|
|
|
204
204
|
|
|
205
205
|
if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
|
|
206
206
|
// TODO: deno, other engines
|
|
207
|
+
|
|
208
|
+
const reportActivity = () => {
|
|
209
|
+
if (process.env.EXODUS_TEST_TIMERS_TRACK) timersDebug()
|
|
210
|
+
if (process?.getActiveResourcesInfo) {
|
|
211
|
+
const all = process.getActiveResourcesInfo().filter((r) => r !== 'PipeWrap')
|
|
212
|
+
if (all.length > 0) {
|
|
213
|
+
const entries = [...new Set(all)].map((k) => [k, all.filter((x) => x === k).length])
|
|
214
|
+
const pretty = prettyFormat(Object.fromEntries(entries), { min: true })
|
|
215
|
+
console.log(`Active resources: { ${pretty.slice(1, -1).replaceAll('"', '')} }`)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
207
220
|
// This doesn't work with async imported tests, so for inband, we delay
|
|
208
221
|
const after = () => {
|
|
209
222
|
jestTimers.useRealTimers()
|
|
@@ -212,7 +225,7 @@ if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
|
|
|
212
225
|
// give everything additional (configurable) defaultTimeout time to finish, otherwide fail
|
|
213
226
|
const timeout = defaultTimeout
|
|
214
227
|
setTimeout(() => {
|
|
215
|
-
|
|
228
|
+
reportActivity()
|
|
216
229
|
console.error(`${prefix} additional ${timeout}ms. Terminating with a failure...`)
|
|
217
230
|
process.exit(1)
|
|
218
231
|
}, timeout).unref()
|
|
@@ -221,7 +234,7 @@ if (process.env.EXODUS_TEST_PLATFORM !== 'deno' && globalThis.process) {
|
|
|
221
234
|
const warnTimeout = 5000
|
|
222
235
|
if (warnTimeout < timeout + 1000) {
|
|
223
236
|
setTimeout(() => {
|
|
224
|
-
|
|
237
|
+
reportActivity()
|
|
225
238
|
console.warn(`${prefix} ${warnTimeout}ms. Waiting for ${timeout}ms to pass to finish...`)
|
|
226
239
|
}, warnTimeout).unref()
|
|
227
240
|
}
|
package/src/jest.timers.js
CHANGED
|
@@ -52,9 +52,11 @@ export function useFakeTimers({ doNotFake = doNotFakeDefault, ...rest } = {}) {
|
|
|
52
52
|
return this
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const runAllTimersTime = 100_000_000_000 // > 3 years
|
|
56
|
+
const runAllTimersSplit = { step: 50_000_000, steps: 2000, last: 0 }
|
|
55
57
|
export function runAllTimers() {
|
|
56
58
|
assertEnabledTimers()
|
|
57
|
-
advanceTimersByTime(
|
|
59
|
+
advanceTimersByTime(runAllTimersTime) // > 3 years
|
|
58
60
|
return this
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -95,7 +97,7 @@ export function advanceTimersByTime(time) {
|
|
|
95
97
|
return this
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
const { step, steps, last } = splitTime(time)
|
|
100
|
+
const { step, steps, last } = time === runAllTimersTime ? runAllTimersSplit : splitTime(time)
|
|
99
101
|
for (let i = 0; i < steps; i++) {
|
|
100
102
|
if (!enabled) break // got disabled while looping
|
|
101
103
|
mock.timers.tick(step)
|
|
@@ -122,7 +124,7 @@ export async function runOnlyPendingTimersAsync() {
|
|
|
122
124
|
export async function advanceTimersByTimeAsync(time) {
|
|
123
125
|
assertEnabledTimers()
|
|
124
126
|
if (mock.timers.tickAsync) {
|
|
125
|
-
await mock.timers.tickAsync(time)
|
|
127
|
+
await mock.timers.tickAsync(time) // runs microtasks at start and end
|
|
126
128
|
} else {
|
|
127
129
|
const { step, steps, last } = splitTime(time)
|
|
128
130
|
for (let i = 0; i < steps; i++) {
|
|
@@ -133,9 +135,9 @@ export async function advanceTimersByTimeAsync(time) {
|
|
|
133
135
|
|
|
134
136
|
if (last > 0 && enabled) await awaitForMicrotaskQueue()
|
|
135
137
|
if (last > 0 && enabled) mock.timers.tick(last)
|
|
138
|
+
await awaitForMicrotaskQueue() // jest doc is misleading and it also does this after running timers
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
await awaitForMicrotaskQueue() // jest doc is misleading and it also does this after running timers
|
|
139
141
|
return this
|
|
140
142
|
}
|
|
141
143
|
|