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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 10 additions & 11 deletions
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

Lines changed: 38 additions & 18 deletions
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 16 additions & 4 deletions
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

Lines changed: 1 addition & 2 deletions
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

Lines changed: 32 additions & 2 deletions
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

Lines changed: 1 addition & 2 deletions
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

Lines changed: 1 addition & 2 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy