@exodus/test 1.0.0-rc.112 → 1.0.0-rc.114
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 +2 -2
- package/bin/find-binary.js +2 -0
- package/bin/index.js +13 -10
- package/package.json +4 -4
- package/src/engine.pure.cjs +6 -11
- package/src/engine.pure.snapshot.cjs +11 -6
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
|
|
|
28
28
|
[](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html)
|
|
29
29
|
[](https://spidermonkey.dev/)
|
|
30
30
|
[](https://github.com/quickjs-ng/quickjs)
|
|
31
|
-
[](https://github.com/Moddable-OpenSource/moddable
|
|
31
|
+
[](https://github.com/Moddable-OpenSource/moddable)
|
|
32
32
|
[](https://github.com/oracle/graaljs)
|
|
33
33
|
[](https://github.com/boa-dev/boa)
|
|
34
34
|
[](https://github.com/Samsung/escargot)
|
|
@@ -174,7 +174,7 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
|
|
|
174
174
|
- `hermes:bundle` — [Hermes](https://hermesengine.dev) (React Native JavaScript engine)
|
|
175
175
|
- `spidermonkey:bundle` — [SpiderMonkey](https://spidermonkey.dev/) (Firefox/Gecko JavaScript engine)
|
|
176
176
|
- `quickjs:bundle` — [QuickJS](https://github.com/quickjs-ng/quickjs)
|
|
177
|
-
- `xs:bundle` — [XS](https://github.com/Moddable-OpenSource/moddable
|
|
177
|
+
- `xs:bundle` — [Moddable XS](https://github.com/Moddable-OpenSource/moddable)
|
|
178
178
|
- `graaljs:bundle` — [GraalJS](https://github.com/oracle/graaljs)
|
|
179
179
|
- `escargot:bundle` — [Escargot](https://github.com/Samsung/escargot)
|
|
180
180
|
- `boa:bundle` — [Boa](https://github.com/boa-dev/boa)
|
package/bin/find-binary.js
CHANGED
|
@@ -69,6 +69,8 @@ function findBinaryOnce(name) {
|
|
|
69
69
|
return require('electron')
|
|
70
70
|
case 'workerd':
|
|
71
71
|
return require.resolve('workerd/bin/workerd')
|
|
72
|
+
case 'porffor':
|
|
73
|
+
return require.resolve('porffor/porf')
|
|
72
74
|
case 'jerryscript':
|
|
73
75
|
name = 'jerry' // look under this name, including in global
|
|
74
76
|
return findFile([jsvu, esvu])
|
package/bin/index.js
CHANGED
|
@@ -55,6 +55,7 @@ const ENGINES = new Map(
|
|
|
55
55
|
'boa:bundle': { binary: 'boa', binaryArgs: ['-m'], ...bareboneOpts },
|
|
56
56
|
'nova:bundle': { binary: 'nova', binaryArgs: ['eval'], ...bareboneOpts },
|
|
57
57
|
'jerryscript:bundle': { binary: 'jerryscript', ...bareboneOpts },
|
|
58
|
+
'porffor:bundle': { binary: 'porffor', ...bareboneOpts }, // blocked on https://github.com/CanadaHonk/porffor/issues/176
|
|
58
59
|
// Special case: running a browser from CLI like a bundle
|
|
59
60
|
'servo:bundle': { binary: 'servo', binaryArgs: ['--headless'], ...bundleOpts, html: true },
|
|
60
61
|
'workerd:bundle': { binary: 'workerd', binaryArgs: ['test'], ...bundleOpts, workerd: true },
|
|
@@ -71,8 +72,8 @@ const ENGINES = new Map(
|
|
|
71
72
|
})
|
|
72
73
|
)
|
|
73
74
|
const bareOk = ['v8', 'd8', 'spidermonkey', 'quickjs', 'xs', 'hermes', 'shermes']
|
|
74
|
-
const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', '
|
|
75
|
-
const bareIncomplete = ['ladybird-js', 'nova', '
|
|
75
|
+
const bareNotrack = ['jsc', 'escargot', 'boa', 'graaljs', 'jerryscript', 'engine262', 'servo']
|
|
76
|
+
const bareIncomplete = ['ladybird-js', 'nova', 'duktape']
|
|
76
77
|
|
|
77
78
|
const getEnvFlag = (name) => {
|
|
78
79
|
if (!Object.hasOwn(process.env, name)) return
|
|
@@ -662,7 +663,7 @@ async function launch(binary, args, opts = {}, buffering = false) {
|
|
|
662
663
|
}
|
|
663
664
|
|
|
664
665
|
const barebones = [...bareOk, ...bareNotrack, ...bareIncomplete]
|
|
665
|
-
assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', ...barebones])
|
|
666
|
+
assertBinary(binary, ['node', 'bun', 'deno', 'electron', 'workerd', 'jerry', 'duk', ...barebones])
|
|
666
667
|
if (binary === c8 && process.platform === 'win32') {
|
|
667
668
|
;[binary, args] = ['node', [binary, ...args]]
|
|
668
669
|
}
|
|
@@ -693,7 +694,7 @@ if (options.pure) {
|
|
|
693
694
|
|
|
694
695
|
setEnv('EXODUS_TEST_CONTEXT', 'pure')
|
|
695
696
|
const missUnhandled = bareNotrack.includes(options.platform) || isBrowserLike
|
|
696
|
-
const isIncomplete = bareIncomplete.includes(options.platform)
|
|
697
|
+
const isIncomplete = bareIncomplete.includes(options.platform) || options.platform === 'workerd'
|
|
697
698
|
if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
|
|
698
699
|
if (isIncomplete) warnHuman(`Warning: ${engineName} support is incomplete`)
|
|
699
700
|
|
|
@@ -711,18 +712,20 @@ if (options.pure) {
|
|
|
711
712
|
bundled.fileWrapper = `${bundled.file}.wrapper.js`
|
|
712
713
|
bundled.fileConfig = `${bundled.file}.capnp`
|
|
713
714
|
assert(/^[a-z0-9/_.-]+\.js$/iu.test(bundled.file), bundled.file)
|
|
715
|
+
const { compatibilityDate } = await import('workerd')
|
|
714
716
|
const jsRelativePath = basename(bundled.file)
|
|
715
717
|
const wrapperRelativePath = basename(bundled.fileWrapper)
|
|
716
718
|
const wrapperContent = `
|
|
717
719
|
export default {
|
|
718
720
|
async test(ctrl, env, ctx) {
|
|
719
721
|
await import('./' + ${JSON.stringify(jsRelativePath)})
|
|
720
|
-
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
exitCode
|
|
722
|
+
await globalThis.EXODUS_TEST_LOAD()
|
|
723
|
+
if (globalThis.EXODUS_TEST_PROMISE) {
|
|
724
|
+
const exitCode = await globalThis.EXODUS_TEST_PROMISE
|
|
725
|
+
if (exitCode !== 0) throw new Error(\`Tests failed with exit code \${exitCode}\`)
|
|
726
|
+
} else {
|
|
727
|
+
console.log('WARNING: node:test not loaded, asynchronous tests might be missed')
|
|
724
728
|
}
|
|
725
|
-
if (exitCode !== 0) throw new Error(\`Tests failed with exit code \${exitCode}\`)
|
|
726
729
|
}
|
|
727
730
|
}`
|
|
728
731
|
await writeFile(bundled.fileWrapper, wrapperContent)
|
|
@@ -734,7 +737,7 @@ const mainWorker :Workerd.Worker = (
|
|
|
734
737
|
(name = ${JSON.stringify(wrapperRelativePath)}, esModule = embed ${JSON.stringify(wrapperRelativePath)}),
|
|
735
738
|
(name = ${JSON.stringify(jsRelativePath)}, esModule = embed ${JSON.stringify(jsRelativePath)}),
|
|
736
739
|
],
|
|
737
|
-
compatibilityDate =
|
|
740
|
+
compatibilityDate = ${JSON.stringify(compatibilityDate)},
|
|
738
741
|
);`
|
|
739
742
|
await writeFile(bundled.fileConfig, configContent)
|
|
740
743
|
}
|
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.114",
|
|
4
4
|
"author": "Exodus Movement, Inc.",
|
|
5
5
|
"description": "A test suite runner",
|
|
6
6
|
"homepage": "https://github.com/ExodusOSS/test",
|
|
@@ -181,8 +181,8 @@
|
|
|
181
181
|
},
|
|
182
182
|
"optionalDependencies": {
|
|
183
183
|
"@chalker/queue": "^1.0.1",
|
|
184
|
-
"@exodus/replay": "^1.0.0-rc.
|
|
185
|
-
"@exodus/test-bundler": "1.0.0-rc.
|
|
184
|
+
"@exodus/replay": "^1.0.0-rc.11",
|
|
185
|
+
"@exodus/test-bundler": "1.0.0-rc.15",
|
|
186
186
|
"c8": "^9.1.0",
|
|
187
187
|
"expect": "^30.2.0",
|
|
188
188
|
"fast-glob": "^3.2.11",
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
"jsvu": "^3.0.3",
|
|
211
211
|
"prettier": "^3.0.3",
|
|
212
212
|
"typedoc": "^0.28.16",
|
|
213
|
-
"workerd": "^1.
|
|
213
|
+
"workerd": "^1.20260207.0"
|
|
214
214
|
},
|
|
215
215
|
"peerDependencies": {
|
|
216
216
|
"@babel/register": "^7.0.0",
|
package/src/engine.pure.cjs
CHANGED
|
@@ -15,7 +15,7 @@ let willstart
|
|
|
15
15
|
|
|
16
16
|
const abstractProcess = globalThis.process || globalThis.EXODUS_TEST_PROCESS
|
|
17
17
|
|
|
18
|
-
if (process.env.EXODUS_TEST_IS_BROWSER) {
|
|
18
|
+
if (process.env.EXODUS_TEST_IS_BROWSER || process.env.EXODUS_TEST_PLATFORM === 'workerd') {
|
|
19
19
|
globalThis.EXODUS_TEST_PROMISE = new Promise((resolve) => (abstractProcess._exitHook = resolve))
|
|
20
20
|
if (!abstractProcess._maybeProcessExitCode && abstractProcess === globalThis.process) {
|
|
21
21
|
// Electron with Node.js integration has real process
|
|
@@ -30,7 +30,7 @@ const check = (condition, message) => {
|
|
|
30
30
|
|
|
31
31
|
function parseArgs(args) {
|
|
32
32
|
check(args.length <= 3)
|
|
33
|
-
const name = typeof args[0] === 'string' ? args.shift() :
|
|
33
|
+
const name = typeof args[0] === 'string' ? args.shift() : undefined
|
|
34
34
|
const fn = args.pop()
|
|
35
35
|
const options = args.pop() || {}
|
|
36
36
|
return { name, options, fn }
|
|
@@ -46,6 +46,7 @@ class Context {
|
|
|
46
46
|
#hooks
|
|
47
47
|
|
|
48
48
|
constructor(parent, name, options = {}) {
|
|
49
|
+
if (!name || typeof name !== 'string') name = '<anonymous>'
|
|
49
50
|
Object.assign(this, { root: parent?.root, parent, name, options })
|
|
50
51
|
this.#fullName = parent && parent !== parent.root ? `${parent.fullName} > ${name}` : name
|
|
51
52
|
if (this.#fullName === name) this.#fullName = this.#fullName.replace(INBAND_PREFIX_REGEX, '')
|
|
@@ -104,7 +105,7 @@ function enterContext(name, options) {
|
|
|
104
105
|
function exitContext() {
|
|
105
106
|
check(context !== context.root)
|
|
106
107
|
context = context.parent
|
|
107
|
-
if (context === context.root
|
|
108
|
+
if (context === context.root) willstart = setTimeout(run, 0)
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
async function runFunction(fn, context) {
|
|
@@ -185,8 +186,6 @@ async function runContext(context) {
|
|
|
185
186
|
async function run() {
|
|
186
187
|
check(!running)
|
|
187
188
|
running = true
|
|
188
|
-
const manual = globalThis.EXODUS_TEST_RUN === run
|
|
189
|
-
const res = manual ? new Promise((resolve) => (abstractProcess._exitHook = resolve)) : undefined
|
|
190
189
|
check(context === context.root)
|
|
191
190
|
await runContext(context).catch((error) => {
|
|
192
191
|
// Should not throw under regular circumstances
|
|
@@ -195,12 +194,8 @@ async function run() {
|
|
|
195
194
|
})
|
|
196
195
|
// Let unhandled errors be processed (and set the error code)
|
|
197
196
|
setTimeout(() => abstractProcess._maybeProcessExitCode?.(), 0)
|
|
198
|
-
return res
|
|
199
197
|
}
|
|
200
198
|
|
|
201
|
-
// For workerd, expose run as a global so the wrapper can call it
|
|
202
|
-
if (process.env.EXODUS_TEST_PLATFORM === 'workerd') globalThis.EXODUS_TEST_RUN = run
|
|
203
|
-
|
|
204
199
|
async function describe(...args) {
|
|
205
200
|
const { name, options, fn } = parseArgs(args)
|
|
206
201
|
enterContext(name, options)
|
|
@@ -491,7 +486,7 @@ function getMacrotick() {
|
|
|
491
486
|
const { scheduler, MessageChannel } = globalThis
|
|
492
487
|
if (scheduler?.yield) return () => scheduler.yield()
|
|
493
488
|
if (setImmediate) return () => new Promise((resolve) => setImmediate(resolve))
|
|
494
|
-
if (MessageChannel) {
|
|
489
|
+
if (MessageChannel && !globalThis.Cloudflare) {
|
|
495
490
|
return async () => {
|
|
496
491
|
const { port1, port2 } = new MessageChannel()
|
|
497
492
|
await new Promise((resolve) => {
|
|
@@ -569,7 +564,7 @@ if (process.env.EXODUS_TEST_ENVIRONMENT === 'bundle') {
|
|
|
569
564
|
|
|
570
565
|
// eslint-disable-next-line no-undef
|
|
571
566
|
let snapshotResolver = (dir, name) => [dir, `${name}.snapshot`] // default per Node.js docs
|
|
572
|
-
let snapshotSerializers = [(obj) => JSON.stringify(obj, null, 2)]
|
|
567
|
+
let snapshotSerializers = [(obj) => (obj === undefined ? `${obj}` : JSON.stringify(obj, null, 2))]
|
|
573
568
|
const serializeSnapshot = (obj) => {
|
|
574
569
|
let val = obj
|
|
575
570
|
for (const fn of snapshotSerializers) val = fn(val)
|
|
@@ -2,6 +2,7 @@ const nameCounts = new Map()
|
|
|
2
2
|
let snapshotText, snapshotTextClean
|
|
3
3
|
|
|
4
4
|
const escapeSnapshot = (str) => str.replaceAll(/([\\`]|\$\{)/gu, '\\$1')
|
|
5
|
+
const escapeSnapshotKey = (s) => escapeSnapshot(s).replaceAll('\n', '\\n').replaceAll('"', '\\"')
|
|
5
6
|
|
|
6
7
|
function matchSnapshot(readSnapshot, assert, name, serialized) {
|
|
7
8
|
// We don't have native snapshots, polyfill reading
|
|
@@ -25,12 +26,16 @@ function matchSnapshot(readSnapshot, assert, name, serialized) {
|
|
|
25
26
|
nameCounts.set(name, count)
|
|
26
27
|
const escaped = escapeSnapshot(serialized)
|
|
27
28
|
const key = `${name} ${count}`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Node.js and jest escape keys differently, both result to same strings, accept both
|
|
30
|
+
for (const keyfun of [escapeSnapshot, escapeSnapshotKey]) {
|
|
31
|
+
const makeEntry = (x) => `\nexports[\`${keyfun(key)}\`] = \`${x}\`;\n`
|
|
32
|
+
const fixedText = escaped.includes('\r') ? snapshotText : snapshotTextClean // well, if we expect \r let's preserve them
|
|
33
|
+
const final = escaped.includes('\n') ? `\n${escaped}\n` : escaped
|
|
34
|
+
if (fixedText.includes(makeEntry(final))) return
|
|
35
|
+
// Perhaps wrapped with newlines from Node.js snapshots?
|
|
36
|
+
if (!final.includes('\n') && fixedText.includes(makeEntry(`\n${final}\n`))) return
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
return assert.fail(`Could not match "${key}" in snapshot. ${addFail}`)
|
|
35
40
|
}
|
|
36
41
|
|