Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: filter projects eagerly during config resolution #7313

Merged
merged 24 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bd8a7df
fix: filter projects eagerly during config resolution
sheremet-va Jan 20, 2025
570e403
chore: cleanup
sheremet-va Jan 20, 2025
48209a6
chore: cleanup
sheremet-va Jan 20, 2025
a9dd4d7
chore: cleanup
sheremet-va Jan 21, 2025
8c28185
chore: improve error message
sheremet-va Jan 21, 2025
79b6d78
chore: fix glob
sheremet-va Jan 21, 2025
bad745f
fix: restart server on project name change
sheremet-va Jan 21, 2025
96a609f
fix: correctly set the name for browser instances
sheremet-va Jan 21, 2025
0fdaf8f
fix: correctly inject name
sheremet-va Jan 21, 2025
4eea6be
test: cleanup
sheremet-va Jan 21, 2025
e8f2251
chore: cleanup
sheremet-va Jan 21, 2025
dde22a6
chore: add comment
sheremet-va Jan 21, 2025
d0581fd
Merge branch 'main' of github.com:vitest-dev/vitest into fix/project-…
sheremet-va Jan 21, 2025
a30a5d4
chore: cleanup error msg
sheremet-va Jan 21, 2025
e5de674
test: oops
sheremet-va Jan 21, 2025
59e0b04
Merge branch 'main' of github.com:vitest-dev/vitest into fix/project-…
sheremet-va Jan 22, 2025
64b039d
refactor: keep project filter in a hidden vitest field
sheremet-va Jan 22, 2025
dfab725
chore: fix check
sheremet-va Jan 22, 2025
eeac4bd
test: cleanup
sheremet-va Jan 22, 2025
572a9d8
test: add workspace test for filtering
sheremet-va Jan 22, 2025
07331ba
fix: don't resolve `node:` and `internal:` in the error stack
sheremet-va Jan 22, 2025
4457ad0
test: add test for project change
sheremet-va Jan 22, 2025
86f9758
chore: cleanup
sheremet-va Jan 22, 2025
85dd916
Merge branch 'main' into fix/project-filter-eager
AriPerkkio Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading