/
vm.ts
122 lines (102 loc) · 3.78 KB
/
vm.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { pathToFileURL } from 'node:url'
import { performance } from 'node:perf_hooks'
import { isContext } from 'node:vm'
import { ModuleCacheMap } from 'vite-node/client'
import { workerId as poolId } from 'tinypool'
import { createBirpc } from 'birpc'
import { resolve } from 'pathe'
import { installSourcemapsSupport } from 'vite-node/source-map'
import type { CancelReason } from '@vitest/runner'
import type { RuntimeRPC, WorkerContext, WorkerGlobalState } from '../types'
import { distDir } from '../paths'
import { loadEnvironment } from '../integrations/env'
import { startVitestExecutor } from './execute'
import { createCustomConsole } from './console'
import { createSafeRpc } from './rpc'
const entryFile = pathToFileURL(resolve(distDir, 'entry-vm.js')).href
export async function run(ctx: WorkerContext) {
const moduleCache = new ModuleCacheMap()
const mockMap = new Map()
const { config, port } = ctx
let setCancel = (_reason: CancelReason) => {}
const onCancel = new Promise<CancelReason>((resolve) => {
setCancel = resolve
})
const rpc = createBirpc<RuntimeRPC>(
{
onCancel: setCancel,
},
{
eventNames: ['onUserConsoleLog', 'onFinished', 'onCollected', 'onWorkerExit'],
post(v) { port.postMessage(v) },
on(fn) { port.addListener('message', fn) },
},
)
const environment = await loadEnvironment(ctx.environment.name, ctx.config.root)
if (!environment.setupVM) {
const envName = ctx.environment.name
const packageId = envName[0] === '.' ? envName : `vitest-environment-${envName}`
throw new TypeError(
`Environment "${ctx.environment.name}" is not a valid environment. `
+ `Path "${packageId}" doesn't support vm environment because it doesn't provide "setupVM" method.`,
)
}
const state: WorkerGlobalState = {
ctx,
moduleCache,
config,
mockMap,
onCancel,
environment,
durations: {
environment: performance.now(),
prepare: performance.now(),
},
rpc: createSafeRpc(rpc),
}
installSourcemapsSupport({
getSourceMap: source => moduleCache.getSourceMap(source),
})
const vm = await environment.setupVM(ctx.environment.options || ctx.config.environmentOptions || {})
state.durations.environment = performance.now() - state.durations.environment
process.env.VITEST_WORKER_ID = String(ctx.workerId)
process.env.VITEST_POOL_ID = String(poolId)
process.env.VITEST_VM_POOL = '1'
if (!vm.getVmContext)
throw new TypeError(`Environment ${ctx.environment.name} doesn't provide "getVmContext" method. It should return a context created by "vm.createContext" method.`)
const context = vm.getVmContext()
if (!isContext(context))
throw new TypeError(`Environment ${ctx.environment.name} doesn't provide a valid context. It should be created by "vm.createContext" method.`)
context.__vitest_worker__ = state
// this is unfortunately needed for our own dependencies
// we need to find a way to not rely on this by default
// because browser doesn't provide these globals
context.process = process
context.global = context
context.console = createCustomConsole(state)
// TODO: don't hardcode setImmediate in fake timers defaults
context.setImmediate = setImmediate
context.clearImmediate = clearImmediate
if (ctx.invalidates) {
ctx.invalidates.forEach((fsPath) => {
moduleCache.delete(fsPath)
moduleCache.delete(`mock:${fsPath}`)
})
}
ctx.files.forEach(i => moduleCache.delete(i))
const executor = await startVitestExecutor({
context,
moduleCache,
mockMap,
state,
})
context.__vitest_mocker__ = executor.mocker
const { run } = await executor.importExternalModule(entryFile)
try {
await run(ctx.files, ctx.config, executor)
}
finally {
await vm.teardown?.()
state.environmentTeardownRun = true
}
}