Skip to content

Commit 1caec52

Browse files
committed
fix: spawn node directly to run tests avoiding validation
1 parent 45d13e9 commit 1caec52

File tree

1 file changed

+164
-142
lines changed

1 file changed

+164
-142
lines changed

src/api/child_process.ts

Lines changed: 164 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1-
import { type ChildProcess, fork } from 'node:child_process'
1+
import { spawn } from 'node:child_process'
22
import { pathToFileURL } from 'node:url'
3-
import * as vscode from 'vscode'
4-
import { gte } from 'semver'
5-
import { findNode, formatPkg, getNodeJsVersion, showVitestError } from '../utils'
3+
import { createServer } from 'node:http'
4+
import getPort from 'get-port'
5+
import { WebSocketServer } from 'ws'
6+
import { formatPkg } from '../utils'
67
import { log } from '../log'
78
import type { ResolvedMeta } from '../api'
8-
import type { WorkerEvent, WorkerRunnerOptions } from '../worker/types'
99
import { getConfig } from '../config'
10-
import { minimumNodeVersion, workerPath } from '../constants'
10+
import { workerPath } from '../constants'
1111
import type { VitestPackage } from './pkg'
12-
import { createVitestRpc } from './rpc'
13-
import type { VitestProcess } from './types'
12+
import { waitForWsResolvedMeta } from './ws'
1413

