Skip to content

Commit

Permalink
refactor: run only a single Vitest instance per process
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Jun 5, 2024
1 parent d556120 commit 2cff93c
Show file tree
Hide file tree
Showing 16 changed files with 138 additions and 201 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,5 +231,10 @@
"*.{js,ts,tsx,vue,md}": [
"eslint --fix"
]
},
"pnpm": {
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
}
}
}
13 changes: 13 additions & 0 deletions patches/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index 8396fdbfbd7e1df8935c0806af9e7b31f8ccc261..7fcc87a89d7ca21cbf3a3e97ddedec0c51a7ef2a 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -66,7 +66,7 @@ function createBirpc(functions, options) {
error = new Error(`[birpc] function "${method}" not found`);
} else {
try {
- result = await fn.apply(rpc, args);
+ result = await fn.apply(functions, args);
} catch (e) {
error = e;
}
10 changes: 8 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion samples/basic/test/add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ describe('testing', () => {
})

it("mul fail", () => {
expect(5 * 5).toBe(26)
expect(5 * 5).toBe(25)
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Link changes the class when hovered 1`] = `
<a
Expand Down
140 changes: 67 additions & 73 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import * as vscode from 'vscode'
import { log } from './log'
import { workerPath } from './constants'
import { getConfig } from './config'
import type { BirpcEvents, VitestEvents, VitestRPC } from './api/rpc'
import type { VitestEvents, VitestRPC } from './api/rpc'
import { createVitestRpc } from './api/rpc'
import type { WorkerRunnerOptions } from './worker/types'
import type { VitestPackage } from './api/pkg'
import { findNode, pluralize, showVitestError } from './utils'
import { findNode, showVitestError } from './utils'
import type { VitestProcess } from './process'

export class VitestReporter {
Expand All @@ -36,10 +36,7 @@ export class VitestReporter {

private createHandler<K extends Exclude<keyof ResolvedMeta['handlers'], 'clearListeners' | 'removeListener'>>(name: K) {
return (callback: VitestEvents[K]) => {
this.handlers[name]((id, ...args) => {
if (id === this.id)
(callback as any)(...args)
})
this.handlers[name](callback as any)
}
}
}
Expand Down Expand Up @@ -140,15 +137,15 @@ export class VitestFolderAPI extends VitestReporter {
}

async runFiles(files?: string[], testNamePatern?: string) {
await this.meta.rpc.runTests(this.id, files?.map(normalize), testNamePatern)
await this.meta.rpc.runTests(files?.map(normalize), testNamePatern)
}

async updateSnapshots(files?: string[], testNamePatern?: string) {
await this.meta.rpc.updateSnapshots(this.id, files?.map(normalize), testNamePatern)
await this.meta.rpc.updateSnapshots(files?.map(normalize), testNamePatern)
}

getFiles() {
return this.meta.rpc.getFiles(this.id)
return this.meta.rpc.getFiles()
}

private testsQueue = new Set<string>()
Expand All @@ -167,7 +164,7 @@ export class VitestFolderAPI extends VitestReporter {
const root = this.workspaceFolder.uri.fsPath
this.testsQueue.clear()
log.info('[API]', `Collecting tests: ${tests.map(t => relative(root, t)).join(', ')}`)
this.collectPromise = this.meta.rpc.collectTests(this.id, tests).finally(() => {
this.collectPromise = this.meta.rpc.collectTests(tests).finally(() => {
this.collectPromise = null
})
}, 50)
Expand All @@ -177,9 +174,7 @@ export class VitestFolderAPI extends VitestReporter {
async dispose() {
WEAKMAP_API_FOLDER.delete(this)
this.handlers.clearListeners()
this.meta.packages.forEach((pkg) => {
delete require.cache[pkg.vitestPackageJsonPath]
})
delete require.cache[this.meta.pkg.vitestPackageJsonPath]
if (!this.meta.process.closed) {
try {
await this.meta.rpc.close()
Expand All @@ -197,33 +192,33 @@ export class VitestFolderAPI extends VitestReporter {
}

async cancelRun() {
await this.meta.rpc.cancelRun(this.id)
await this.meta.rpc.cancelRun()
}

waitForCoverageReport() {
return this.meta.rpc.waitForCoverageReport(this.id)
return this.meta.rpc.waitForCoverageReport()
}

async enableCoverage() {
await this.meta.rpc.enableCoverage(this.id)
await this.meta.rpc.enableCoverage()
}

async disableCoverage() {
await this.meta.rpc.disableCoverage(this.id)
await this.meta.rpc.disableCoverage()
}

async watchTests(files?: string[], testNamePattern?: string) {
await this.meta.rpc.watchTests(this.id, files?.map(normalize), testNamePattern)
await this.meta.rpc.watchTests(files?.map(normalize), testNamePattern)
}

async unwatchTests() {
await this.meta.rpc.unwatchTests(this.id)
await this.meta.rpc.unwatchTests()
}
}

export async function resolveVitestAPI(showWarning: boolean, packages: VitestPackage[]) {
export async function resolveVitestAPI(packages: VitestPackage[]) {
const promises = packages.map(async (pkg) => {
const vitest = await createVitestProcess(showWarning, [pkg])
const vitest = await createVitestProcess(pkg)
return new VitestFolderAPI(pkg, vitest)
})
const apis = await Promise.all(promises)
Expand All @@ -233,39 +228,39 @@ export async function resolveVitestAPI(showWarning: boolean, packages: VitestPac
export interface ResolvedMeta {
rpc: VitestRPC
process: VitestProcess
packages: VitestPackage[]
pkg: VitestPackage
handlers: {
onConsoleLog: (listener: BirpcEvents['onConsoleLog']) => void
onTaskUpdate: (listener: BirpcEvents['onTaskUpdate']) => void
onFinished: (listener: BirpcEvents['onFinished']) => void
onCollected: (listener: BirpcEvents['onCollected']) => void
onWatcherStart: (listener: BirpcEvents['onWatcherStart']) => void
onWatcherRerun: (listener: BirpcEvents['onWatcherRerun']) => void
onConsoleLog: (listener: VitestEvents['onConsoleLog']) => void
onTaskUpdate: (listener: VitestEvents['onTaskUpdate']) => void
onFinished: (listener: VitestEvents['onFinished']) => void
onCollected: (listener: VitestEvents['onCollected']) => void
onWatcherStart: (listener: VitestEvents['onWatcherStart']) => void
onWatcherRerun: (listener: VitestEvents['onWatcherRerun']) => void
clearListeners: () => void
removeListener: (name: string, listener: any) => void
}
}

async function createChildVitestProcess(showWarning: boolean, meta: VitestPackage[]) {
const pnpLoaders = [
...new Set(meta.map(meta => meta.loader).filter(Boolean) as string[]),
]
const pnp = meta.find(meta => meta.pnp)?.pnp as string
if (pnpLoaders.length > 1)
throw new Error(`Multiple loaders are not supported: ${pnpLoaders.join(', ')}`)
if (pnpLoaders.length && !pnp)
function formapPkg(pkg: VitestPackage) {
return `Vitest v${pkg.version} (${relative(dirname(pkg.cwd), pkg.id)})`
}

async function createChildVitestProcess(pkg: VitestPackage) {
const pnpLoader = pkg.loader
const pnp = pkg.pnp
if (pnpLoader && !pnp)
throw new Error('pnp file is required if loader option is used')
const execArgv = pnpLoaders[0] && !gte(process.version, '18.19.0')
const execArgv = pnpLoader && pnp && !gte(process.version, '18.19.0')
? [
'--require',
pnp,
'--experimental-loader',
pathToFileURL(pnpLoaders[0]).toString(),
pathToFileURL(pnpLoader).toString(),
]
: undefined
const env = getConfig().env || {}
const execPath = getConfig().nodeExecutable || await findNode(vscode.workspace.workspaceFile?.fsPath || vscode.workspace.workspaceFolders![0].uri.fsPath)
log.info('[API]', `Running Vitest: ${meta.map(x => `v${x.version} (${relative(dirname(x.cwd), x.id)})`).join(', ')} with Node.js: ${execPath}`)
log.info('[API]', `Running ${formapPkg(pkg)} with Node.js: ${execPath}`)
const vitest = fork(
workerPath,
{
Expand All @@ -282,7 +277,7 @@ async function createChildVitestProcess(showWarning: boolean, meta: VitestPackag
NODE_ENV: env.NODE_ENV ?? process.env.NODE_ENV ?? 'test',
},
stdio: 'overlapped',
cwd: pnp ? dirname(pnp) : meta[0].cwd,
cwd: pnp ? dirname(pnp) : pkg.cwd,
},
)

Expand All @@ -297,24 +292,24 @@ async function createChildVitestProcess(showWarning: boolean, meta: VitestPackag
if (message.type === 'ready') {
vitest.off('message', ready)
// started _some_ projects, but some failed - log them, this can only happen if there are multiple projects
if (message.errors.length) {
message.errors.forEach(([id, error]: [string, string]) => {
const metaIndex = meta.findIndex(m => m.id === id)
meta.splice(metaIndex, 1)
log.error('[API]', `Vitest failed to start for ${id}: \n${error}`)
})
if (showWarning) {
const errorsNumber = message.errors.length
const resultButton = errorsNumber > 1 ? 'See errors' : 'See error'
vscode.window.showWarningMessage(
`There ${errorsNumber > 1 ? 'were' : 'was'} ${pluralize(message.errors.length, 'error')} during Vitest startup. Check the output for more details.`,
resultButton,
).then((result) => {
if (result === resultButton)
vscode.commands.executeCommand('vitest.openOutput')
})
}
}
// if (message.errors.length) {
// message.errors.forEach(([id, error]: [string, string]) => {
// const metaIndex = pkg.findIndex(m => m.id === id)
// pkg.splice(metaIndex, 1)
// log.error('[API]', `Vitest failed to start for ${id}: \n${error}`)
// })
// if (showWarning) {
// const errorsNumber = message.errors.length
// const resultButton = errorsNumber > 1 ? 'See errors' : 'See error'
// vscode.window.showWarningMessage(
// `There ${errorsNumber > 1 ? 'were' : 'was'} ${pluralize(message.errors.length, 'error')} during Vitest startup. Check the output for more details.`,
// resultButton,
// ).then((result) => {
// if (result === resultButton)
// vscode.commands.executeCommand('vitest.openOutput')
// })
// }
// }
resolve(vitest)
}
if (message.type === 'error') {
Expand Down Expand Up @@ -345,28 +340,27 @@ async function createChildVitestProcess(showWarning: boolean, meta: VitestPackag
vitest.once('spawn', () => {
const runnerOptions: WorkerRunnerOptions = {
type: 'init',
meta: meta.map(m => ({
vitestNodePath: m.vitestNodePath,
env: getConfig(m.folder).env || undefined,
configFile: m.configFile,
cwd: m.cwd,
arguments: m.arguments,
workspaceFile: m.workspaceFile,
id: m.id,
})),
loader: pnpLoaders[0] && gte(process.version, '18.19.0') ? pnpLoaders[0] : undefined,
meta: {
vitestNodePath: pkg.vitestNodePath,
env: getConfig(pkg.folder).env || undefined,
configFile: pkg.configFile,
cwd: pkg.cwd,
arguments: pkg.arguments,
workspaceFile: pkg.workspaceFile,
id: pkg.id,
},
loader: pnpLoader && gte(process.version, '18.19.0') ? pnpLoader : undefined,
}

vitest.send(runnerOptions)
})
})
}

// TODO: packages should be a single package
export async function createVitestProcess(showWarning: boolean, packages: VitestPackage[]): Promise<ResolvedMeta> {
const vitest = await createChildVitestProcess(showWarning, packages)
export async function createVitestProcess(pkg: VitestPackage): Promise<ResolvedMeta> {
const vitest = await createChildVitestProcess(pkg)

log.info('[API]', `Vitest ${packages.map(x => `v${x.version} (${relative(dirname(x.cwd), x.id)})`).join(', ')} process ${vitest.pid} created`)
log.info('[API]', `${formapPkg(pkg)} process ${vitest.pid} created`)

const { handlers, api } = createVitestRpc({
on: listener => vitest.on('message', listener),
Expand All @@ -377,7 +371,7 @@ export async function createVitestProcess(showWarning: boolean, packages: Vitest
rpc: api,
process: new VitestChildProvess(vitest),
handlers,
packages,
pkg,
}
}

Expand Down
29 changes: 9 additions & 20 deletions src/api/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ export interface VitestMethods {
enableCoverage: () => void
disableCoverage: () => void
waitForCoverageReport: () => Promise<string | null>
}

type VitestPoolMethods = {
[K in keyof VitestMethods]: (id: string, ...args: Parameters<VitestMethods[K]>) => ReturnType<VitestMethods[K]>
}

export interface VitestPool extends VitestPoolMethods {
close: () => void
}

Expand All @@ -34,11 +27,7 @@ export interface VitestEvents {
onWatcherRerun: (files: string[], trigger?: string, collecting?: boolean) => void
}

export type BirpcEvents = {
[K in keyof VitestEvents]: (folder: string, ...args: Parameters<VitestEvents[K]>) => void
}

export type VitestRPC = BirpcReturn<VitestPool, BirpcEvents>
export type VitestRPC = BirpcReturn<VitestMethods, VitestEvents>

function createHandler<T extends (...args: any) => any>() {
const handlers: T[] = []
Expand All @@ -57,15 +46,15 @@ function createHandler<T extends (...args: any) => any>() {

export function createRpcOptions() {
const handlers = {
onConsoleLog: createHandler<BirpcEvents['onConsoleLog']>(),
onTaskUpdate: createHandler<BirpcEvents['onTaskUpdate']>(),
onFinished: createHandler<BirpcEvents['onFinished']>(),
onCollected: createHandler<BirpcEvents['onCollected']>(),
onWatcherRerun: createHandler<BirpcEvents['onWatcherRerun']>(),
onWatcherStart: createHandler<BirpcEvents['onWatcherStart']>(),
onConsoleLog: createHandler<VitestEvents['onConsoleLog']>(),
onTaskUpdate: createHandler<VitestEvents['onTaskUpdate']>(),
onFinished: createHandler<VitestEvents['onFinished']>(),
onCollected: createHandler<VitestEvents['onCollected']>(),
onWatcherRerun: createHandler<VitestEvents['onWatcherRerun']>(),
onWatcherStart: createHandler<VitestEvents['onWatcherStart']>(),
}

const events: Omit<BirpcEvents, 'onReady' | 'onError'> = {
const events: Omit<VitestEvents, 'onReady' | 'onError'> = {
onConsoleLog: handlers.onConsoleLog.trigger,
onFinished: handlers.onFinished.trigger,
onTaskUpdate: handlers.onTaskUpdate.trigger,
Expand Down Expand Up @@ -100,7 +89,7 @@ export function createVitestRpc(options: {
}) {
const { events, handlers } = createRpcOptions()

const api = createBirpc<VitestPool, BirpcEvents>(
const api = createBirpc<VitestMethods, VitestEvents>(
events,
{
timeout: -1,
Expand Down
Loading

0 comments on commit 2cff93c

Please sign in to comment.