@exodus/test 1.0.0-rc.100 → 1.0.0-rc.101

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 CHANGED
@@ -15,7 +15,7 @@ It can run your existing tests on [all runtimes and also browsers](#engines), wi
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
17
  [GraalJS](https://github.com/oracle/graaljs), [Escargot](https://github.com/Samsung/escargot),
18
- and even [engine262](https://github.com/engine262/engine262).
18
+ [Boa](https://github.com/boa-dev/boa), and even [engine262](https://github.com/engine262/engine262).
19
19
  - Testsuite-agnostic — can run any file as long as it sets exit code based on test results
20
20
  - Built-in [Jest](https://jestjs.io) compatibility (with `--jest`), including `jest.*` global
21
21
  - Up to ~10x faster depending on the original setup
@@ -45,8 +45,10 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
45
45
  - `node:pure` — implementation in pure JS, runs on Node.js
46
46
  - `node:bundle` — same as `node:pure`, but bundles everything into a single file before launching
47
47
  - Other runtimes:
48
- - `bun:pure` / `bun:bundle` Bun, expects `bun` to be available
48
+ - `deno:pure` Deno (requires Deno v2.4.0 or later, expects `deno` to be available)
49
49
  - `deno:bundle` — Deno (v1 or v2, whichever `deno` is)
50
+ - `deno:test` — incomplete, lacks `--jest` support due to missing `afterEach` / `beforeEach` in Deno
51
+ - `bun:pure` / `bun:bundle` — Bun, expects `bun` to be available
50
52
  - `electron-as-node:test` / `electron-as-node:pure` / `electron-as-node:bundle`\
51
53
  Same as `node:*`, but uses `electron` binary.\
52
54
  The usecase is mostly to test on BoringSSL instead of OpenSSL.
@@ -73,6 +75,7 @@ Use `--engine` (or `EXODUS_TEST_ENGINE=`) to specify one of:
73
75
  - `xs:bundle` — [XS](https://github.com/Moddable-OpenSource/moddable-xst)
74
76
  - `graaljs:bundle` — [GraalJS](https://github.com/oracle/graaljs)
75
77
  - `escargot:bundle` — [Escargot](https://github.com/Samsung/escargot)
78
+ - `boa:bundle` — [Boa](https://github.com/boa-dev/boa)
76
79
  - `engine262:bundle` - [engine262](https://github.com/engine262/engine262), the per-spec implementation of ECMA-262
77
80
  (install with [esvu](https://npmjs.com/package/esvu))
78
81
 
@@ -57,6 +57,7 @@ function findBinaryOnce(name) {
57
57
  case 'quickjs':
58
58
  case 'graaljs':
59
59
  case 'escargot':
60
+ case 'boa': // not present in jsvu, esvu lacks aarch64-apple-darwin platform (as it's only in nightly)
60
61
  case 'ladybird-js': // naming by esvu
61
62
  case 'engine262':
62
63
  return findFile([jsvu, esvu])
package/bin/index.js CHANGED
@@ -19,29 +19,34 @@ import { glob as globImplementation } from '../src/glob.cjs'
19
19
  const DEFAULT_PATTERNS = [`**/?(*.)+(spec|test).?([cm])[jt]s?(x)`] // do not trust magic dirs by default
20
20
  const bundleOpts = { pure: true, bundle: true, esbuild: true, ts: 'auto' }
21
21
  const bareboneOpts = { ...bundleOpts, barebone: true }
22
- const hermesAv = ['-Og', '-Xmicrotask-queue']
22
+ const hermesA = ['-Og', '-Xmicrotask-queue']
23
+ const denoA = ['run', '--allow-all'] // also will set DENO_COMPAT=1 env flag below
23
24
  const ENGINES = new Map(
24
25
  Object.entries({
25
- 'node:test': { binary: 'node', pure: false, hasImportLoader: true, ts: 'flag', haveIsOk: true },
26
- 'node:pure': { binary: 'node', pure: true, hasImportLoader: true, ts: 'flag', haveIsOk: true },
26
+ 'node:test': { binary: 'node', pure: false, loader: '--import', ts: 'flag', haveIsOk: true },
27
+ 'node:pure': { binary: 'node', pure: true, loader: '--import', ts: 'flag', haveIsOk: true },
27
28
  'node:bundle': { binary: 'node', ...bundleOpts },
28
- 'bun:pure': { binary: 'bun', pure: true, hasImportLoader: false, ts: 'auto' },
29
+ 'bun:test': { binary: 'bun', ts: 'auto' },
30
+ 'bun:pure': { binary: 'bun', pure: true, ts: 'auto' },
29
31
  'bun:bundle': { binary: 'bun', ...bundleOpts },
30
- 'electron-as-node:test': { binary: 'electron', pure: false, hasImportLoader: true, ts: 'flag' },
31
- 'electron-as-node:pure': { binary: 'electron', pure: true, hasImportLoader: true, ts: 'flag' },
32
+ 'electron-as-node:test': { binary: 'electron', pure: false, loader: '--import', ts: 'flag' },
33
+ 'electron-as-node:pure': { binary: 'electron', pure: true, loader: '--import', ts: 'flag' },
32
34
  'electron-as-node:bundle': { binary: 'electron', ...bundleOpts },
33
35
  'electron:bundle': { binary: 'electron', electron: true, ...bundleOpts },
36
+ 'deno:test': { binary: 'deno', pure: false, loader: '--preload', ts: 'auto' },
37
+ 'deno:pure': { binary: 'deno', binaryArgs: denoA, pure: true, loader: '--preload', ts: 'auto' },
34
38
  'deno:bundle': { binary: 'deno', binaryArgs: ['run'], target: 'deno1', ...bundleOpts },
35
39
  // Barebone engines
36
40
  'd8:bundle': { binary: 'd8', ...bareboneOpts },
37
41
  'jsc:bundle': { binary: 'jsc', target: 'safari13', ...bareboneOpts },
38
- 'hermes:bundle': { binary: 'hermes', binaryArgs: hermesAv, target: 'es2018', ...bareboneOpts },
42
+ 'hermes:bundle': { binary: 'hermes', binaryArgs: hermesA, target: 'es2018', ...bareboneOpts },
39
43
  'spidermonkey:bundle': { binary: 'spidermonkey', ...bareboneOpts },
40
44
  'engine262:bundle': { binary: 'engine262', ...bareboneOpts },
41
45
  'quickjs:bundle': { binary: 'quickjs', binaryArgs: ['--std'], ...bareboneOpts },
42
46
  'xs:bundle': { binary: 'xs', ...bareboneOpts },
43
47
  'graaljs:bundle': { binary: 'graaljs', ...bareboneOpts },
44
48
  'escargot:bundle': { binary: 'escargot', ...bareboneOpts },
49
+ 'boa:bundle': { binary: 'boa', binaryArgs: ['-m'], ...bareboneOpts },
45
50
  // Browser engines
46
51
  'chrome:puppeteer': { binary: 'chrome', browsers: 'puppeteer', ...bundleOpts },
47
52
  'firefox:puppeteer': { binary: 'firefox', browsers: 'puppeteer', ...bundleOpts },
@@ -55,7 +60,7 @@ const ENGINES = new Map(
55
60
  })
56
61
  )
57
62
  const barebonesOk = ['d8', 'spidermonkey', 'quickjs', 'xs', 'hermes']
58
- const barebonesUnhandled = ['jsc', 'escargot', 'graaljs', 'engine262']
63
+ const barebonesUnhandled = ['jsc', 'escargot', 'boa', 'graaljs', 'engine262']
59
64
 
60
65
  const getEnvFlag = (name) => {
61
66
  if (!Object.hasOwn(process.env, name)) return
@@ -274,6 +279,7 @@ setEnv('EXODUS_TEST_DEVTOOLS', options.devtools ? '1' : '')
274
279
  setEnv('EXODUS_TEST_IS_BROWSER', isBrowserLike ? '1' : '')
275
280
  setEnv('EXODUS_TEST_IS_BAREBONE', options.barebone ? '1' : '')
276
281
  setEnv('EXODUS_TEST_ENVIRONMENT', options.bundle ? 'bundle' : '') // perhaps switch to _IS_BUNDLED?
282
+ if (['deno:pure', 'deno:test'].includes(options.engine)) setEnv('DENO_COMPAT', '1') // https://deno.com/blog/v2.4#deno_compat1
277
283
 
278
284
  assert(!options.devtools || isBrowserLike || !options.pure, engineFlagError('devtools'))
279
285
  assert(!options.throttle || options.browsers, engineFlagError('throttle-cpu'))
@@ -296,6 +302,7 @@ if (options.pure) {
296
302
  assert(!options.forceExit, `Can not use --force-exit with ${engineName} yet`) // TODO
297
303
  assert(!options.watch, `Can not use --watch with with ${engineName}`)
298
304
  assert(options.testNamePattern.length === 0, '--test-name-pattern requires node:test engine now')
305
+ // eslint-disable-next-line unicorn/prefer-switch
299
306
  } else if (options.engine === 'node:test' || options.engine === 'electron-as-node:test') {
300
307
  const reporter = import.meta.resolve('./reporter.js')
301
308
  args.push('--test', '--no-warnings=ExperimentalWarning', '--test-reporter', reporter)
@@ -318,6 +325,12 @@ if (options.pure) {
318
325
  for (const pattern of options.testNamePattern) args.push('--test-name-pattern', pattern)
319
326
 
320
327
  args.push('--expose-internals') // this is unoptimal and hopefully temporary, see rationale in src/dark.cjs
328
+ } else if (options.engine === 'deno:test') {
329
+ args.push('test', '--allow-all')
330
+ assert(!options.jest, 'deno:test engine does not support --jest yet')
331
+ } else if (options.engine === 'bun:test') {
332
+ args.push('test')
333
+ throw new Error('bun:test is unavailable because Bun test runner has many bugs and does not work')
321
334
  } else {
322
335
  throw new Error('Unreachable')
323
336
  }
@@ -332,8 +345,12 @@ if (process.env.EXODUS_TEST_IGNORE) {
332
345
 
333
346
  // This might be used in presets, so has to be loaded before jest
334
347
  if (options.flow && !options.bundle) args.push('--import', import.meta.resolve('../loader/flow.js'))
335
- if (!options.bundle && !['node:test', 'electron-as-node:test'].includes(options.engine)) {
336
- args.push('--import', import.meta.resolve('../loader/node-test.js'))
348
+ if (['node:test', 'electron-as-node:test', 'deno:test'].includes(options.engine)) {
349
+ // Do not need node:test override
350
+ } else if (options.engine === 'deno:pure') {
351
+ args.push('--import-map', import.meta.resolve('../loader/deno-import-map.json'))
352
+ } else if (!options.bundle) {
353
+ args.push(options.loader ?? '-r', import.meta.resolve('../loader/node-test.js'))
337
354
  }
338
355
 
339
356
  // The comment below is disabled, we don't auto-mock @jest/globals anymore, and having our loader first is faster
@@ -347,7 +364,7 @@ if (options.jest) {
347
364
  if (options.bundle) {
348
365
  setEnv('EXODUS_TEST_JEST_CONFIG', JSON.stringify(jestConfig))
349
366
  } else {
350
- args.push(options.hasImportLoader ? '--import' : '-r', import.meta.resolve('../loader/jest.js'))
367
+ args.push(options.loader ?? '-r', import.meta.resolve('../loader/jest.js'))
351
368
  }
352
369
 
353
370
  if (config.testFailureExitCode !== undefined) {
@@ -418,7 +435,7 @@ if (options.concurrency) {
418
435
 
419
436
  if (options.esbuild && !options.bundle) {
420
437
  setEnv('EXODUS_TEST_ESBUILD', options.esbuild)
421
- if (options.hasImportLoader) {
438
+ if (options.loader === '--import') {
422
439
  const optional = options.esbuild === '*' ? '' : '.optional'
423
440
  args.push('--import', import.meta.resolve(`../loader/esbuild${optional}.js`))
424
441
  } else if (options.flagEngine === false) {
@@ -440,7 +457,7 @@ if (options.typescript) {
440
457
  assert(!options.babel, 'Options --typescript and --babel are mutually exclusive')
441
458
 
442
459
  if (options.ts === 'flag') {
443
- assert(options.hasImportLoader)
460
+ assert(options.loader === '--import')
444
461
  // TODO: switch to native --experimental-strip-types where available
445
462
  args.push('--import', import.meta.resolve('../loader/typescript.js'))
446
463
  } else if (options.ts !== 'auto') {
@@ -660,7 +677,7 @@ if (options.pure) {
660
677
  const missUnhandled = barebonesUnhandled.includes(options.platform) || isBrowserLike
661
678
  if (missUnhandled) warnHuman(`Warning: ${engineName} does not have unhandled rejections tracking`)
662
679
 
663
- const runOne = async (inputFile, attempt = 0) => {
680
+ const runOne = async (inputFile) => {
664
681
  const bundled = buildFile ? await buildFile(inputFile) : undefined
665
682
  if (buildFile) assert(bundled.file)
666
683
  const file = buildFile ? bundled.file : inputFile
@@ -681,12 +698,6 @@ if (options.pure) {
681
698
  const ok = code === 0 && !/^(✖ FAIL|‼ FATAL) /mu.test(stdout)
682
699
  return { ok, output: [stdout, stderr], ms }
683
700
  } catch (err) {
684
- const retryOnXS = new Set(['SIGSEGV', 'SIGBUS'])
685
- if (options.engine === 'xs:bundle' && retryOnXS.has(err.signal) && attempt < 4) {
686
- // xs sometimes randomly crashes with SIGSEGV on CI. Allow 5 attempts (allow #0 - #3 to fail)
687
- return runOne(inputFile, attempt + 1)
688
- }
689
-
690
701
  const ms = Number(process.hrtime.bigint() - start) / 1e6
691
702
  const { code, stderr = '', signal, killed } = err
692
703
  const stdout = cleanOut(err.stdout || '')
@@ -738,8 +749,8 @@ if (options.pure) {
738
749
  console.timeEnd(timeLabel)
739
750
  } else {
740
751
  assert(!buildFile)
741
- assertBinary(options.binary, ['node', 'electron'])
742
- assert(['node:test', 'electron-as-node:test'].includes(options.engine))
752
+ assertBinary(options.binary, ['node', 'electron', 'deno', 'bun'])
753
+ assert(['node:test', 'electron-as-node:test', 'deno:test', 'bun:test'].includes(options.engine))
743
754
  setEnv('EXODUS_TEST_CONTEXT', 'node:test') // The context is always node:test in this branch
744
755
  assert(files.length > 0) // otherwise we can run recursively
745
756
  assert(!options.binaryArgs)
@@ -0,0 +1,9 @@
1
+ {
2
+ "imports": {
3
+ "node:test": "../src/node.js",
4
+ "@jest/globals": "../src/jest.js",
5
+ "tape": "../src/tape.js",
6
+ "tape-promise/tape": "../src/tape.js",
7
+ "micro-should": "../src/jest.js"
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/test",
3
- "version": "1.0.0-rc.100",
3
+ "version": "1.0.0-rc.101",
4
4
  "author": "Exodus Movement, Inc.",
5
5
  "description": "A test suite runner",
6
6
  "homepage": "https://github.com/ExodusMovement/test",
@@ -46,6 +46,7 @@
46
46
  "bin/inband.js",
47
47
  "bin/reporter.js",
48
48
  "loader/babel.cjs",
49
+ "loader/deno-import-map.json",
49
50
  "loader/esbuild.js",
50
51
  "loader/esbuild.optional.js",
51
52
  "loader/flow.js",
@@ -96,9 +97,12 @@
96
97
  "test:simple": "node ./bin/index.js 'tests/*.test.js'",
97
98
  "test:pure": "EXODUS_TEST_ENGINE=node:pure npm run test --",
98
99
  "test:bundle": "EXODUS_TEST_ENGINE=node:bundle npm run test:_bundle --",
100
+ "test:bun:test": "EXODUS_TEST_ENGINE=bun:test npm run test --",
99
101
  "test:bun:pure": "EXODUS_TEST_ENGINE=bun:pure npm run test --",
100
102
  "test:bun:bundle": "EXODUS_TEST_ENGINE=bun:bundle npm run test:_bundle",
101
- "test:deno": "EXODUS_TEST_ENGINE=deno:bundle npm run test:_bundle --",
103
+ "test:deno:test": "EXODUS_TEST_ENGINE=deno:test node ./bin/index.js tests/tape.test.js tests/simple.test.js tests/env.test.js 'tests/engines/**.test.js' tests/node/simple.test.js tests/node/order.test.js",
104
+ "test:deno:pure": "EXODUS_TEST_IGNORE='**/jest-repo/examples/timer/**' EXODUS_TEST_ENGINE=deno:pure npm run test --",
105
+ "test:deno:bundle": "EXODUS_TEST_ENGINE=deno:bundle npm run test:_bundle --",
102
106
  "test:electron:node": "EXODUS_TEST_ENGINE=electron-as-node:test npm run test",
103
107
  "test:electron:node:pure": "EXODUS_TEST_ENGINE=electron-as-node:pure npm run test --",
104
108
  "test:electron:node:bundle": "EXODUS_TEST_ENGINE=electron-as-node:bundle npm run test:_bundle",
@@ -119,10 +123,12 @@
119
123
  "test:xs": "EXODUS_TEST_ENGINE=xs:bundle npm run test:_bundle --",
120
124
  "test:graaljs": "EXODUS_TEST_ENGINE=graaljs:bundle npm run test:_bundle --",
121
125
  "test:escargot": "EXODUS_TEST_ENGINE=escargot:bundle npm run test:_bundle --",
126
+ "test:boa": "EXODUS_TEST_ENGINE=boa:bundle npm run test:_bundle --",
122
127
  "test:fetch": "node ./bin/index.js --jest --drop-network --engine node:pure 'tests/replay/*.test.js'",
123
128
  "test:jsdom": "EXODUS_TEST_JEST_CONFIG='{\"testMatch\":[\"**/*.jsdom-test.js\"],\"testEnvironment\":\"jsdom\", \"rootDir\": \".\"}' ./bin/index.js --jest",
124
129
  "coverage": "node ./bin/index.js --jest --esbuild --coverage",
125
130
  "playwright": "node ./bin/index.js --playwright",
131
+ "esvu": "esvu",
126
132
  "jsvu": "jsvu",
127
133
  "jest": "NODE_OPTIONS=--experimental-vm-modules jest tests/jest/ tests/jest-when/",
128
134
  "lint": "prettier --list-different . && eslint .",
@@ -131,7 +137,7 @@
131
137
  "optionalDependencies": {
132
138
  "@chalker/queue": "^1.0.1",
133
139
  "@exodus/replay": "^1.0.0-rc.9",
134
- "@exodus/test-bundler": "1.0.0-rc.5",
140
+ "@exodus/test-bundler": "1.0.0-rc.6",
135
141
  "c8": "^9.1.0",
136
142
  "expect": "^29.7.0",
137
143
  "fast-glob": "^3.2.11",
@@ -146,9 +152,11 @@
146
152
  "@exodus/prettier": "^1.0.0",
147
153
  "@jest/globals": "^29.7.0",
148
154
  "@types/jest-when": "^3.5.2",
155
+ "@types/node": "^24.0.11",
149
156
  "@typescript-eslint/eslint-plugin": "^7.15.0",
150
157
  "electron": "^35.2.2",
151
158
  "eslint": "^8.44.0",
159
+ "esvu": "^1.2.16",
152
160
  "jest": "^29.7.0",
153
161
  "jest-matcher-utils": "^29.7.0",
154
162
  "jest-serializer-ansi-escapes": "^3.0.0",
@@ -29,7 +29,7 @@ const setSnapshotResolver = (fn) => {
29
29
  snapshot?.setResolveSnapshotPath(resolveSnapshot)
30
30
  }
31
31
 
32
- const mockModule = mock.module
32
+ const mockModule = mock?.module
33
33
  ? (t, o) => mock.module(t.includes('\\') ? pathToFileURL(t) : t, o) // resolve windows-looking paths
34
34
  : undefined
35
35
 
@@ -37,9 +37,9 @@ function parseArgs(args) {
37
37
  }
38
38
 
39
39
  class Context {
40
- test = test // todo: bind to context
41
- describe = describe // todo: bind to context
42
- plan = plan
40
+ test = (...args) => test(...args) // TODO: bind to context
41
+ describe = (...args) => describe(...args) // TODO: bind to context
42
+ plan = (count) => plan(count) // TODO: bind to context
43
43
  children = []
44
44
  #fullName
45
45
  #assert
package/src/jest.mock.js CHANGED
@@ -384,7 +384,12 @@ function jestmock(name, mocker, { override = false, actual, builtin, loc } = {})
384
384
  overridenBuiltins.add(resolved)
385
385
  overrideModule(resolved, true) // Override builtin modules
386
386
  if (syncBuiltinESMExports) {
387
- syncBuiltinESMExports()
387
+ try {
388
+ syncBuiltinESMExports()
389
+ } catch (err) {
390
+ if (!globalThis.Deno) throw err // Deno throws on syncBuiltinESMExports, ignore for now
391
+ }
392
+
388
393
  isOverridenBuiltinSynchedWithESM = true
389
394
  }
390
395
  }
package/src/tape.js CHANGED
@@ -1,4 +1,4 @@
1
- import { assert, assertLoose, test as nodeTest } from './engine.js'
1
+ import { assert, assertLoose, test as nodeTest, after } from './engine.js'
2
2
  import { createCallerLocationHook } from './dark.cjs'
3
3
  import './version.js'
4
4
 
@@ -154,6 +154,7 @@ function tapeWrap(test) {
154
154
 
155
155
  if (test.skip) tap.skip = tapeWrap(test.skip)
156
156
  if (test.only) tap.only = tapeWrap(test.only)
157
+ tap.onFinish = (fn) => after(fn)
157
158
  return tap
158
159
  }
159
160