Skip to content

Commit

Permalink
fix: filter projects eagerly during config resolution (#7313)
Browse files Browse the repository at this point in the history
Co-authored-by: Ari Perkkiö <[email protected]>
  • Loading branch information
sheremet-va and AriPerkkio authored Jan 23, 2025
1 parent ed9aeba commit dff4406
Show file tree
Hide file tree
Showing 21 changed files with 359 additions and 84 deletions.
3 changes: 3 additions & 0 deletions packages/browser/src/node/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {
}
await project._initBrowserProvider()

if (!project.browser) {
throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ''}. This is a bug in Vitest. Please, open a new issue with reproduction.`)
}
await executeTests(method, project, files)
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/utils/src/source-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ export function parseSingleV8Stack(raw: string): ParsedStack | null {
}

// normalize Windows path (\ -> /)
file = resolve(file)
file = file.startsWith('node:') || file.startsWith('internal:')
? file
: resolve(file)

if (method) {
method = method.replace(/__vite_ssr_import_\d+__\./g, '')
Expand Down
21 changes: 10 additions & 11 deletions packages/vitest/src/node/config/resolveConfig.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { ResolvedConfig as ResolvedViteConfig } from 'vite'
import type { Logger } from '../logger'
import type { Vitest } from '../core'
import type { BenchmarkBuiltinReporters } from '../reporters'
import type {
ApiConfig,
ResolvedConfig,
UserConfig,
VitestRunMode,
} from '../types/config'
import type { BaseCoverageOptions, CoverageReporterWithOptions } from '../types/coverage'
import type { BuiltinPool, ForksOptions, PoolOptions, ThreadsOptions } from '../types/pool-options'
Expand All @@ -20,7 +19,6 @@ import {
extraInlineDeps,
} from '../../constants'
import { benchmarkConfigDefaults, configDefaults } from '../../defaults'
import { wildcardPatternToRegExp } from '../../utils/base'
import { isCI, stdProvider } from '../../utils/env'
import { getWorkersCountByPercentage } from '../../utils/workers'
import { VitestCache } from '../cache'
Expand Down Expand Up @@ -111,11 +109,12 @@ function resolveInlineWorkerOption(value: string | number): number {
}

export function resolveConfig(
mode: VitestRunMode,
vitest: Vitest,
options: UserConfig,
viteConfig: ResolvedViteConfig,
logger: Logger,
): ResolvedConfig {
const mode = vitest.mode
const logger = vitest.logger
if (options.dom) {
if (
viteConfig.test?.environment != null
Expand All @@ -142,6 +141,7 @@ export function resolveConfig(
mode,
} as any as ResolvedConfig

resolved.project = toArray(resolved.project)
resolved.provide ??= {}

const inspector = resolved.inspect || resolved.inspectBrk
Expand Down Expand Up @@ -256,15 +256,15 @@ export function resolveConfig(
}
}

const playwrightChromiumOnly = isPlaywrightChromiumOnly(resolved)
const playwrightChromiumOnly = isPlaywrightChromiumOnly(vitest, resolved)

// Browser-mode "Playwright + Chromium" only features:
if (browser.enabled && !playwrightChromiumOnly) {
const browserConfig = {
browser: {
provider: browser.provider,
name: browser.name,
instances: browser.instances,
instances: browser.instances?.map(i => ({ browser: i.browser })),
},
}

Expand Down Expand Up @@ -469,7 +469,7 @@ export function resolveConfig(
resolved.forceRerunTriggers.push(...resolved.snapshotSerializers)

if (options.resolveSnapshotPath) {
delete (resolved as UserConfig).resolveSnapshotPath
delete (resolved as any).resolveSnapshotPath
}

resolved.pool ??= 'threads'
Expand Down Expand Up @@ -897,7 +897,7 @@ export function resolveCoverageReporters(configReporters: NonNullable<BaseCovera
return resolvedReporters
}

function isPlaywrightChromiumOnly(config: ResolvedConfig) {
function isPlaywrightChromiumOnly(vitest: Vitest, config: ResolvedConfig) {
const browser = config.browser
if (!browser || browser.provider !== 'playwright' || !browser.enabled) {
return false
Expand All @@ -908,11 +908,10 @@ function isPlaywrightChromiumOnly(config: ResolvedConfig) {
if (!browser.instances) {
return false
}
const filteredProjects = toArray(config.project).map(p => wildcardPatternToRegExp(p))
for (const instance of browser.instances) {
const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser)
// browser config is filtered out
if (filteredProjects.length && !filteredProjects.every(p => p.test(name))) {
if (!vitest._matchesProjectFilter(name)) {
continue
}
if (instance.browser !== 'chromium') {
Expand Down
56 changes: 38 additions & 18 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { VitestSpecifications } from './specifications'
import { StateManager } from './state'
import { TestRun } from './test-run'
import { VitestWatcher } from './watcher'
import { resolveBrowserWorkspace, resolveWorkspace } from './workspace/resolveWorkspace'
import { getDefaultTestProject, resolveBrowserWorkspace, resolveWorkspace } from './workspace/resolveWorkspace'

const WATCHER_DEBOUNCE = 100

Expand Down Expand Up @@ -90,14 +90,19 @@ export class Vitest {
/** @internal */ closingPromise?: Promise<void>
/** @internal */ isCancelling = false
/** @internal */ coreWorkspaceProject: TestProject | undefined
/** @internal */ resolvedProjects: TestProject[] = []
/**
* @internal
* @deprecated
*/
resolvedProjects: TestProject[] = []
/** @internal */ _browserLastPort = defaultBrowserPort
/** @internal */ _browserSessions = new BrowserSessions()
/** @internal */ _options: UserConfig = {}
/** @internal */ reporters: Reporter[] = undefined!
/** @internal */ vitenode: ViteNodeServer = undefined!
/** @internal */ runner: ViteNodeRunner = undefined!
/** @internal */ _testRun: TestRun = undefined!
/** @internal */ _projectFilters: RegExp[] = []

private isFirstRun = true
private restartsCount = 0
Expand Down Expand Up @@ -211,9 +216,11 @@ export class Vitest {
this.specifications.clearCache()
this._onUserTestsRerun = []

const resolved = resolveConfig(this.mode, options, server.config, this.logger)

this._projectFilters = toArray(options.project || []).map(project => wildcardPatternToRegExp(project))
this._vite = server

const resolved = resolveConfig(this, options, server.config)

this._config = resolved
this._state = new StateManager()
this._cache = new VitestCache(this.version)
Expand Down Expand Up @@ -272,14 +279,8 @@ export class Vitest {
const projects = await this.resolveWorkspace(cliOptions)
this.resolvedProjects = projects
this.projects = projects
const filters = toArray(resolved.project).map(s => wildcardPatternToRegExp(s))
if (filters.length > 0) {
this.projects = this.projects.filter(p =>
filters.some(pattern => pattern.test(p.name)),
)
if (!this.projects.length) {
throw new Error(`No projects matched the filter "${toArray(resolved.project).join('", "')}".`)
}
if (!this.projects.length) {
throw new Error(`No projects matched the filter "${toArray(resolved.project).join('", "')}".`)
}
if (!this.coreWorkspaceProject) {
this.coreWorkspaceProject = TestProject._createBasicProject(this)
Expand Down Expand Up @@ -397,8 +398,15 @@ export class Vitest {

this._workspaceConfigPath = workspaceConfigPath

// user doesn't have a workspace config, return default project
if (!workspaceConfigPath) {
return resolveBrowserWorkspace(this, new Set(), [this._ensureRootProject()])
// user can filter projects with --project flag, `getDefaultTestProject`
// returns the project only if it matches the filter
const project = getDefaultTestProject(this)
if (!project) {
return []
}
return resolveBrowserWorkspace(this, new Set(), [project])
}

const workspaceModule = await this.import<{
Expand Down Expand Up @@ -858,15 +866,15 @@ export class Vitest {
/** @internal */
async changeProjectName(pattern: string): Promise<void> {
if (pattern === '') {
delete this.configOverride.project
this.configOverride.project = undefined
this._projectFilters = []
}
else {
this.configOverride.project = pattern
this.configOverride.project = [pattern]
this._projectFilters = [wildcardPatternToRegExp(pattern)]
}

this.projects = this.resolvedProjects.filter(p => p.name === pattern)
const files = (await this.globTestSpecifications()).map(spec => spec.moduleId)
await this.rerunFiles(files, 'change project filter', pattern === '')
await this.vite.restart()
}

/** @internal */
Expand Down Expand Up @@ -1247,6 +1255,18 @@ export class Vitest {
onAfterSetServer(fn: OnServerRestartHandler): void {
this._onSetServer.push(fn)
}

/**
* Check if the project with a given name should be included.
* @internal
*/
_matchesProjectFilter(name: string): boolean {
// no filters applied, any project can be included
if (!this._projectFilters.length) {
return true
}
return this._projectFilters.some(filter => filter.test(name))
}
}

function assert(condition: unknown, property: string, name: string = property): asserts condition {
Expand Down
8 changes: 8 additions & 0 deletions packages/vitest/src/node/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ export class RangeLocationFilterProvidedError extends Error {
+ `are not supported. Consider specifying the exact line numbers of your tests.`)
}
}

export class VitestFilteredOutProjectError extends Error {
code = 'VITEST_FILTERED_OUT_PROJECT'

constructor() {
super('VITEST_FILTERED_OUT_PROJECT')
}
}
20 changes: 16 additions & 4 deletions packages/vitest/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ export async function VitestPlugin(

// store defines for globalThis to make them
// reassignable when running in worker in src/runtime/setup.ts
const defines: Record<string, any> = deleteDefineConfig(viteConfig);
const defines: Record<string, any> = deleteDefineConfig(viteConfig)

(options as ResolvedConfig).defines = defines
;(options as unknown as ResolvedConfig).defines = defines

let open: string | boolean | undefined = false

Expand Down Expand Up @@ -145,6 +145,11 @@ export async function VitestPlugin(
},
}

if (ctx.configOverride.project) {
// project filter was set by the user, so we need to filter the project
options.project = ctx.configOverride.project
}

config.customLogger = createViteLogger(
ctx.logger,
viteConfig.logLevel || 'warn',
Expand Down Expand Up @@ -217,9 +222,9 @@ export async function VitestPlugin(
return config
},
async configResolved(viteConfig) {
const viteConfigTest = (viteConfig.test as any) || {}
const viteConfigTest = (viteConfig.test as UserConfig) || {}
if (viteConfigTest.watch === false) {
viteConfigTest.run = true
;(viteConfigTest as any).run = true
}

if ('alias' in viteConfigTest) {
Expand Down Expand Up @@ -255,6 +260,13 @@ export async function VitestPlugin(
enumerable: false,
configurable: true,
})

const originalName = options.name
if (options.browser?.enabled && options.browser?.instances) {
options.browser.instances.forEach((instance) => {
instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser
})
}
},
configureServer: {
// runs after vite:import-analysis as it relies on `server` instance on Vite 5
Expand Down
3 changes: 1 addition & 2 deletions packages/vitest/src/node/plugins/publicConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ export async function resolveConfig(
// Reflect just to avoid type error
const updatedOptions = Reflect.get(config, '_vitest') as UserConfig
const vitestConfig = resolveVitestConfig(
'test',
vitest,
updatedOptions,
config,
vitest.logger,
)
return {
viteConfig: config,
Expand Down
34 changes: 32 additions & 2 deletions packages/vitest/src/node/plugins/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { deepMerge } from '@vitest/utils'
import { basename, dirname, relative, resolve } from 'pathe'
import { configDefaults } from '../../defaults'
import { generateScopedClassName } from '../../integrations/css/css-modules'
import { VitestFilteredOutProjectError } from '../errors'
import { createViteLogger, silenceImportViteIgnoreWarning } from '../viteLogger'
import { CoverageTransform } from './coverageTransform'
import { CSSEnablerPlugin } from './cssEnabler'
Expand Down Expand Up @@ -62,6 +63,35 @@ export function WorkspaceVitestPlugin(
}
}

// keep project names to potentially filter it out
const workspaceNames = [name]
if (viteConfig.test?.browser?.enabled) {
if (viteConfig.test.browser.name) {
const browser = viteConfig.test.browser.name
// vitest injects `instances` in this case later on
workspaceNames.push(name ? `${name} (${browser})` : browser)
}

viteConfig.test.browser.instances?.forEach((instance) => {
// every instance is a potential project
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser
workspaceNames.push(instance.name)
})
}

const filters = project.vitest.config.project
// if there is `--project=...` filter, check if any of the potential projects match
// if projects don't match, we ignore the test project altogether
// if some of them match, they will later be filtered again by `resolveWorkspace`
if (filters.length) {
const hasProject = workspaceNames.some((name) => {
return project.vitest._matchesProjectFilter(name)
})
if (!hasProject) {
throw new VitestFilteredOutProjectError()
}
}

const config: ViteConfig = {
root,
resolve: {
Expand Down Expand Up @@ -92,7 +122,7 @@ export function WorkspaceVitestPlugin(
fs: {
allow: resolveFsAllow(
project.vitest.config.root,
project.vitest.server.config.configFile,
project.vitest.vite.config.configFile,
),
},
},
Expand Down Expand Up @@ -138,7 +168,7 @@ export function WorkspaceVitestPlugin(
}
}
config.customLogger = createViteLogger(
project.logger,
project.vitest.logger,
viteConfig.logLevel || 'warn',
{
allowClearScreen: false,
Expand Down
3 changes: 1 addition & 2 deletions packages/vitest/src/node/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,13 +577,12 @@ export class TestProject {
/** @internal */
async _configureServer(options: UserConfig, server: ViteDevServer): Promise<void> {
this._config = resolveConfig(
this.vitest.mode,
this.vitest,
{
...options,
coverage: this.vitest.config.coverage,
},
server.config,
this.vitest.logger,
)
for (const _providedKey in this.config.provide) {
const providedKey = _providedKey as keyof ProvidedContext
Expand Down
3 changes: 1 addition & 2 deletions packages/vitest/src/node/stdin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Writable } from 'node:stream'
import type { Vitest } from './core'
import readline from 'node:readline'
import { getTests } from '@vitest/runner/utils'
import { toArray } from '@vitest/utils'
import { relative, resolve } from 'pathe'
import prompt from 'prompts'
import c from 'tinyrainbow'
Expand Down Expand Up @@ -182,7 +181,7 @@ export function registerConsoleShortcuts(
name: 'filter',
type: 'text',
message: 'Input a single project name',
initial: toArray(ctx.configOverride.project)[0] || '',
initial: ctx.config.project[0] || '',
},
])
on()
Expand Down
Loading

0 comments on commit dff4406

Please sign in to comment.