Skip to content

Commit

Permalink
fix(vitest): deduplicate vitest when running globally or in a workspa…
Browse files Browse the repository at this point in the history
…ce (#4238)
  • Loading branch information
sheremet-va authored Oct 4, 2023
1 parent 62d0080 commit 9350461
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 21 deletions.
7 changes: 7 additions & 0 deletions packages/vite-node/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { performance } from 'node:perf_hooks'
import { existsSync } from 'node:fs'
import assert from 'node:assert'
import { join, normalize, relative, resolve } from 'pathe'
import type { TransformResult, ViteDevServer } from 'vite'
import createDebug from 'debug'
Expand Down Expand Up @@ -131,9 +132,14 @@ export class ViteNodeServer {
return (ssrTransformResult?.map || null) as unknown as EncodedSourceMap | null
}

private assertMode(mode: 'web' | 'ssr') {
assert(mode === 'web' || mode === 'ssr', `"transformMode" can only be "web" or "ssr", received "${mode}".`)
}

async fetchModule(id: string, transformMode?: 'web' | 'ssr'): Promise<FetchResult> {
const moduleId = normalizeModuleId(id)
const mode = transformMode || this.getTransformMode(id)
this.assertMode(mode)
const promiseMap = this.fetchPromiseMap[mode]
// reuse transform for concurrent requests
if (!promiseMap.has(moduleId)) {
Expand All @@ -152,6 +158,7 @@ export class ViteNodeServer {

async transformRequest(id: string, filepath = id, transformMode?: 'web' | 'ssr') {
const mode = transformMode || this.getTransformMode(id)
this.assertMode(mode)
const promiseMap = this.transformPromiseMap[mode]
// reuse transform for concurrent requests
if (!promiseMap.has(id)) {
Expand Down
19 changes: 18 additions & 1 deletion packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync, promises as fs } from 'node:fs'
import type { ViteDevServer } from 'vite'
import { mergeConfig } from 'vite'
import { basename, dirname, join, normalize, relative } from 'pathe'
import { basename, dirname, join, normalize, relative, resolve } from 'pathe'
import fg from 'fast-glob'
import mm from 'micromatch'
import c from 'picocolors'
Expand All @@ -15,6 +15,7 @@ import { hasFailed, noop, slash, toArray } from '../utils'
import { getCoverageProvider } from '../integrations/coverage'
import type { BrowserProvider } from '../types/browser'
import { CONFIG_NAMES, configFiles, workspacesFiles as workspaceFiles } from '../constants'
import { rootDir } from '../paths'
import { createPool } from './pool'
import type { ProcessPool, WorkspaceSpec } from './pool'
import { createBenchmarkReporters, createReporters } from './reporters/utils'
Expand Down Expand Up @@ -58,6 +59,12 @@ export class Vitest {
public projects: WorkspaceProject[] = []
private projectsTestFiles = new Map<string, Set<WorkspaceProject>>()

projectFiles!: {
workerPath: string
forksPath: string
vmPath: string
}

constructor(
public readonly mode: VitestRunMode,
) {
Expand Down Expand Up @@ -90,6 +97,16 @@ export class Vitest {
this.registerWatcher()

this.vitenode = new ViteNodeServer(server, this.config.server)

// if Vitest is running globally, then we should still import local vitest if possible
const projectVitestPath = await this.vitenode.resolveId('vitest')
const vitestDir = projectVitestPath ? resolve(projectVitestPath.id, '../..') : rootDir
this.projectFiles = {
workerPath: join(vitestDir, 'dist/worker.js'),
forksPath: join(vitestDir, 'dist/child.js'),
vmPath: join(vitestDir, 'dist/vm.js'),
}

const node = this.vitenode
this.runner = new ViteNodeRunner({
root: server.config.root,
Expand Down
5 changes: 3 additions & 2 deletions packages/vitest/src/node/plugins/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DepOptimizationOptions, ResolvedConfig, UserConfig as ViteConfig }
import { dirname } from 'pathe'
import type { DepsOptimizationOptions, InlineConfig } from '../../types'
import { VitestCache } from '../cache'
import { rootDir } from '../../paths'

export function resolveOptimizerConfig(_testOptions: DepsOptimizationOptions | undefined, viteOptions: DepOptimizationOptions | undefined, testConfig: InlineConfig) {
const testOptions = _testOptions || {}
Expand Down Expand Up @@ -102,6 +103,6 @@ export function hijackVitePluginInject(viteConfig: ResolvedConfig) {

export function resolveFsAllow(projectRoot: string, rootConfigFile: string | false | undefined) {
if (!rootConfigFile)
return [searchForWorkspaceRoot(projectRoot)]
return [dirname(rootConfigFile), searchForWorkspaceRoot(projectRoot)]
return [searchForWorkspaceRoot(projectRoot), rootDir]
return [dirname(rootConfigFile), searchForWorkspaceRoot(projectRoot), rootDir]
}
4 changes: 4 additions & 0 deletions packages/vitest/src/node/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export interface ProcessPool {
}

export interface PoolProcessOptions {
workerPath: string
forksPath: string
vmPath: string
execArgv: string[]
env: Record<string, string>
}
Expand Down Expand Up @@ -61,6 +64,7 @@ export function createPool(ctx: Vitest): ProcessPool {
)

const options: PoolProcessOptions = {
...ctx.projectFiles,
execArgv: ctx.config.deps.registerNodeLoader
? [
...execArgv,
Expand Down
9 changes: 2 additions & 7 deletions packages/vitest/src/node/pools/child.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import v8 from 'node:v8'
import { fileURLToPath, pathToFileURL } from 'node:url'
import * as nodeos from 'node:os'
import EventEmitter from 'node:events'
import { Tinypool } from 'tinypool'
import type { TinypoolChannel, Options as TinypoolOptions } from 'tinypool'
import { createBirpc } from 'birpc'
import { resolve } from 'pathe'
import type { ContextTestEnvironment, ResolvedConfig, RunnerRPC, RuntimeRPC, Vitest } from '../../types'
import type { ChildContext } from '../../types/child'
import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool'
import { distDir } from '../../paths'
import type { WorkspaceProject } from '../workspace'
import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers'
import { groupBy } from '../../utils'
import { createMethodsRPC } from './rpc'

const childPath = fileURLToPath(pathToFileURL(resolve(distDir, './child.js')).href)

function createChildProcessChannel(project: WorkspaceProject) {
const emitter = new EventEmitter()
const cleanup = () => emitter.removeAllListeners()
Expand Down Expand Up @@ -53,7 +48,7 @@ function stringifyRegex(input: RegExp | string): string {
return `$$vitest:${input.toString()}`
}

export function createChildProcessPool(ctx: Vitest, { execArgv, env }: PoolProcessOptions): ProcessPool {
export function createChildProcessPool(ctx: Vitest, { execArgv, env, forksPath }: PoolProcessOptions): ProcessPool {
const numCpus
= typeof nodeos.availableParallelism === 'function'
? nodeos.availableParallelism()
Expand All @@ -68,7 +63,7 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env }: PoolProce

const options: TinypoolOptions = {
runtime: 'child_process',
filename: childPath,
filename: forksPath,

maxThreads,
minThreads,
Expand Down
7 changes: 1 addition & 6 deletions packages/vitest/src/node/pools/threads.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { MessageChannel } from 'node:worker_threads'
import * as nodeos from 'node:os'
import { pathToFileURL } from 'node:url'
import { createBirpc } from 'birpc'
import { resolve } from 'pathe'
import type { Options as TinypoolOptions } from 'tinypool'
import Tinypool from 'tinypool'
import { distDir } from '../../paths'
import type { ContextTestEnvironment, ResolvedConfig, RunnerRPC, RuntimeRPC, Vitest, WorkerContext } from '../../types'
import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool'
import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers'
import { AggregateError, groupBy } from '../../utils/base'
import type { WorkspaceProject } from '../workspace'
import { createMethodsRPC } from './rpc'

const workerPath = pathToFileURL(resolve(distDir, './worker.js')).href

function createWorkerChannel(project: WorkspaceProject) {
const channel = new MessageChannel()
const port = channel.port2
Expand All @@ -38,7 +33,7 @@ function createWorkerChannel(project: WorkspaceProject) {
return { workerPort, port }
}

export function createThreadsPool(ctx: Vitest, { execArgv, env }: PoolProcessOptions): ProcessPool {
export function createThreadsPool(ctx: Vitest, { execArgv, env, workerPath }: PoolProcessOptions): ProcessPool {
const numCpus
= typeof nodeos.availableParallelism === 'function'
? nodeos.availableParallelism()
Expand Down
8 changes: 3 additions & 5 deletions packages/vitest/src/node/pools/vm-threads.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { MessageChannel } from 'node:worker_threads'
import * as nodeos from 'node:os'
import { pathToFileURL } from 'node:url'
import { createBirpc } from 'birpc'
import { resolve } from 'pathe'
import type { Options as TinypoolOptions } from 'tinypool'
import Tinypool from 'tinypool'
import { distDir, rootDir } from '../../paths'
import { rootDir } from '../../paths'
import type { ContextTestEnvironment, ResolvedConfig, RunnerRPC, RuntimeRPC, Vitest, WorkerContext } from '../../types'
import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool'
import { groupFilesByEnv } from '../../utils/test-helpers'
Expand All @@ -14,7 +13,6 @@ import type { WorkspaceProject } from '../workspace'
import { getWorkerMemoryLimit, stringToBytes } from '../../utils/memory-limit'
import { createMethodsRPC } from './rpc'

const workerPath = pathToFileURL(resolve(distDir, './vm.js')).href
const suppressWarningsPath = resolve(rootDir, './suppress-warnings.cjs')

function createWorkerChannel(project: WorkspaceProject) {
Expand All @@ -40,7 +38,7 @@ function createWorkerChannel(project: WorkspaceProject) {
return { workerPort, port }
}

export function createVmThreadsPool(ctx: Vitest, { execArgv, env }: PoolProcessOptions): ProcessPool {
export function createVmThreadsPool(ctx: Vitest, { execArgv, env, vmPath }: PoolProcessOptions): ProcessPool {
const numCpus
= typeof nodeos.availableParallelism === 'function'
? nodeos.availableParallelism()
Expand All @@ -54,7 +52,7 @@ export function createVmThreadsPool(ctx: Vitest, { execArgv, env }: PoolProcessO
const minThreads = ctx.config.poolOptions?.vmThreads?.minThreads ?? threadsCount

const options: TinypoolOptions = {
filename: workerPath,
filename: vmPath,
// TODO: investigate further
// It seems atomics introduced V8 Fatal Error https://github.com/vitest-dev/vitest/issues/1191
useAtomics: ctx.config.poolOptions?.vmThreads?.useAtomics ?? false,
Expand Down

0 comments on commit 9350461

Please sign in to comment.