@exodus/test-bundler 1.0.0-rc.1
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/CHANGELOG.md +3 -0
- package/LICENSE +21 -0
- package/babel-worker.cjs +62 -0
- package/bundle.js +555 -0
- package/modules/ansi-styles.cjs +49 -0
- package/modules/assert-strict.cjs +1 -0
- package/modules/child_process.cjs +10 -0
- package/modules/cluster.cjs +27 -0
- package/modules/crypto.cjs +5 -0
- package/modules/empty/function-throw.cjs +4 -0
- package/modules/empty/module-throw.cjs +1 -0
- package/modules/fs-promises.cjs +1 -0
- package/modules/fs.cjs +123 -0
- package/modules/globals.cjs +341 -0
- package/modules/globals.node.cjs +8 -0
- package/modules/http.cjs +119 -0
- package/modules/https.cjs +11 -0
- package/modules/jest-message-util.js +5 -0
- package/modules/jest-util.js +22 -0
- package/modules/module.cjs +16 -0
- package/modules/node-buffer.cjs +3 -0
- package/modules/text-encoding-utf.cjs +90 -0
- package/modules/tty.cjs +10 -0
- package/modules/url.cjs +32 -0
- package/modules/util-format.cjs +48 -0
- package/modules/util.cjs +4 -0
- package/modules/ws.cjs +20 -0
- package/package.json +81 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const colors = [
|
|
2
|
+
'reset',
|
|
3
|
+
'bold',
|
|
4
|
+
'dim',
|
|
5
|
+
'italic',
|
|
6
|
+
'underline',
|
|
7
|
+
'overline',
|
|
8
|
+
'inverse',
|
|
9
|
+
'hidden',
|
|
10
|
+
'strikethrough',
|
|
11
|
+
'black',
|
|
12
|
+
'red',
|
|
13
|
+
'green',
|
|
14
|
+
'yellow',
|
|
15
|
+
'blue',
|
|
16
|
+
'magenta',
|
|
17
|
+
'cyan',
|
|
18
|
+
'white',
|
|
19
|
+
'blackBright',
|
|
20
|
+
'redBright',
|
|
21
|
+
'greenBright',
|
|
22
|
+
'yellowBright',
|
|
23
|
+
'blueBright',
|
|
24
|
+
'magentaBright',
|
|
25
|
+
'cyanBright',
|
|
26
|
+
'whiteBright',
|
|
27
|
+
'gray',
|
|
28
|
+
'grey',
|
|
29
|
+
'bgBlack',
|
|
30
|
+
'bgRed',
|
|
31
|
+
'bgGreen',
|
|
32
|
+
'bgYellow',
|
|
33
|
+
'bgBlue',
|
|
34
|
+
'bgMagenta',
|
|
35
|
+
'bgCyan',
|
|
36
|
+
'bgWhite',
|
|
37
|
+
'bgBlackBright',
|
|
38
|
+
'bgRedBright',
|
|
39
|
+
'bgGreenBright',
|
|
40
|
+
'bgYellowBright',
|
|
41
|
+
'bgBlueBright',
|
|
42
|
+
'bgMagentaBright',
|
|
43
|
+
'bgCyanBright',
|
|
44
|
+
'bgWhiteBright',
|
|
45
|
+
'bgGray',
|
|
46
|
+
'bgGrey',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
for (const key of colors) exports[key] = { open: '', close: '' }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('node:assert').strict
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const keys = 'ChildProcess,exec,execFile,execFileSync,execSync,fork,spawn,spawnSync'.split(',')
|
|
2
|
+
|
|
3
|
+
const makeMethod = (key) => {
|
|
4
|
+
// Not an arrow as ChildProcess is a class and can be called with new
|
|
5
|
+
return function () {
|
|
6
|
+
throw new Error(`child_process.${key} unsupported in bundled mode`)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = Object.fromEntries(keys.map((key) => [key, makeMethod(key)]))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const EventEmitter = require('events')
|
|
2
|
+
|
|
3
|
+
const makeMethod = (key) => {
|
|
4
|
+
// Not an arrow as Worker is a class and can be called with new
|
|
5
|
+
return function () {
|
|
6
|
+
throw new Error(`cluster.${key} unsupported in bundled mode`)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const cluster = new EventEmitter()
|
|
11
|
+
|
|
12
|
+
Object.assign(cluster, {
|
|
13
|
+
isWorker: false,
|
|
14
|
+
isPrimary: true,
|
|
15
|
+
workers: {},
|
|
16
|
+
settings: {},
|
|
17
|
+
SCHED_NONE: 1,
|
|
18
|
+
SCHED_RR: 2,
|
|
19
|
+
schedulingPolicy: 2,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
for (const key of ['Worker', 'setupPrimary', 'fork', 'disconnect']) cluster[key] = makeMethod(key)
|
|
23
|
+
|
|
24
|
+
cluster.isMaster = cluster.isPrimary
|
|
25
|
+
cluster.setupMaster = cluster.setupPrimary
|
|
26
|
+
|
|
27
|
+
module.exports = cluster
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
const cb = require('crypto-browserify')
|
|
2
|
+
const webcrypto = globalThis.crypto
|
|
3
|
+
const randomUUID = () => webcrypto.randomUUID()
|
|
4
|
+
const getRandomValues = (array) => webcrypto.getRandomValues(array)
|
|
5
|
+
module.exports = { ...cb, webcrypto, subtle: webcrypto?.subtle, randomUUID, getRandomValues }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
throw new Error('module unsupported in bundled mode')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('node:fs').promises
|
package/modules/fs.cjs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const constants = require('constants-browserify')
|
|
2
|
+
const { resolve } = require('path')
|
|
3
|
+
const { F_OK, R_OK, W_OK, X_OK } = constants
|
|
4
|
+
|
|
5
|
+
// promises, sync, callbacks
|
|
6
|
+
const universalKeys = [
|
|
7
|
+
'access',
|
|
8
|
+
'appendFile',
|
|
9
|
+
'chmod',
|
|
10
|
+
'chown',
|
|
11
|
+
'copyFile',
|
|
12
|
+
'cp',
|
|
13
|
+
'lchmod',
|
|
14
|
+
'lchown',
|
|
15
|
+
'link',
|
|
16
|
+
'lstat',
|
|
17
|
+
'lutimes',
|
|
18
|
+
'mkdir',
|
|
19
|
+
'mkdtemp',
|
|
20
|
+
'open',
|
|
21
|
+
'opendir',
|
|
22
|
+
'readFile',
|
|
23
|
+
'readdir',
|
|
24
|
+
'readlink',
|
|
25
|
+
'realpath',
|
|
26
|
+
'rename',
|
|
27
|
+
'rm',
|
|
28
|
+
'rmdir',
|
|
29
|
+
'stat',
|
|
30
|
+
'statfs',
|
|
31
|
+
'symlink',
|
|
32
|
+
'truncate',
|
|
33
|
+
'unlink',
|
|
34
|
+
'utimes',
|
|
35
|
+
'writeFile',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
// promises
|
|
39
|
+
const promisesKeys = [...universalKeys, 'watch']
|
|
40
|
+
|
|
41
|
+
// sync, callbacks
|
|
42
|
+
const baseKeys = [
|
|
43
|
+
...universalKeys,
|
|
44
|
+
'close',
|
|
45
|
+
'exists',
|
|
46
|
+
'fchmod',
|
|
47
|
+
'fchown',
|
|
48
|
+
'fdatasync',
|
|
49
|
+
'fstat',
|
|
50
|
+
'fsync',
|
|
51
|
+
'ftruncate',
|
|
52
|
+
'futimes',
|
|
53
|
+
'read',
|
|
54
|
+
'readv',
|
|
55
|
+
'write',
|
|
56
|
+
'writev',
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
const mainKeys = [
|
|
60
|
+
...baseKeys,
|
|
61
|
+
...baseKeys.map((name) => `${name}Sync`),
|
|
62
|
+
'createReadStream',
|
|
63
|
+
'createWriteStream',
|
|
64
|
+
'watch',
|
|
65
|
+
'watchFile',
|
|
66
|
+
'unwatchFile',
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const err = (key, file) => {
|
|
70
|
+
const info = file ? `\n (trying to access ${file})` : ''
|
|
71
|
+
throw new Error(`fs.${key} unsupported in bundled mode${info}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const stubs = Object.fromEntries(mainKeys.map((key) => [key, () => err(key)]))
|
|
75
|
+
const stubsPromises = Object.fromEntries(promisesKeys.map((key) => [key, async () => err(key)]))
|
|
76
|
+
const promises = { ...stubsPromises, constants }
|
|
77
|
+
|
|
78
|
+
const decode = (source, sourceEncoding, encoding) => {
|
|
79
|
+
if (encoding && sourceEncoding === encoding) return source
|
|
80
|
+
const data = Buffer.from(source, sourceEncoding)
|
|
81
|
+
return encoding === undefined ? data : data.toString(encoding)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const getOptions = (arg, options) => {
|
|
85
|
+
if (typeof arg !== 'string') throw new Error('first argument should be string')
|
|
86
|
+
const file = resolve(process.cwd(), arg)
|
|
87
|
+
if (typeof options === 'string') return { file, encoding: options, rest: {} }
|
|
88
|
+
if (options === undefined) return { file, rest: {} }
|
|
89
|
+
if (typeof options !== 'object') throw new Error('Unexpected options')
|
|
90
|
+
const { encoding: enc, ...rest } = options
|
|
91
|
+
if (enc !== undefined && typeof enc !== 'string') throw new Error('encoding should be a string')
|
|
92
|
+
return { file, encoding: enc, rest }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const fsFilesContents =
|
|
96
|
+
// eslint-disable-next-line no-undef
|
|
97
|
+
typeof EXODUS_TEST_FSFILES_CONTENTS === 'undefined' ? null : new Map(EXODUS_TEST_FSFILES_CONTENTS)
|
|
98
|
+
const readFileSync = (arg, options) => {
|
|
99
|
+
const { file, encoding, rest } = getOptions(arg, options)
|
|
100
|
+
if (Object.keys(rest).length > 0) throw new Error('Unsupported readFileSync options')
|
|
101
|
+
if (fsFilesContents?.has(file)) return decode(fsFilesContents.get(file), 'base64', encoding)
|
|
102
|
+
err('readFileSync', file)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// eslint-disable-next-line no-undef
|
|
106
|
+
const fsDir = typeof EXODUS_TEST_FSDIRS === 'undefined' ? null : new Map(EXODUS_TEST_FSDIRS)
|
|
107
|
+
const readdirSync = (arg, options) => {
|
|
108
|
+
const { file: dir, encoding, rest } = getOptions(arg, options)
|
|
109
|
+
if (Object.keys(rest).length > 0) throw new Error('Unsupported readdirSync options')
|
|
110
|
+
const enc = encoding === 'buffer' ? undefined : encoding || 'utf8'
|
|
111
|
+
if (fsDir?.has(dir)) return fsDir.get(dir).map((name) => decode(name, 'utf8', enc))
|
|
112
|
+
err('readdirSync', dir)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// eslint-disable-next-line no-undef
|
|
116
|
+
const fsFiles = typeof EXODUS_TEST_FSFILES === 'undefined' ? null : new Set(EXODUS_TEST_FSFILES)
|
|
117
|
+
const existsSync = (file) => {
|
|
118
|
+
if (fsFiles?.has(file) || fsFilesContents?.has(file) || fsDir?.has(file)) return true
|
|
119
|
+
err('existsSync', file)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const implemented = { existsSync, readFileSync, readdirSync }
|
|
123
|
+
module.exports = { ...stubs, ...implemented, promises, constants, F_OK, R_OK, W_OK, X_OK }
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// We expect bundler to optimize out EXODUS_TEST_PLATFORM blocks
|
|
2
|
+
/* eslint-disable sonarjs/no-collapsible-if, unicorn/no-lonely-if */
|
|
3
|
+
|
|
4
|
+
if (!globalThis.global) globalThis.global = globalThis
|
|
5
|
+
if (!globalThis.Buffer) globalThis.Buffer = require('buffer').Buffer
|
|
6
|
+
|
|
7
|
+
const consoleKeys = ['log', 'error', 'warn', 'info', 'debug', 'trace']
|
|
8
|
+
const { print } = globalThis
|
|
9
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'engine262') delete globalThis.console // prints [object Object] on everything
|
|
10
|
+
if (!globalThis.console) globalThis.console = Object.fromEntries(consoleKeys.map((k) => [k, print])) // eslint-disable-line no-undef
|
|
11
|
+
for (const k of consoleKeys) if (!console[k]) console[k] = console.log // SpiderMonkey has console but no console.error
|
|
12
|
+
|
|
13
|
+
// In browsers e.g. errors (and some other objects) are hard to unwrap via the API
|
|
14
|
+
// So we just stringify everything instead on the sender side
|
|
15
|
+
// In barebone, we don't want console.log({x:10}) to print "[Object object]"", we want "{ x: 10 }"
|
|
16
|
+
if (process.env.EXODUS_TEST_IS_BROWSER || process.env.EXODUS_TEST_IS_BAREBONE) {
|
|
17
|
+
const utilFormat = require('exodus-test:util-format')
|
|
18
|
+
if (print) globalThis.print = (...args) => print(utilFormat(...args))
|
|
19
|
+
for (const type of consoleKeys) {
|
|
20
|
+
if (!Object.hasOwn(console, type)) continue
|
|
21
|
+
const orig = console[type].bind(console)
|
|
22
|
+
console[type] = (...args) => orig(utilFormat(...args))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!console.time || !console.timeEnd) {
|
|
27
|
+
const start = new Map()
|
|
28
|
+
const now = globalThis.performance?.now ? performance.now.bind(performance) : Date.now.bind(Date) // d8 and jsc have performance.now()
|
|
29
|
+
const warn = (text) => console.error(`Warning: ${text}`)
|
|
30
|
+
console.time = (key = 'default') => {
|
|
31
|
+
if (start.has(key)) return warn(`Label '${key}' already exists for console.time()`) // Does not reset
|
|
32
|
+
start.set(key, now()) // Start late
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.timeEnd = (key = 'default') => {
|
|
36
|
+
const ms = now() // End early
|
|
37
|
+
if (!start.has(key)) return warn(`No such label '${key}' for console.timeEnd()`)
|
|
38
|
+
console.log(`${key}: ${ms - start.get(key)}ms`)
|
|
39
|
+
start.delete(key)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!globalThis.fetch) {
|
|
44
|
+
globalThis.fetch = () => {
|
|
45
|
+
throw new Error('Fetch not supported')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!globalThis.WebSocket) {
|
|
50
|
+
globalThis.WebSocket = () => {
|
|
51
|
+
throw new Error('WebSocket not supported')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!Array.prototype.at) {
|
|
56
|
+
const at = function (i) {
|
|
57
|
+
return this[i < 0 ? this.length + i : i]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line no-extend-native
|
|
61
|
+
Object.defineProperty(Array.prototype, 'at', { configurable: true, writable: true, value: at })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'hermes') {
|
|
65
|
+
// Fixed after 0.12, not present in 0.12
|
|
66
|
+
// Refs: https://github.com/facebook/hermes/commit/e8fa81328dd630e39975e6d16ac3e6f47f4cba06
|
|
67
|
+
if (!Promise.allSettled) {
|
|
68
|
+
const wrap = (element) =>
|
|
69
|
+
Promise.resolve(element).then(
|
|
70
|
+
(value) => ({ status: 'fulfilled', value }),
|
|
71
|
+
(reason) => ({ status: 'rejected', reason })
|
|
72
|
+
)
|
|
73
|
+
Promise.allSettled = (iterable) => Promise.all([...iterable].map((element) => wrap(element)))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Refs: https://github.com/facebook/hermes/commit/e97db61b49bd0c065a3ce7da46f074bc39b80c6a
|
|
77
|
+
if (!Promise.any) {
|
|
78
|
+
const AggregateError =
|
|
79
|
+
globalThis.AggregateError ||
|
|
80
|
+
class AggregateError extends Error {
|
|
81
|
+
constructor(errors, message) {
|
|
82
|
+
super(message)
|
|
83
|
+
this.name = 'AggregateError'
|
|
84
|
+
this.errors = errors
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const errmsg = 'All promises were rejected'
|
|
89
|
+
Promise.any = function (values) {
|
|
90
|
+
const promises = [...values]
|
|
91
|
+
const errors = []
|
|
92
|
+
if (promises.length === 0) return Promise.reject(new AggregateError(errors, errmsg))
|
|
93
|
+
let resolved = false
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const oneResolve = (value) => {
|
|
96
|
+
if (resolved) return
|
|
97
|
+
resolved = true
|
|
98
|
+
errors.length = 0
|
|
99
|
+
resolve(value)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const oneReject = (error) => {
|
|
103
|
+
if (resolved) return
|
|
104
|
+
errors.push(error)
|
|
105
|
+
if (errors.length === promises.length) reject(new AggregateError(errors, errmsg))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
promises.forEach((promise) => Promise.resolve(promise).then(oneResolve, oneReject))
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'quickjs' && globalThis.os) {
|
|
115
|
+
const { setTimeout, setInterval, clearTimeout, clearInterval } = globalThis.os
|
|
116
|
+
Object.assign(globalThis, { setTimeout, setInterval, clearTimeout, clearInterval })
|
|
117
|
+
for (const key of ['os', 'std', 'bjson']) delete globalThis[key]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (globalThis.describe) delete globalThis.describe
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
process.env.EXODUS_TEST_PLATFORM === 'hermes' ||
|
|
124
|
+
(process.env.EXODUS_TEST_IS_BAREBONE && !globalThis.clearTimeout)
|
|
125
|
+
) {
|
|
126
|
+
// Ok, we have broken timers, let's hack them around
|
|
127
|
+
const { setTimeout: setTimeoutOriginal, clearTimeout: clearTimeoutOriginal } = globalThis
|
|
128
|
+
const tickTimes = async (n) => {
|
|
129
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'escargot') {
|
|
130
|
+
// escargot is _special_ (slow on await, unless we drain manually)
|
|
131
|
+
let promise = Promise.resolve()
|
|
132
|
+
for (let i = 0; i < n; i++) promise = promise.then(() => {})
|
|
133
|
+
globalThis.drainJobQueue()
|
|
134
|
+
await promise
|
|
135
|
+
} else {
|
|
136
|
+
const promise = Promise.resolve() // tickTimes(0) is equivalent to one Promise.resolve() as it's async
|
|
137
|
+
for (let i = 0; i < n; i++) await promise
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// TODO: use interrupt timers on jsc
|
|
142
|
+
|
|
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
|
|
145
|
+
const dateNow = Date.now.bind(Date)
|
|
146
|
+
const precision = clearTimeoutOriginal ? Infinity : 10 // have to tick this fast for clearTimeout to work
|
|
147
|
+
let current = 0
|
|
148
|
+
let loopTimeout
|
|
149
|
+
let publicId = 0
|
|
150
|
+
const timerMap = new Map()
|
|
151
|
+
let queue = []
|
|
152
|
+
const stopLoop = () => {
|
|
153
|
+
clearTimeoutOriginal?.(loopTimeout)
|
|
154
|
+
current++
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const restartLoop = () => {
|
|
158
|
+
if (loopTimeout !== undefined) clearTimeoutOriginal?.(loopTimeout) // hermes clearTimeout doesn't follow spec on undefined
|
|
159
|
+
const at = queue[0].runAt
|
|
160
|
+
const id = ++current
|
|
161
|
+
const tick = () => {
|
|
162
|
+
if (id !== current) return
|
|
163
|
+
const remaining = at - dateNow()
|
|
164
|
+
if (remaining <= 0) return queueTick()
|
|
165
|
+
loopTimeout = schedule(tick, Math.min(precision, remaining))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
loopTimeout = schedule(tick, Math.min(precision, at - dateNow()))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const queueSchedule = (entry) => {
|
|
172
|
+
if (!entry.publicId) entry.publicId = ++publicId // eslint-disable-line @exodus/mutable/no-param-reassign-prop-only
|
|
173
|
+
timerMap.set(entry.publicId, entry)
|
|
174
|
+
|
|
175
|
+
const before = queue.findIndex((x) => x.runAt > entry.runAt)
|
|
176
|
+
if (before === -1) {
|
|
177
|
+
queue.push(entry)
|
|
178
|
+
} else {
|
|
179
|
+
queue.splice(before, 0, entry)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (entry === queue[0]) restartLoop()
|
|
183
|
+
return entry.publicId
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const queueMicrotick = () => {
|
|
187
|
+
if (queue.length === 0 || !(queue[0].runAt <= dateNow())) return null
|
|
188
|
+
const next = queue.shift()
|
|
189
|
+
if (next.interval === undefined) {
|
|
190
|
+
timerMap.delete(next.publicId)
|
|
191
|
+
} else {
|
|
192
|
+
next.runAt += next.interval
|
|
193
|
+
queueSchedule(next)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
next.callback(...next.args)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const queueTick = () => {
|
|
200
|
+
current++ // safeguard
|
|
201
|
+
while (queueMicrotick() !== null);
|
|
202
|
+
if (queue.length > 0) restartLoop()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
globalThis.setTimeout = (callback, delay = 0, ...args) =>
|
|
206
|
+
queueSchedule({ callback, runAt: delay + dateNow(), args })
|
|
207
|
+
|
|
208
|
+
globalThis.setInterval = (callback, delay = 0, ...args) =>
|
|
209
|
+
queueSchedule({ callback, runAt: delay + dateNow(), interval: delay, args })
|
|
210
|
+
|
|
211
|
+
globalThis.clearTimeout = globalThis.clearInterval = (id) => {
|
|
212
|
+
const entry = timerMap.get(id)
|
|
213
|
+
if (!entry) return
|
|
214
|
+
timerMap.delete(id)
|
|
215
|
+
queue = queue.filter((x) => x !== entry)
|
|
216
|
+
if (queue.length === 0) stopLoop()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { setTimeout } = globalThis // we need non-overriden by fake timers one
|
|
221
|
+
|
|
222
|
+
const isBarebone = process.env.EXODUS_TEST_IS_BAREBONE
|
|
223
|
+
if (typeof process === 'undefined') {
|
|
224
|
+
// Fixes process.exitCode handling
|
|
225
|
+
|
|
226
|
+
const process = {
|
|
227
|
+
__proto__: null,
|
|
228
|
+
_exitCode: 0,
|
|
229
|
+
set exitCode(value) {
|
|
230
|
+
process._exitCode = value
|
|
231
|
+
if (globalThis.process) globalThis.process.exitCode = value
|
|
232
|
+
if (globalThis.Deno) globalThis.Deno.exitCode = value
|
|
233
|
+
},
|
|
234
|
+
get exitCode() {
|
|
235
|
+
return process._exitCode
|
|
236
|
+
},
|
|
237
|
+
exit: (code = 0) => {
|
|
238
|
+
globalThis.Deno?.exit?.(code)
|
|
239
|
+
globalThis.process?.exit?.(code)
|
|
240
|
+
process.exitCode = code
|
|
241
|
+
process._maybeProcessExitCode()
|
|
242
|
+
},
|
|
243
|
+
_exitHook: null,
|
|
244
|
+
_maybeProcessExitCode: () => {
|
|
245
|
+
if (globalThis.Deno) return // has native exitCode support
|
|
246
|
+
if (process._exitHook) return process._exitHook(process._exitCode)
|
|
247
|
+
if (process._exitCode !== 0) {
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
if (isBarebone) print('EXODUS_TEST_FAILED_EXIT_CODE_1')
|
|
250
|
+
const err = new Error('Test failed')
|
|
251
|
+
err.stack = ''
|
|
252
|
+
throw err
|
|
253
|
+
}, 0)
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
cwd: () => {
|
|
257
|
+
// eslint-disable-next-line no-undef
|
|
258
|
+
if (typeof EXODUS_TEST_PROCESS_CWD === 'string') return EXODUS_TEST_PROCESS_CWD
|
|
259
|
+
throw new Error('Can not determine cwd, no process available')
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
globalThis.EXODUS_TEST_PROCESS = process
|
|
264
|
+
} else {
|
|
265
|
+
Object.assign(process, { argv: process.argv }) // apply values from defined bundled vars, if present
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'hermes' || process.env.EXODUS_TEST_IS_BROWSER) {
|
|
269
|
+
const print = console.log.bind(console) // we don not want overrides
|
|
270
|
+
let logHeader = () => {
|
|
271
|
+
globalThis.EXODUS_TEST_PROCESS.exitCode = 1
|
|
272
|
+
print(`‼ FATAL Tests generated asynchronous activity after they ended.
|
|
273
|
+
This activity created errors and would have caused tests to fail, but instead triggered unhandledRejection events`)
|
|
274
|
+
logHeader = () => {}
|
|
275
|
+
setTimeout(() => globalThis.EXODUS_TEST_PROCESS._maybeProcessExitCode(), 0)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (process.env.EXODUS_TEST_PLATFORM === 'hermes') {
|
|
279
|
+
const onUnhandled = (i, err) => {
|
|
280
|
+
logHeader()
|
|
281
|
+
print(`Uncaught error #${i}: ${err}`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
globalThis.HermesInternal?.enablePromiseRejectionTracker({ allRejections: true, onUnhandled })
|
|
285
|
+
} else if (process.env.EXODUS_TEST_IS_BROWSER) {
|
|
286
|
+
// Won't catch all errors, as we might still be running, but better than nothing
|
|
287
|
+
// We also don't print anything except the header, as browsers already print that
|
|
288
|
+
// Cancelling the default behavior is less robust as we want to treat this as error
|
|
289
|
+
globalThis.addEventListener('unhandledrejection', () => logHeader())
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!globalThis.crypto?.getRandomValues && globalThis.EXODUS_TEST_CRYPTO_ENTROPY) {
|
|
294
|
+
const entropy = Buffer.from(globalThis.EXODUS_TEST_CRYPTO_ENTROPY, 'base64')
|
|
295
|
+
let pos = 0
|
|
296
|
+
if (!globalThis.crypto) globalThis.crypto = {}
|
|
297
|
+
const TypedArray = Object.getPrototypeOf(Uint8Array)
|
|
298
|
+
globalThis.crypto.getRandomValues = (typedArray) => {
|
|
299
|
+
if (!(typedArray instanceof TypedArray)) throw new Error('Argument should be a TypedArray')
|
|
300
|
+
const view = Buffer.from(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength)
|
|
301
|
+
if (pos + view.length <= entropy.length) {
|
|
302
|
+
pos += view.length
|
|
303
|
+
const copied = entropy.copy(view, 0, pos - view.length)
|
|
304
|
+
if (copied !== view.length) throw new Error('Unexpected')
|
|
305
|
+
return typedArray
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
throw new Error(`Not enough csprng entropy in this test bundle (ref: @exodus/test)`)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
delete globalThis.EXODUS_TEST_CRYPTO_ENTROPY
|
|
313
|
+
|
|
314
|
+
if (globalThis.crypto?.getRandomValues && !globalThis.crypto?.randomUUID) {
|
|
315
|
+
const getRandomValues = globalThis.crypto.getRandomValues.bind(globalThis.crypto)
|
|
316
|
+
let entropy
|
|
317
|
+
|
|
318
|
+
const hex = (start, end) => entropy.slice(start, end).toString('hex')
|
|
319
|
+
|
|
320
|
+
globalThis.crypto.randomUUID = () => {
|
|
321
|
+
if (!entropy) entropy = Buffer.alloc(16)
|
|
322
|
+
|
|
323
|
+
getRandomValues(entropy)
|
|
324
|
+
entropy[6] = (entropy[6] & 0x0f) | 0x40 // version 4: 0100xxxx
|
|
325
|
+
entropy[8] = (entropy[8] & 0x3f) | 0x80 // variant 1: 10xxxxxx
|
|
326
|
+
|
|
327
|
+
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
328
|
+
return `${hex(0, 4)}-${hex(4, 6)}-${hex(6, 8)}-${hex(8, 10)}-${hex(10, 16)}`
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!globalThis.crypto.subtle) globalThis.crypto.subtle = {} // For getRandomValues detection
|
|
333
|
+
|
|
334
|
+
if (process.env.EXODUS_TEST_IS_BAREBONE) {
|
|
335
|
+
if (!globalThis.URLSearchParams) globalThis.URLSearchParams = require('@ungap/url-search-params')
|
|
336
|
+
if (!globalThis.TextEncoder || !globalThis.TextDecoder) {
|
|
337
|
+
const { TextEncoder, TextDecoder } = require('exodus-test:text-encoding-utf')
|
|
338
|
+
if (!globalThis.TextEncoder) globalThis.TextEncoder = TextEncoder
|
|
339
|
+
if (!globalThis.TextDecoder) global.TextDecoder = TextDecoder
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// A slimmed-down version of globals.cjs specifically for Node.js bundle
|
|
2
|
+
Object.assign(process, { argv: process.argv })
|
|
3
|
+
|
|
4
|
+
if (!globalThis.crypto) {
|
|
5
|
+
// Old Node.js, we polyfill it as our bundler polyfills crypto module using webcrypto RNG
|
|
6
|
+
const r = require // prevent embed
|
|
7
|
+
globalThis.crypto = r('node:crypto').webcrypto
|
|
8
|
+
}
|