Skip to content

Commit dff4406

Browse files
fix: filter projects eagerly during config resolution (#7313)
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
1 parent ed9aeba commit dff4406

File tree

21 files changed

+359
-84
lines changed

21 files changed

+359
-84
lines changed

packages/browser/src/node/pool.ts

+3
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export function createBrowserPool(vitest: Vitest): ProcessPool {
133133
}
134134
await project._initBrowserProvider()
135135

136+
if (!project.browser) {
137+
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.`)
138+
}
136139
await executeTests(method, project, files)
137140
}
138141
}

packages/utils/src/source-map.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ export function parseSingleV8Stack(raw: string): ParsedStack | null {
167167
}
168168

169169
// normalize Windows path (\ -> /)
170-
file = resolve(file)
170+
file = file.startsWith('node:') || file.startsWith('internal:')
171+
? file
172+
: resolve(file)
171173

172174
if (method) {
173175
method = method.replace(/__vite_ssr_import_\d+__\./g, '')

packages/vitest/src/node/config/resolveConfig.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import type { ResolvedConfig as ResolvedViteConfig } from 'vite'
2-
import type { Logger } from '../logger'
2+
import type { Vitest } from '../core'
33
import type { BenchmarkBuiltinReporters } from '../reporters'
44
import type {
55
ApiConfig,
66
ResolvedConfig,
77
UserConfig,
8-
VitestRunMode,
98
} from '../types/config'
109
import type { BaseCoverageOptions, CoverageReporterWithOptions } from '../types/coverage'
1110
import type { BuiltinPool, ForksOptions, PoolOptions, ThreadsOptions } from '../types/pool-options'
@@ -20,7 +19,6 @@ import {
2019
extraInlineDeps,
2120
} from '../../constants'
2221
import { benchmarkConfigDefaults, configDefaults } from '../../defaults'
23-
import { wildcardPatternToRegExp } from '../../utils/base'
2422
import { isCI, stdProvider } from '../../utils/env'
2523
import { getWorkersCountByPercentage } from '../../utils/workers'
2624
import { VitestCache } from '../cache'
@@ -111,11 +109,12 @@ function resolveInlineWorkerOption(value: string | number): number {
111109
}
112110

113111
export function resolveConfig(
114-
mode: VitestRunMode,
112+
vitest: Vitest,
115113
options: UserConfig,
116114
viteConfig: ResolvedViteConfig,
117-
logger: Logger,
118115
): ResolvedConfig {
116+
const mode = vitest.mode
117+
const logger = vitest.logger
119118
if (options.dom) {
120119
if (
121120
viteConfig.test?.environment != null
@@ -142,6 +141,7 @@ export function resolveConfig(
142141
mode,
143142
} as any as ResolvedConfig
144143

144+
resolved.project = toArray(resolved.project)
145145
resolved.provide ??= {}
146146

147147
const inspector = resolved.inspect || resolved.inspectBrk
@@ -256,15 +256,15 @@ export function resolveConfig(
256256
}
257257
}
258258

259-
const playwrightChromiumOnly = isPlaywrightChromiumOnly(resolved)
259+
const playwrightChromiumOnly = isPlaywrightChromiumOnly(vitest, resolved)
260260

261261
// Browser-mode "Playwright + Chromium" only features:
262262
if (browser.enabled && !playwrightChromiumOnly) {
263263
const browserConfig = {
264264
browser: {
265265
provider: browser.provider,
266266
name: browser.name,
267-
instances: browser.instances,
267+
instances: browser.instances?.map(i => ({ browser: i.browser })),
268268
},
269269
}
270270

@@ -469,7 +469,7 @@ export function resolveConfig(
469469
resolved.forceRerunTriggers.push(...resolved.snapshotSerializers)
470470

471471
if (options.resolveSnapshotPath) {
472-
delete (resolved as UserConfig).resolveSnapshotPath
472+
delete (resolved as any).resolveSnapshotPath
473473
}
474474

475475
resolved.pool ??= 'threads'
@@ -897,7 +897,7 @@ export function resolveCoverageReporters(configReporters: NonNullable<BaseCovera
897897
return resolvedReporters
898898
}
899899

900-
function isPlaywrightChromiumOnly(config: ResolvedConfig) {
900+
function isPlaywrightChromiumOnly(vitest: Vitest, config: ResolvedConfig) {
901901
const browser = config.browser
902902
if (!browser || browser.provider !== 'playwright' || !browser.enabled) {
903903
return false
@@ -908,11 +908,10 @@ function isPlaywrightChromiumOnly(config: ResolvedConfig) {
908908
if (!browser.instances) {
909909
return false
910910
}
911-
const filteredProjects = toArray(config.project).map(p => wildcardPatternToRegExp(p))
912911
for (const instance of browser.instances) {
913912
const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser)
914913
// browser config is filtered out
915-
if (filteredProjects.length && !filteredProjects.every(p => p.test(name))) {
914+
if (!vitest._matchesProjectFilter(name)) {
916915
continue
917916
}
918917
if (instance.browser !== 'chromium') {

packages/vitest/src/node/core.ts

+38-18
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { VitestSpecifications } from './specifications'
4040
import { StateManager } from './state'
4141
import { TestRun } from './test-run'
4242
import { VitestWatcher } from './watcher'
43-
import { resolveBrowserWorkspace, resolveWorkspace } from './workspace/resolveWorkspace'
43+
import { getDefaultTestProject, resolveBrowserWorkspace, resolveWorkspace } from './workspace/resolveWorkspace'
4444

4545
const WATCHER_DEBOUNCE = 100
4646

@@ -90,14 +90,19 @@ export class Vitest {
9090
/** @internal */ closingPromise?: Promise<void>
9191
/** @internal */ isCancelling = false
9292
/** @internal */ coreWorkspaceProject: TestProject | undefined
93-
/** @internal */ resolvedProjects: TestProject[] = []
93+
/**
94+
* @internal
95+
* @deprecated
96+
*/
97+
resolvedProjects: TestProject[] = []
9498
/** @internal */ _browserLastPort = defaultBrowserPort
9599
/** @internal */ _browserSessions = new BrowserSessions()
96100
/** @internal */ _options: UserConfig = {}
97101
/** @internal */ reporters: Reporter[] = undefined!
98102
/** @internal */ vitenode: ViteNodeServer = undefined!
99103
/** @internal */ runner: ViteNodeRunner = undefined!
100104
/** @internal */ _testRun: TestRun = undefined!
105+
/** @internal */ _projectFilters: RegExp[] = []
101106

102107
private isFirstRun = true
103108
private restartsCount = 0
@@ -211,9 +216,11 @@ export class Vitest {
211216
this.specifications.clearCache()
212217
this._onUserTestsRerun = []
213218

214-
const resolved = resolveConfig(this.mode, options, server.config, this.logger)
215-
219+
this._projectFilters = toArray(options.project || []).map(project => wildcardPatternToRegExp(project))
216220
this._vite = server
221+
222+
const resolved = resolveConfig(this, options, server.config)
223+
217224
this._config = resolved
218225
this._state = new StateManager()
219226
this._cache = new VitestCache(this.version)
@@ -272,14 +279,8 @@ export class Vitest {
272279
const projects = await this.resolveWorkspace(cliOptions)
273280
this.resolvedProjects = projects
274281
this.projects = projects
275-
const filters = toArray(resolved.project).map(s => wildcardPatternToRegExp(s))
276-
if (filters.length > 0) {
277-
this.projects = this.projects.filter(p =>
278-
filters.some(pattern => pattern.test(p.name)),
279-
)
280-
if (!this.projects.length) {
281-
throw new Error(`No projects matched the filter "${toArray(resolved.project).join('", "')}".`)
282-
}
282+
if (!this.projects.length) {
283+
throw new Error(`No projects matched the filter "${toArray(resolved.project).join('", "')}".`)
283284
}
284285
if (!this.coreWorkspaceProject) {
285286
this.coreWorkspaceProject = TestProject._createBasicProject(this)
@@ -397,8 +398,15 @@ export class Vitest {
397398

398399
this._workspaceConfigPath = workspaceConfigPath
399400

401+
// user doesn't have a workspace config, return default project
400402
if (!workspaceConfigPath) {
401-
return resolveBrowserWorkspace(this, new Set(), [this._ensureRootProject()])
403+
// user can filter projects with --project flag, `getDefaultTestProject`
404+
// returns the project only if it matches the filter
405+
const project = getDefaultTestProject(this)
406+
if (!project) {
407+
return []
408+
}
409+
return resolveBrowserWorkspace(this, new Set(), [project])
402410
}
403411

404412
const workspaceModule = await this.import<{
@@ -858,15 +866,15 @@ export class Vitest {
858866
/** @internal */
859867
async changeProjectName(pattern: string): Promise<void> {
860868
if (pattern === '') {
861-
delete this.configOverride.project
869+
this.configOverride.project = undefined
870+
this._projectFilters = []
862871
}
863872
else {
864-
this.configOverride.project = pattern
873+
this.configOverride.project = [pattern]
874+
this._projectFilters = [wildcardPatternToRegExp(pattern)]
865875
}
866876

867-
this.projects = this.resolvedProjects.filter(p => p.name === pattern)
868-
const files = (await this.globTestSpecifications()).map(spec => spec.moduleId)
869-
await this.rerunFiles(files, 'change project filter', pattern === '')
877+
await this.vite.restart()
870878
}
871879

872880
/** @internal */
@@ -1247,6 +1255,18 @@ export class Vitest {
12471255
onAfterSetServer(fn: OnServerRestartHandler): void {
12481256
this._onSetServer.push(fn)
12491257
}
1258+
1259+
/**
1260+
* Check if the project with a given name should be included.
1261+
* @internal
1262+
*/
1263+
_matchesProjectFilter(name: string): boolean {
1264+
// no filters applied, any project can be included
1265+
if (!this._projectFilters.length) {
1266+
return true
1267+
}
1268+
return this._projectFilters.some(filter => filter.test(name))
1269+
}
12501270
}
12511271

12521272
function assert(condition: unknown, property: string, name: string = property): asserts condition {

packages/vitest/src/node/errors.ts

+8
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,11 @@ export class RangeLocationFilterProvidedError extends Error {
3939
+ `are not supported. Consider specifying the exact line numbers of your tests.`)
4040
}
4141
}
42+
43+
export class VitestFilteredOutProjectError extends Error {
44+
code = 'VITEST_FILTERED_OUT_PROJECT'
45+
46+
constructor() {
47+
super('VITEST_FILTERED_OUT_PROJECT')
48+
}
49+
}

packages/vitest/src/node/plugins/index.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ export async function VitestPlugin(
6363

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

68-
(options as ResolvedConfig).defines = defines
68+
;(options as unknown as ResolvedConfig).defines = defines
6969

7070
let open: string | boolean | undefined = false
7171

@@ -145,6 +145,11 @@ export async function VitestPlugin(
145145
},
146146
}
147147

148+
if (ctx.configOverride.project) {
149+
// project filter was set by the user, so we need to filter the project
150+
options.project = ctx.configOverride.project
151+
}
152+
148153
config.customLogger = createViteLogger(
149154
ctx.logger,
150155
viteConfig.logLevel || 'warn',
@@ -217,9 +222,9 @@ export async function VitestPlugin(
217222
return config
218223
},
219224
async configResolved(viteConfig) {
220-
const viteConfigTest = (viteConfig.test as any) || {}
225+
const viteConfigTest = (viteConfig.test as UserConfig) || {}
221226
if (viteConfigTest.watch === false) {
222-
viteConfigTest.run = true
227+
;(viteConfigTest as any).run = true
223228
}
224229

225230
if ('alias' in viteConfigTest) {
@@ -255,6 +260,13 @@ export async function VitestPlugin(
255260
enumerable: false,
256261
configurable: true,
257262
})
263+
264+
const originalName = options.name
265+
if (options.browser?.enabled && options.browser?.instances) {
266+
options.browser.instances.forEach((instance) => {
267+
instance.name ??= originalName ? `${originalName} (${instance.browser})` : instance.browser
268+
})
269+
}
258270
},
259271
configureServer: {
260272
// runs after vite:import-analysis as it relies on `server` instance on Vite 5

packages/vitest/src/node/plugins/publicConfig.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ export async function resolveConfig(
4545
// Reflect just to avoid type error
4646
const updatedOptions = Reflect.get(config, '_vitest') as UserConfig
4747
const vitestConfig = resolveVitestConfig(
48-
'test',
48+
vitest,
4949
updatedOptions,
5050
config,
51-
vitest.logger,
5251
)
5352
return {
5453
viteConfig: config,

packages/vitest/src/node/plugins/workspace.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { deepMerge } from '@vitest/utils'
66
import { basename, dirname, relative, resolve } from 'pathe'
77
import { configDefaults } from '../../defaults'
88
import { generateScopedClassName } from '../../integrations/css/css-modules'
9+
import { VitestFilteredOutProjectError } from '../errors'
910
import { createViteLogger, silenceImportViteIgnoreWarning } from '../viteLogger'
1011
import { CoverageTransform } from './coverageTransform'
1112
import { CSSEnablerPlugin } from './cssEnabler'
@@ -62,6 +63,35 @@ export function WorkspaceVitestPlugin(
6263
}
6364
}
6465

66+
// keep project names to potentially filter it out
67+
const workspaceNames = [name]
68+
if (viteConfig.test?.browser?.enabled) {
69+
if (viteConfig.test.browser.name) {
70+
const browser = viteConfig.test.browser.name
71+
// vitest injects `instances` in this case later on
72+
workspaceNames.push(name ? `${name} (${browser})` : browser)
73+
}
74+
75+
viteConfig.test.browser.instances?.forEach((instance) => {
76+
// every instance is a potential project
77+
instance.name ??= name ? `${name} (${instance.browser})` : instance.browser
78+
workspaceNames.push(instance.name)
79+
})
80+
}
81+
82+
const filters = project.vitest.config.project
83+
// if there is `--project=...` filter, check if any of the potential projects match
84+
// if projects don't match, we ignore the test project altogether
85+
// if some of them match, they will later be filtered again by `resolveWorkspace`
86+
if (filters.length) {
87+
const hasProject = workspaceNames.some((name) => {
88+
return project.vitest._matchesProjectFilter(name)
89+
})
90+
if (!hasProject) {
91+
throw new VitestFilteredOutProjectError()
92+
}
93+
}
94+
6595
const config: ViteConfig = {
6696
root,
6797
resolve: {
@@ -92,7 +122,7 @@ export function WorkspaceVitestPlugin(
92122
fs: {
93123
allow: resolveFsAllow(
94124
project.vitest.config.root,
95-
project.vitest.server.config.configFile,
125+
project.vitest.vite.config.configFile,
96126
),
97127
},
98128
},
@@ -138,7 +168,7 @@ export function WorkspaceVitestPlugin(
138168
}
139169
}
140170
config.customLogger = createViteLogger(
141-
project.logger,
171+
project.vitest.logger,
142172
viteConfig.logLevel || 'warn',
143173
{
144174
allowClearScreen: false,

packages/vitest/src/node/project.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -577,13 +577,12 @@ export class TestProject {
577577
/** @internal */
578578
async _configureServer(options: UserConfig, server: ViteDevServer): Promise<void> {
579579
this._config = resolveConfig(
580-
this.vitest.mode,
580+
this.vitest,
581581
{
582582
...options,
583583
coverage: this.vitest.config.coverage,
584584
},
585585
server.config,
586-
this.vitest.logger,
587586
)
588587
for (const _providedKey in this.config.provide) {
589588
const providedKey = _providedKey as keyof ProvidedContext

packages/vitest/src/node/stdin.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Writable } from 'node:stream'
22
import type { Vitest } from './core'
33
import readline from 'node:readline'
44
import { getTests } from '@vitest/runner/utils'
5-
import { toArray } from '@vitest/utils'
65
import { relative, resolve } from 'pathe'
76
import prompt from 'prompts'
87
import c from 'tinyrainbow'
@@ -182,7 +181,7 @@ export function registerConsoleShortcuts(
182181
name: 'filter',
183182
type: 'text',
184183
message: 'Input a single project name',
185-
initial: toArray(ctx.configOverride.project)[0] || '',
184+
initial: ctx.config.project[0] || '',
186185
},
187186
])
188187
on()

0 commit comments

Comments
 (0)