From 5b58b3990e35ae8204f49ed540fc51378daabfde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Tue, 27 Feb 2024 14:15:44 +0200 Subject: [PATCH] fix(forks): wrap `defines` to support `undefined` values (#5284) --- packages/vitest/src/node/pools/forks.ts | 16 ++-------- packages/vitest/src/node/pools/vmForks.ts | 15 ++------- packages/vitest/src/runtime/workers/forks.ts | 4 +-- packages/vitest/src/runtime/workers/utils.ts | 32 +++++++++++++------ .../vitest/src/runtime/workers/vmForks.ts | 4 +-- packages/vitest/src/utils/config-helpers.ts | 25 +++++++++++++++ packages/vitest/src/workers.ts | 2 +- test/core/test/define-ssr.test.ts | 11 +++++++ test/core/test/define-web.test.ts | 11 +++++++ test/core/vite.config.ts | 4 +++ 10 files changed, 83 insertions(+), 41 deletions(-) diff --git a/packages/vitest/src/node/pools/forks.ts b/packages/vitest/src/node/pools/forks.ts index b6f8072d943b..38e1266a90e9 100644 --- a/packages/vitest/src/node/pools/forks.ts +++ b/packages/vitest/src/node/pools/forks.ts @@ -8,6 +8,7 @@ import type { ContextRPC, ContextTestEnvironment, ResolvedConfig, RunnerRPC, Run import type { PoolProcessOptions, ProcessPool, RunWithFiles } from '../pool' import type { WorkspaceProject } from '../workspace' import { envsOrder, groupFilesByEnv } from '../../utils/test-helpers' +import { wrapSerializableConfig } from '../../utils/config-helpers' import { groupBy, resolve } from '../../utils' import { createMethodsRPC } from './rpc' @@ -44,12 +45,6 @@ function createChildProcessChannel(project: WorkspaceProject) { return { channel, cleanup } } -function stringifyRegex(input: RegExp | string): string { - if (typeof input === 'string') - return input - return `$$vitest:${input.toString()}` -} - export function createForksPool(ctx: Vitest, { execArgv, env }: PoolProcessOptions): ProcessPool { const numCpus = typeof nodeos.availableParallelism === 'function' @@ -144,14 +139,7 @@ export function createForksPool(ctx: Vitest, { execArgv, env }: PoolProcessOptio return configs.get(project)! const _config = project.getSerializableConfig() - - const config = { - ..._config, - // v8 serialize does not support regex - testNamePattern: _config.testNamePattern - ? stringifyRegex(_config.testNamePattern) - : undefined, - } as ResolvedConfig + const config = wrapSerializableConfig(_config) configs.set(project, config) return config diff --git a/packages/vitest/src/node/pools/vmForks.ts b/packages/vitest/src/node/pools/vmForks.ts index f4f8db7352a9..5acb6e24d219 100644 --- a/packages/vitest/src/node/pools/vmForks.ts +++ b/packages/vitest/src/node/pools/vmForks.ts @@ -12,6 +12,7 @@ import { groupFilesByEnv } from '../../utils/test-helpers' import { AggregateError } from '../../utils/base' import type { WorkspaceProject } from '../workspace' import { getWorkerMemoryLimit, stringToBytes } from '../../utils/memory-limit' +import { wrapSerializableConfig } from '../../utils/config-helpers' import { createMethodsRPC } from './rpc' const suppressWarningsPath = resolve(rootDir, './suppress-warnings.cjs') @@ -49,12 +50,6 @@ function createChildProcessChannel(project: WorkspaceProject) { return { channel, cleanup } } -function stringifyRegex(input: RegExp | string): string { - if (typeof input === 'string') - return input - return `$$vitest:${input.toString()}` -} - export function createVmForksPool(ctx: Vitest, { execArgv, env }: PoolProcessOptions): ProcessPool { const numCpus = typeof nodeos.availableParallelism === 'function' @@ -149,14 +144,8 @@ export function createVmForksPool(ctx: Vitest, { execArgv, env }: PoolProcessOpt return configs.get(project)! const _config = project.getSerializableConfig() + const config = wrapSerializableConfig(_config) - const config = { - ..._config, - // v8 serialize does not support regex - testNamePattern: _config.testNamePattern - ? stringifyRegex(_config.testNamePattern) - : undefined, - } as ResolvedConfig configs.set(project, config) return config } diff --git a/packages/vitest/src/runtime/workers/forks.ts b/packages/vitest/src/runtime/workers/forks.ts index b63b03472d8f..65b0e919776a 100644 --- a/packages/vitest/src/runtime/workers/forks.ts +++ b/packages/vitest/src/runtime/workers/forks.ts @@ -1,6 +1,6 @@ import v8 from 'node:v8' import type { WorkerGlobalState } from '../../types/worker' -import { createForksRpcOptions, unwrapForksConfig } from './utils' +import { createForksRpcOptions, unwrapSerializableConfig } from './utils' import { runBaseTests } from './base' import type { VitestWorker } from './types' @@ -13,7 +13,7 @@ class ForksBaseWorker implements VitestWorker { // TODO: don't rely on reassigning process.exit // https://github.com/vitest-dev/vitest/pull/4441#discussion_r1443771486 const exit = process.exit - state.ctx.config = unwrapForksConfig(state.ctx.config) + state.ctx.config = unwrapSerializableConfig(state.ctx.config) try { await runBaseTests(state) diff --git a/packages/vitest/src/runtime/workers/utils.ts b/packages/vitest/src/runtime/workers/utils.ts index 57d422c3c6ec..9ffbf2b4983d 100644 --- a/packages/vitest/src/runtime/workers/utils.ts +++ b/packages/vitest/src/runtime/workers/utils.ts @@ -4,6 +4,8 @@ import type { WorkerContext } from '../../types/worker' import type { ResolvedConfig } from '../../types/config' import type { WorkerRpcOptions } from './types' +const REGEXP_WRAP_PREFIX = '$$vitest:' + export function createThreadsRpcOptions({ port }: WorkerContext): WorkerRpcOptions { return { post: (v) => { port.postMessage(v) }, @@ -28,15 +30,27 @@ export function createForksRpcOptions(nodeV8: typeof import('v8')): WorkerRpcOpt } } -function parsePossibleRegexp(str: string | RegExp) { - const prefix = '$$vitest:' - if (typeof str === 'string' && str.startsWith(prefix)) - return parseRegexp(str.slice(prefix.length)) - return str -} +/** + * Reverts the wrapping done by `utils/config-helpers.ts`'s `wrapSerializableConfig` + */ +export function unwrapSerializableConfig(config: ResolvedConfig) { + if (config.testNamePattern && typeof config.testNamePattern === 'string') { + const testNamePattern = config.testNamePattern as string + + if (testNamePattern.startsWith(REGEXP_WRAP_PREFIX)) + config.testNamePattern = parseRegexp(testNamePattern.slice(REGEXP_WRAP_PREFIX.length)) + } + + if (config.defines && Array.isArray(config.defines.keys) && config.defines.original) { + const { keys, original } = config.defines + const defines: ResolvedConfig['defines'] = {} + + // Apply all keys from the original. Entries which had undefined value are missing from original now + for (const key of keys) + defines[key] = original[key] + + config.defines = defines + } -export function unwrapForksConfig(config: ResolvedConfig) { - if (config.testNamePattern) - config.testNamePattern = parsePossibleRegexp(config.testNamePattern) as RegExp return config } diff --git a/packages/vitest/src/runtime/workers/vmForks.ts b/packages/vitest/src/runtime/workers/vmForks.ts index 5f43d89bec98..365c7634851c 100644 --- a/packages/vitest/src/runtime/workers/vmForks.ts +++ b/packages/vitest/src/runtime/workers/vmForks.ts @@ -1,6 +1,6 @@ import v8 from 'node:v8' import type { WorkerGlobalState } from '../../types/worker' -import { createForksRpcOptions, unwrapForksConfig } from './utils' +import { createForksRpcOptions, unwrapSerializableConfig } from './utils' import type { VitestWorker } from './types' import { runVmTests } from './vm' @@ -11,7 +11,7 @@ class ForksVmWorker implements VitestWorker { async runTests(state: WorkerGlobalState) { const exit = process.exit - state.ctx.config = unwrapForksConfig(state.ctx.config) + state.ctx.config = unwrapSerializableConfig(state.ctx.config) try { await runVmTests(state) diff --git a/packages/vitest/src/utils/config-helpers.ts b/packages/vitest/src/utils/config-helpers.ts index bd5f0b50fc97..c86b0c2f51a3 100644 --- a/packages/vitest/src/utils/config-helpers.ts +++ b/packages/vitest/src/utils/config-helpers.ts @@ -1,5 +1,8 @@ +import type { ResolvedConfig } from '../types/config' import type { BenchmarkBuiltinReporters, BuiltinReporters } from '../node/reporters' +const REGEXP_WRAP_PREFIX = '$$vitest:' + interface PotentialConfig { outputFile?: string | Partial> } @@ -13,3 +16,25 @@ export function getOutputFile(config: PotentialConfig | undefined, reporter: Bui return config.outputFile[reporter] } + +/** + * Prepares `ResolvedConfig` for serialization, e.g. `node:v8.serialize` + */ +export function wrapSerializableConfig(config: ResolvedConfig) { + let testNamePattern = config.testNamePattern + let defines = config.defines + + // v8 serialize does not support regex + if (testNamePattern && typeof testNamePattern !== 'string') + testNamePattern = `${REGEXP_WRAP_PREFIX}${testNamePattern.toString()}` as unknown as RegExp + + // v8 serialize drops properties with undefined value + if (defines) + defines = { keys: Object.keys(defines), original: defines } + + return { + ...config, + testNamePattern, + defines, + } as ResolvedConfig +} diff --git a/packages/vitest/src/workers.ts b/packages/vitest/src/workers.ts index 413fb3a42d79..7456a5dece6d 100644 --- a/packages/vitest/src/workers.ts +++ b/packages/vitest/src/workers.ts @@ -1,4 +1,4 @@ -export { createForksRpcOptions, createThreadsRpcOptions, unwrapForksConfig } from './runtime/workers/utils' +export { createForksRpcOptions, createThreadsRpcOptions, unwrapSerializableConfig } from './runtime/workers/utils' export { provideWorkerState } from './utils/global' export { run as runVitestWorker } from './runtime/worker' export { runVmTests } from './runtime/workers/vm' diff --git a/test/core/test/define-ssr.test.ts b/test/core/test/define-ssr.test.ts index 4cbe9bb6b140..3dcb75129b81 100644 --- a/test/core/test/define-ssr.test.ts +++ b/test/core/test/define-ssr.test.ts @@ -3,6 +3,10 @@ import { afterAll, expect, test } from 'vitest' declare let __DEFINE__: string declare let __JSON__: any declare let __MODE__: string +declare let __UNDEFINED__: undefined +declare let __NULL__: null +declare let __ZERO__: 0 +declare let __FALSE__: false declare let SOME: { VARIABLE: string SOME: { @@ -64,3 +68,10 @@ test('dotted defines are processed by Vite, but cannot be reassigned', () => { SOME.VARIABLE = 'new variable' expect(SOME.VARIABLE).not.toBe('new variable') }) + +test('falsy defines are passed', () => { + expect(__UNDEFINED__).toBe(undefined) + expect(__NULL__).toBe(null) + expect(__ZERO__).toBe(0) + expect(__FALSE__).toBe(false) +}) diff --git a/test/core/test/define-web.test.ts b/test/core/test/define-web.test.ts index 3af47b384850..94ef951c93d3 100644 --- a/test/core/test/define-web.test.ts +++ b/test/core/test/define-web.test.ts @@ -5,6 +5,10 @@ import { afterAll, expect, test } from 'vitest' declare let __DEFINE__: string declare let __JSON__: any declare let __MODE__: string +declare let __UNDEFINED__: undefined +declare let __NULL__: null +declare let __ZERO__: 0 +declare let __FALSE__: false declare let SOME: { VARIABLE: string SOME: { @@ -61,3 +65,10 @@ test('dotted defines can be reassigned', () => { SOME.VARIABLE = 'new variable' expect(SOME.VARIABLE).toBe('new variable') }) + +test('falsy defines are passed', () => { + expect(__UNDEFINED__).toBe(undefined) + expect(__NULL__).toBe(null) + expect(__ZERO__).toBe(0) + expect(__FALSE__).toBe(false) +}) diff --git a/test/core/vite.config.ts b/test/core/vite.config.ts index a951fb84eff6..ff8ff6b20be2 100644 --- a/test/core/vite.config.ts +++ b/test/core/vite.config.ts @@ -32,6 +32,10 @@ export default defineConfig({ '__MODE__': 'process.env.MODE', 'SOME.VARIABLE': '"variable"', 'SOME.SOME.VARIABLE': '"nested variable"', + '__UNDEFINED__': undefined, + '__NULL__': null, + '__ZERO__': 0, + '__FALSE__': false, }, resolve: { alias: [