diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index 17597ed3192d..d05bfdfebb20 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -151,8 +151,8 @@ export function createPool(ctx: Vitest): ProcessPool { invalidates, providedContext: project.getProvidedContext(), workerId: workerId++, + environment, }, - environment, project, env, execArgv, diff --git a/packages/vitest/src/node/pools/pool.ts b/packages/vitest/src/node/pools/pool.ts index e0c357c7915b..7c13499176e4 100644 --- a/packages/vitest/src/node/pools/pool.ts +++ b/packages/vitest/src/node/pools/pool.ts @@ -1,3 +1,4 @@ +import type { ContextTestEnvironment } from '../../types/worker' import type { Logger } from '../logger' import type { StateManager } from '../state' import type { PoolOptions, PoolTask, WorkerResponse } from './types' @@ -213,7 +214,9 @@ export class Pool { const index = this.sharedRunners.findIndex(runner => isEqualRunner(runner, task)) if (index !== -1) { - return this.sharedRunners.splice(index, 1)[0] + const runner = this.sharedRunners.splice(index, 1)[0] + runner.reconfigure(task) + return runner } } @@ -221,7 +224,7 @@ export class Pool { distPath: this.options.distPath, project: task.project, method, - environment: task.environment, + environment: task.context.environment, env: task.env, execArgv: task.execArgv, } @@ -289,11 +292,47 @@ function isEqualRunner(runner: PoolRunner, task: PoolTask) { if (task.isolate) { throw new Error('Isolated tasks should not share runners') } + if (runner.worker.name !== task.worker || runner.project !== task.project) { + return false + } + // by default, check that the environments are the same + // some workers (like vmThreads/vmForks) do not need this check + if (!runner.worker.canReuse) { + return isEnvironmentEqual(task.context.environment, runner.environment) + } + return runner.worker.canReuse(task) +} + +function isEnvironmentEqual(env1: ContextTestEnvironment, env2: ContextTestEnvironment): boolean { + if (env1.name !== env2.name) { + return false + } + return deepEqual(env1.options, env2.options) +} + +function deepEqual(obj1: any, obj2: any): boolean { + if (obj1 === obj2) { + return true + } + if (obj1 == null || obj2 == null) { + return obj1 === obj2 + } + if (typeof obj1 !== 'object' || typeof obj2 !== 'object') { + return false + } + + const keys1 = Object.keys(obj1) + const keys2 = Object.keys(obj2) + + if (keys1.length !== keys2.length) { + return false + } + + for (const key of keys1) { + if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + return false + } + } - return ( - runner.worker.name === task.worker - && runner.project === task.project - && runner.environment.name === task.environment.name - && (!runner.worker.canReuse || runner.worker.canReuse(task)) - ) + return true } diff --git a/packages/vitest/src/node/pools/poolRunner.ts b/packages/vitest/src/node/pools/poolRunner.ts index 1d78c2028419..da40c7a14468 100644 --- a/packages/vitest/src/node/pools/poolRunner.ts +++ b/packages/vitest/src/node/pools/poolRunner.ts @@ -5,7 +5,7 @@ import type { RunnerRPC, RuntimeRPC } from '../../types/rpc' import type { ContextTestEnvironment, WorkerExecuteContext } from '../../types/worker' import type { Traces } from '../../utils/traces' import type { TestProject } from '../project' -import type { PoolOptions, PoolRunnerOTEL, PoolWorker, WorkerRequest, WorkerResponse } from './types' +import type { PoolOptions, PoolRunnerOTEL, PoolTask, PoolWorker, WorkerRequest, WorkerResponse } from './types' import { EventEmitter } from 'node:events' import { createDefer } from '@vitest/utils/helpers' import { createBirpc } from 'birpc' @@ -43,7 +43,7 @@ export class PoolRunner { public poolId: number | undefined = undefined public readonly project: TestProject - public readonly environment: ContextTestEnvironment + public environment: ContextTestEnvironment private _state: RunnerState = RunnerState.IDLE private _operationLock: DeferPromise | null = null @@ -113,6 +113,15 @@ export class PoolRunner { this._offCancel = vitest.onCancel(reason => this._rpc.onCancel(reason)) } + /** + * "reconfigure" can only be called if `environment` is different, since different project always + * requires a new PoolRunner instance. + */ + public reconfigure(task: PoolTask): void { + this.environment = task.context.environment + this._otel?.span.setAttribute('vitest.environment', this.environment.name) + } + postMessage(message: WorkerRequest): void { // Only send messages when runner is active (not fully stopped) // Allow sending during STOPPING state for the 'stop' message itself diff --git a/packages/vitest/src/node/pools/types.ts b/packages/vitest/src/node/pools/types.ts index f336121ed000..9f6f6f21716c 100644 --- a/packages/vitest/src/node/pools/types.ts +++ b/packages/vitest/src/node/pools/types.ts @@ -34,8 +34,8 @@ export interface PoolWorker { /** * This is called on workers that already satisfy certain constraints: + * - The task has the same worker name * - The task has the same project - * - The task has the same environment */ canReuse?: (task: PoolTask) => boolean } @@ -55,7 +55,6 @@ export interface PoolTask { */ execArgv: string[] context: WorkerExecuteContext - environment: ContextTestEnvironment memoryLimit: number | null } diff --git a/packages/vitest/src/node/pools/workers/vmForksWorker.ts b/packages/vitest/src/node/pools/workers/vmForksWorker.ts index 2cc5c8d2e870..f73f38456230 100644 --- a/packages/vitest/src/node/pools/workers/vmForksWorker.ts +++ b/packages/vitest/src/node/pools/workers/vmForksWorker.ts @@ -14,4 +14,8 @@ export class VmForksPoolWorker extends ForksPoolWorker { /** Loads {@link file://./../../../runtime/workers/vmForks.ts} */ this.entrypoint = resolve(options.distPath, 'workers/vmForks.js') } + + canReuse(): boolean { + return true + } } diff --git a/packages/vitest/src/node/pools/workers/vmThreadsWorker.ts b/packages/vitest/src/node/pools/workers/vmThreadsWorker.ts index 779e45b3be67..b17b25c6e096 100644 --- a/packages/vitest/src/node/pools/workers/vmThreadsWorker.ts +++ b/packages/vitest/src/node/pools/workers/vmThreadsWorker.ts @@ -14,4 +14,8 @@ export class VmThreadsPoolWorker extends ThreadsPoolWorker { /** Loads {@link file://./../../../runtime/workers/vmThreads.ts} */ this.entrypoint = resolve(options.distPath, 'workers/vmThreads.js') } + + canReuse(): boolean { + return true + } } diff --git a/packages/vitest/src/types/worker.ts b/packages/vitest/src/types/worker.ts index 1b84b390d01f..d96da85a49f4 100644 --- a/packages/vitest/src/types/worker.ts +++ b/packages/vitest/src/types/worker.ts @@ -24,6 +24,7 @@ export interface WorkerExecuteContext { files: FileSpecification[] providedContext: Record invalidates?: string[] + environment: ContextTestEnvironment /** Exposed to test runner as `VITEST_WORKER_ID`. Value is unique per each isolated worker. */ workerId: number