1514
async function createChildVitestProcess(pkg: VitestPackage) {
1615
const pnpLoader = pkg.loader
1716
const pnp = pkg.pnp
1817
if (pnpLoader && !pnp)
1918
throw new Error('pnp file is required if loader option is used')
2019
const env = getConfig().env || {}
21-
const execPath = await findNode(vscode.workspace.workspaceFile?.fsPath || pkg.cwd)
22-
const execVersion = await getNodeJsVersion(execPath)
23-
if (execVersion && !gte(execVersion, minimumNodeVersion)) {
24-
const errorMsg = `Node.js version ${execVersion} is not supported. Minimum required version is ${minimumNodeVersion}`
25-
log.error('[API]', errorMsg)
26-
throw new Error(errorMsg)
27-
}
20+
// const execPath = await findNode(vscode.workspace.workspaceFile?.fsPath || pkg.cwd)
21+
// const execVersion = await getNodeJsVersion(execPath)
22+
// if (execVersion && !gte(execVersion, minimumNodeVersion)) {
23+
// const errorMsg = `Node.js version ${execVersion} is not supported. Minimum required version is ${minimumNodeVersion}`
24+
// log.error('[API]', errorMsg)
25+
// throw new Error(errorMsg)
26+
// }
2827
const runtimeArgs = getConfig(pkg.folder).nodeExecArgs || []
2928
const execArgv = pnpLoader && pnp // && !gte(execVersion, '18.19.0')
3029
? [
@@ -35,145 +34,168 @@ async function createChildVitestProcess(pkg: VitestPackage) {
3534
...runtimeArgs,
3635
]
3736
: runtimeArgs
38-
log.info('[API]', `Running ${formatPkg(pkg)} with Node.js@${execVersion}: ${execPath} ${execArgv ? execArgv.join(' ') : ''}`)
37+
const script = `node ${workerPath} ${execArgv.join(' ')}`.trim()
38+
log.info('[API]', `Running ${formatPkg(pkg)} with "${script}"`)
3939
const logLevel = getConfig(pkg.folder).logLevel
40-
const vitest = fork(
41-
workerPath,
42-
{
43-
execPath,
44-
execArgv,
45-
env: {
46-
...process.env,
47-
...env,
48-
VITEST_VSCODE_LOG: env.VITEST_VSCODE_LOG ?? process.env.VITEST_VSCODE_LOG ?? logLevel,
49-
VITEST_VSCODE: 'true',
50-
// same env var as `startVitest`
51-
// https://github.com/vitest-dev/vitest/blob/5c7e9ca05491aeda225ce4616f06eefcd068c0b4/packages/vitest/src/node/cli/cli-api.ts
52-
TEST: 'true',
53-
VITEST: 'true',
54-
NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
55-
},
56-
stdio: 'overlapped',
57-
cwd: pkg.cwd,
40+
const port = await getPort()
41+
const server = createServer().listen(port)
42+
const wss = new WebSocketServer({ server })
43+
const wsAddress = `ws://localhost:${port}`
44+
spawn('node', [workerPath, ...execArgv], {
45+
env: {
46+
...process.env,
47+
...env,
48+
VITEST_VSCODE_LOG: env.VITEST_VSCODE_LOG ?? process.env.VITEST_VSCODE_LOG ?? logLevel,
49+
VITEST_VSCODE: 'true',
50+
// same env var as `startVitest`
51+
// https://github.com/vitest-dev/vitest/blob/5c7e9ca05491aeda225ce4616f06eefcd068c0b4/packages/vitest/src/node/cli/cli-api.ts
52+
TEST: 'true',
53+
VITEST_WS_ADDRESS: wsAddress,
54+
VITEST: 'true',
55+
NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
5856
},
59-
)
60-
61-
vitest.stdout?.on('data', d => log.worker('info', d.toString()))
62-
vitest.stderr?.on('data', (chunk) => {
63-
const string = chunk.toString()
64-
log.worker('error', string)
65-
if (string.startsWith(' MISSING DEPENDENCY')) {
66-
const error = string.split(/\r?\n/, 1)[0].slice(' MISSING DEPENDENCY'.length)
67-
showVitestError(error)
68-
}
57+
// stdio: 'overlapped',
58+
cwd: pkg.cwd,
6959
})
7060

71-
return new Promise<{ process: ChildProcess; configs: string[] }>((resolve, reject) => {
72-
function onMessage(message: WorkerEvent) {
73-
if (message.type === 'debug')
74-
log.worker('info', ...message.args)
75-
76-
if (message.type === 'ready') {
77-
resolve({ process: vitest, configs: message.configs })
78-
}
79-
if (message.type === 'error') {
80-
const error = new Error(`Vitest failed to start: \n${message.error}`)
81-
reject(error)
82-
}
83-
vitest.off('error', onError)
84-
vitest.off('message', onMessage)
85-
vitest.off('exit', onExit)
86-
}
87-
88-
function onError(err: Error) {
89-
log.error('[API]', err)
90-
reject(err)
91-
vitest.off('error', onError)
92-
vitest.off('message', onMessage)
93-
vitest.off('exit', onExit)
94-
}
95-
96-
function onExit(code: number) {
97-
reject(new Error(`Vitest process exited with code ${code}`))
98-
}
99-
100-
vitest.on('error', onError)
101-
vitest.on('message', onMessage)
102-
vitest.on('exit', onExit)
103-
vitest.once('spawn', () => {
104-
const runnerOptions: WorkerRunnerOptions = {
105-
type: 'init',
106-
meta: {
107-
shellType: 'child_process',
108-
vitestNodePath: pkg.vitestNodePath,
109-
env: getConfig(pkg.folder).env || undefined,
110-
configFile: pkg.configFile,
111-
cwd: pkg.cwd,
112-
arguments: pkg.arguments,
113-
workspaceFile: pkg.workspaceFile,
114-
id: pkg.id,
115-
pnpApi: pnp,
116-
pnpLoader: pnpLoader // && gte(execVersion, '18.19.0')
117-
? pathToFileURL(pnpLoader).toString()
118-
: undefined,
119-
},
120-
debug: false,
121-
astCollect: getConfig(pkg.folder).experimentalStaticAstCollect,
122-
}
123-
124-
vitest.send(runnerOptions)
125-
})
126-
})
61+
return await waitForWsResolvedMeta(wss, pkg, true, 'child_process')
62+
// const vitest = fork(
63+
// workerPath,
64+
// {
65+
// execPath,
66+
// execArgv,
67+
// env: {
68+
// ...process.env,
69+
// ...env,
70+
// VITEST_VSCODE_LOG: env.VITEST_VSCODE_LOG ?? process.env.VITEST_VSCODE_LOG ?? logLevel,
71+
// VITEST_VSCODE: 'true',
72+
// // same env var as `startVitest`
73+
// // https://github.com/vitest-dev/vitest/blob/5c7e9ca05491aeda225ce4616f06eefcd068c0b4/packages/vitest/src/node/cli/cli-api.ts
74+
// TEST: 'true',
75+
// VITEST: 'true',
76+
// NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
77+
// },
78+
// stdio: 'overlapped',
79+
// cwd: pkg.cwd,
80+
// },
81+
// )
82+
83+
// vitest.stdout?.on('data', d => log.worker('info', d.toString()))
84+
// vitest.stderr?.on('data', (chunk) => {
85+
// const string = chunk.toString()
86+
// log.worker('error', string)
87+
// if (string.startsWith(' MISSING DEPENDENCY')) {
88+
// const error = string.split(/\r?\n/, 1)[0].slice(' MISSING DEPENDENCY'.length)
89+
// showVitestError(error)
90+
// }
91+
// })
92+
93+
// return new Promise<{ process: ChildProcess; configs: string[] }>((resolve, reject) => {
94+
// function onMessage(message: WorkerEvent) {
95+
// if (message.type === 'debug')
96+
// log.worker('info', ...message.args)
97+
98+
// if (message.type === 'ready') {
99+
// resolve({ process: vitest, configs: message.configs })
100+
// }
101+
// if (message.type === 'error') {
102+
// const error = new Error(`Vitest failed to start: \n${message.error}`)
103+
// reject(error)
104+
// }
105+
// vitest.off('error', onError)
106+
// vitest.off('message', onMessage)
107+
// vitest.off('exit', onExit)
108+
// }
109+
110+
// function onError(err: Error) {
111+
// log.error('[API]', err)
112+
// reject(err)
113+
// vitest.off('error', onError)
114+
// vitest.off('message', onMessage)
115+
// vitest.off('exit', onExit)
116+
// }
117+
118+
// function onExit(code: number) {
119+
// reject(new Error(`Vitest process exited with code ${code}`))
120+
// }
121+
122+
// vitest.on('error', onError)
123+
// vitest.on('message', onMessage)
124+
// vitest.on('exit', onExit)
125+
// vitest.once('spawn', () => {
126+
// const runnerOptions: WorkerRunnerOptions = {
127+
// type: 'init',
128+
// meta: {
129+
// shellType: 'child_process',
130+
// vitestNodePath: pkg.vitestNodePath,
131+
// env: getConfig(pkg.folder).env || undefined,
132+
// configFile: pkg.configFile,
133+
// cwd: pkg.cwd,
134+
// arguments: pkg.arguments,
135+
// workspaceFile: pkg.workspaceFile,
136+
// id: pkg.id,
137+
// pnpApi: pnp,
138+
// pnpLoader: pnpLoader // && gte(execVersion, '18.19.0')
139+
// ? pathToFileURL(pnpLoader).toString()
140+
// : undefined,
141+
// },
142+
// debug: false,
143+
// astCollect: getConfig(pkg.folder).experimentalStaticAstCollect,
144+
// }
145+
146+
// vitest.send(runnerOptions)
147+
// })
148+
// })
127149
}
128150

129151
export async function createVitestProcess(pkg: VitestPackage): Promise<ResolvedMeta> {
130-
const { process: vitest, configs } = await createChildVitestProcess(pkg)
131-
132-
log.info('[API]', `${formatPkg(pkg)} child process ${vitest.pid} created`)
133-
134-
const { handlers, api } = createVitestRpc({
135-
on: listener => vitest.on('message', listener),
136-
send: message => vitest.send(message),
137-
})
138-
139-
vitest.once('exit', () => {
140-
log.verbose?.('[API]', 'Vitest child_process connection closed, cannot call RPC anymore.')
141-
api.$close()
142-
})
143-
144-
return {
145-
rpc: api,
146-
configs,
147-
process: new VitestChildProcess(vitest),
148-
handlers,
149-
pkg,
150-
}
152+
return await createChildVitestProcess(pkg)
153+
154+
// log.info('[API]', `${formatPkg(pkg)} child process ${vitest.pid} created`)
155+
156+
// const { handlers, api } = createVitestRpc({
157+
// on: listener => vitest.on('message', listener),
158+
// send: message => vitest.send(message),
159+
// })
160+
161+
// vitest.once('exit', () => {
162+
// log.verbose?.('[API]', 'Vitest child_process connection closed, cannot call RPC anymore.')
163+
// api.$close()
164+
// })
165+
166+
// return {
167+
// rpc: api,
168+
// configs,
169+
// process: new VitestChildProcess(vitest),
170+
// handlers,
171+
// pkg,
172+
// }
151173
}
152174

153-
class VitestChildProcess implements VitestProcess {
154-
constructor(private child: ChildProcess) {}
175+
// class VitestChildProcess implements VitestProcess {
176+
// constructor(private child: ChildProcess) {}
155177

156-
get id() {
157-
return this.child.pid ?? 0
158-
}
178+
// get id() {
179+
// return this.child.pid ?? 0
180+
// }
159181

160-
get closed() {
161-
return this.child.killed
162-
}
182+
// get closed() {
183+
// return this.child.killed
184+
// }
163185

164-
on(event: string, listener: (...args: any[]) => void) {
165-
this.child.on(event, listener)
166-
}
186+
// on(event: string, listener: (...args: any[]) => void) {
187+
// this.child.on(event, listener)
188+
// }
167189

168-
once(event: string, listener: (...args: any[]) => void) {
169-
this.child.once(event, listener)
170-
}
190+
// once(event: string, listener: (...args: any[]) => void) {
191+
// this.child.once(event, listener)
192+
// }
171193

172-
off(event: string, listener: (...args: any[]) => void) {
173-
this.child.off(event, listener)
174-
}
194+
// off(event: string, listener: (...args: any[]) => void) {
195+
// this.child.off(event, listener)
196+
// }
175197

176-
close() {
177-
this.child.kill()
178-
}
179-
}
198+
// close() {
199+
// this.child.kill()
200+
// }
201+
// }

0 commit comments

Comments
 (0)