From 874a121e97439f0d80b5a0060d6867592e7b5aaa Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 19 Aug 2024 10:08:54 +0200 Subject: [PATCH] feat(api): make spec into a class instead of a tuple (#6355) --- packages/browser/src/node/index.ts | 4 -- packages/vitest/src/api/setup.ts | 12 ++--- packages/vitest/src/node/core.ts | 45 +++++++++------- packages/vitest/src/node/pool.ts | 17 +++++- packages/vitest/src/node/pools/forks.ts | 8 +-- packages/vitest/src/node/pools/threads.ts | 4 +- packages/vitest/src/node/pools/typecheck.ts | 12 ++--- .../src/node/sequencers/BaseSequencer.ts | 6 +-- packages/vitest/src/node/spec.ts | 54 +++++++++++++++++++ packages/vitest/src/node/workspace.ts | 6 +++ packages/vitest/src/utils/test-helpers.ts | 4 +- test/core/test/sequencers.test.ts | 12 +++-- 12 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 packages/vitest/src/node/spec.ts diff --git a/packages/browser/src/node/index.ts b/packages/browser/src/node/index.ts index 791c1b3752d9..0aec26679174 100644 --- a/packages/browser/src/node/index.ts +++ b/packages/browser/src/node/index.ts @@ -16,10 +16,6 @@ export async function createBrowserServer( ) { const server = new BrowserServer(project, '/') - const root = project.config.root - - await project.ctx.packageInstaller.ensureInstalled('@vitest/browser', root) - const configPath = typeof configFile === 'string' ? configFile : false const vite = await createServer({ diff --git a/packages/vitest/src/api/setup.ts b/packages/vitest/src/api/setup.ts index b283c0b3a00c..5594397091d5 100644 --- a/packages/vitest/src/api/setup.ts +++ b/packages/vitest/src/api/setup.ts @@ -102,14 +102,14 @@ export function setup(ctx: Vitest, _server?: ViteDevServer) { return ctx.state.getUnhandledErrors() }, async getTestFiles() { - const spec = await ctx.globTestFiles() - return spec.map(([project, file, options]) => [ + const spec = await ctx.globTestSpecs() + return spec.map(spec => [ { - name: project.config.name, - root: project.config.root, + name: spec.project.config.name, + root: spec.project.config.root, }, - file, - options, + spec.moduleId, + { pool: spec.pool }, ]) }, }, diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index d091647a711b..ce12df6e1357 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -17,8 +17,8 @@ import { WebSocketReporter } from '../api/setup' import type { SerializedCoverageConfig } from '../runtime/config' import type { SerializedSpec } from '../runtime/types/utils' import type { ArgumentsType, OnServerRestartHandler, ProvidedContext, UserConsoleLog } from '../types/general' -import { createPool, getFilePoolName } from './pool' import type { ProcessPool, WorkspaceSpec } from './pool' +import { createPool, getFilePoolName } from './pool' import { createBenchmarkReporters, createReporters } from './reporters/utils' import { StateManager } from './state' import { resolveConfig } from './config/resolveConfig' @@ -437,7 +437,7 @@ export class Vitest { } } - private async getTestDependencies([project, filepath]: WorkspaceSpec, deps = new Set()) { + private async getTestDependencies(spec: WorkspaceSpec, deps = new Set()) { const addImports = async (project: WorkspaceProject, filepath: string) => { if (deps.has(filepath)) { return @@ -459,8 +459,8 @@ export class Vitest { })) } - await addImports(project, filepath) - deps.delete(filepath) + await addImports(spec.project.workspaceProject, spec.moduleId) + deps.delete(spec.moduleId) return deps } @@ -531,10 +531,10 @@ export class Vitest { for (const project of this.projects) { if (project.isTestFile(file)) { const pool = getFilePoolName(project, file) - specs.push([project, file, { pool }]) + specs.push(project.createSpec(file, pool)) } if (project.isTypecheckFile(file)) { - specs.push([project, file, { pool: 'typescript' }]) + specs.push(project.createSpec(file, 'typescript')) } } specs.forEach(spec => this.ensureSpecCached(spec)) @@ -542,7 +542,7 @@ export class Vitest { } async initializeGlobalSetup(paths: WorkspaceSpec[]) { - const projects = new Set(paths.map(([project]) => project)) + const projects = new Set(paths.map(spec => spec.project.workspaceProject)) const coreProject = this.getCoreWorkspaceProject() if (!projects.has(coreProject)) { projects.add(coreProject) @@ -566,16 +566,16 @@ export class Vitest { async runFiles(specs: WorkspaceSpec[], allTestsRun: boolean) { await this.initializeDistPath() - const filepaths = specs.map(([, file]) => file) + const filepaths = specs.map(spec => spec.moduleId) this.state.collectPaths(filepaths) await this.report('onPathsCollected', filepaths) await this.report('onSpecsCollected', specs.map( - ([project, file, options]) => + spec => [{ - name: project.config.name, - root: project.config.root, - }, file, options] satisfies SerializedSpec, + name: spec.project.config.name, + root: spec.project.config.root, + }, spec.moduleId, { pool: spec.pool }] satisfies SerializedSpec, )) // previous run @@ -618,7 +618,7 @@ export class Vitest { })() .finally(async () => { // can be duplicate files if different projects are using the same file - const files = Array.from(new Set(specs.map(([, p]) => p))) + const files = Array.from(new Set(specs.map(spec => spec.moduleId))) const coverage = await this.coverageProvider?.generateCoverage({ allTestsRun }) await this.report('onFinished', this.state.getFiles(files), this.state.getUnhandledErrors(), coverage) @@ -638,7 +638,7 @@ export class Vitest { async collectFiles(specs: WorkspaceSpec[]) { await this.initializeDistPath() - const filepaths = specs.map(([, file]) => file) + const filepaths = specs.map(spec => spec.moduleId) this.state.collectPaths(filepaths) // previous run @@ -709,7 +709,7 @@ export class Vitest { else { this.configOverride.project = pattern } this.projects = this.resolvedProjects.filter(p => p.getName() === pattern) - const files = (await this.globTestFiles()).map(([, file]) => file) + const files = (await this.globTestSpecs()).map(spec => spec.moduleId) await this.rerunFiles(files, 'change project filter') } @@ -1083,21 +1083,21 @@ export class Vitest { } public async getTestFilepaths() { - return this.globTestFiles().then(files => files.map(([, file]) => file)) + return this.globTestSpecs().then(specs => specs.map(spec => spec.moduleId)) } - public async globTestFiles(filters: string[] = []) { + public async globTestSpecs(filters: string[] = []) { const files: WorkspaceSpec[] = [] await Promise.all(this.projects.map(async (project) => { const { testFiles, typecheckTestFiles } = await project.globTestFiles(filters) testFiles.forEach((file) => { const pool = getFilePoolName(project, file) - const spec: WorkspaceSpec = [project, file, { pool }] + const spec = project.createSpec(file, pool) this.ensureSpecCached(spec) files.push(spec) }) typecheckTestFiles.forEach((file) => { - const spec: WorkspaceSpec = [project, file, { pool: 'typescript' }] + const spec = project.createSpec(file, 'typescript') this.ensureSpecCached(spec) files.push(spec) }) @@ -1105,6 +1105,13 @@ export class Vitest { return files } + /** + * @deprecated use globTestSpecs instead + */ + public async globTestFiles(filters: string[] = []) { + return this.globTestSpecs(filters) + } + private ensureSpecCached(spec: WorkspaceSpec) { const file = spec[1] const specs = this._cachedSpecs.get(file) || [] diff --git a/packages/vitest/src/node/pool.ts b/packages/vitest/src/node/pool.ts index 2e225e0e5053..109bb3948fe6 100644 --- a/packages/vitest/src/node/pool.ts +++ b/packages/vitest/src/node/pool.ts @@ -9,8 +9,23 @@ import { createVmThreadsPool } from './pools/vmThreads' import type { WorkspaceProject } from './workspace' import { createTypecheckPool } from './pools/typecheck' import { createVmForksPool } from './pools/vmForks' +import type { WorkspaceSpec as _WorkspaceSpec } from './spec' + +export type WorkspaceSpec = _WorkspaceSpec & [ + /** + * @deprecated use spec.project instead + */ + project: WorkspaceProject, + /** + * @deprecated use spec.moduleId instead + */ + file: string, + /** + * @deprecated use spec.pool instead + */ + options: { pool: Pool }, +] -export type WorkspaceSpec = [project: WorkspaceProject, testFile: string, options: { pool: Pool }] export type RunWithFiles = ( files: WorkspaceSpec[], invalidates?: string[] diff --git a/packages/vitest/src/node/pools/forks.ts b/packages/vitest/src/node/pools/forks.ts index b7185520b886..84bdd83f930e 100644 --- a/packages/vitest/src/node/pools/forks.ts +++ b/packages/vitest/src/node/pools/forks.ts @@ -166,17 +166,19 @@ export function createForksPool( } const workspaceMap = new Map() - for (const [project, file] of specs) { + for (const spec of specs) { + const file = spec.moduleId + const project = spec.project.workspaceProject const workspaceFiles = workspaceMap.get(file) ?? [] workspaceFiles.push(project) workspaceMap.set(file, workspaceFiles) } const singleFork = specs.filter( - ([project]) => project.config.poolOptions?.forks?.singleFork, + spec => spec.project.config.poolOptions?.forks?.singleFork, ) const multipleForks = specs.filter( - ([project]) => !project.config.poolOptions?.forks?.singleFork, + spec => !spec.project.config.poolOptions?.forks?.singleFork, ) if (multipleForks.length) { diff --git a/packages/vitest/src/node/pools/threads.ts b/packages/vitest/src/node/pools/threads.ts index 9613d47d198f..413914461712 100644 --- a/packages/vitest/src/node/pools/threads.ts +++ b/packages/vitest/src/node/pools/threads.ts @@ -170,10 +170,10 @@ export function createThreadsPool( } const singleThreads = specs.filter( - ([project]) => project.config.poolOptions?.threads?.singleThread, + spec => spec.project.config.poolOptions?.threads?.singleThread, ) const multipleThreads = specs.filter( - ([project]) => !project.config.poolOptions?.threads?.singleThread, + spec => !spec.project.config.poolOptions?.threads?.singleThread, ) if (multipleThreads.length) { diff --git a/packages/vitest/src/node/pools/typecheck.ts b/packages/vitest/src/node/pools/typecheck.ts index ca476443be43..5e0e4bdf45df 100644 --- a/packages/vitest/src/node/pools/typecheck.ts +++ b/packages/vitest/src/node/pools/typecheck.ts @@ -100,20 +100,20 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool { } async function collectTests(specs: WorkspaceSpec[]) { - const specsByProject = groupBy(specs, ([project]) => project.getName()) + const specsByProject = groupBy(specs, spec => spec.project.name) for (const name in specsByProject) { - const project = specsByProject[name][0][0] - const files = specsByProject[name].map(([_, file]) => file) - const checker = await createWorkspaceTypechecker(project, files) + const project = specsByProject[name][0].project + const files = specsByProject[name].map(spec => spec.moduleId) + const checker = await createWorkspaceTypechecker(project.workspaceProject, files) checker.setFiles(files) await checker.collectTests() - ctx.state.collectFiles(project, checker.getTestFiles()) + ctx.state.collectFiles(project.workspaceProject, checker.getTestFiles()) await ctx.report('onCollected') } } async function runTests(specs: WorkspaceSpec[]) { - const specsByProject = groupBy(specs, ([project]) => project.getName()) + const specsByProject = groupBy(specs, spec => spec.project.name) const promises: Promise[] = [] for (const name in specsByProject) { diff --git a/packages/vitest/src/node/sequencers/BaseSequencer.ts b/packages/vitest/src/node/sequencers/BaseSequencer.ts index f27bdd011e90..8b3715757355 100644 --- a/packages/vitest/src/node/sequencers/BaseSequencer.ts +++ b/packages/vitest/src/node/sequencers/BaseSequencer.ts @@ -21,7 +21,7 @@ export class BaseSequencer implements TestSequencer { const shardEnd = shardSize * index return [...files] .map((spec) => { - const fullPath = resolve(slash(config.root), slash(spec[1])) + const fullPath = resolve(slash(config.root), slash(spec.moduleId)) const specPath = fullPath?.slice(config.root.length) return { spec, @@ -37,8 +37,8 @@ export class BaseSequencer implements TestSequencer { public async sort(files: WorkspaceSpec[]): Promise { const cache = this.ctx.cache return [...files].sort((a, b) => { - const keyA = `${a[0].getName()}:${relative(this.ctx.config.root, a[1])}` - const keyB = `${b[0].getName()}:${relative(this.ctx.config.root, b[1])}` + const keyA = `${a.project.name}:${relative(this.ctx.config.root, a.moduleId)}` + const keyB = `${b.project.name}:${relative(this.ctx.config.root, b.moduleId)}` const aState = cache.getFileTestResults(keyA) const bState = cache.getFileTestResults(keyB) diff --git a/packages/vitest/src/node/spec.ts b/packages/vitest/src/node/spec.ts new file mode 100644 index 000000000000..45f5efd7128c --- /dev/null +++ b/packages/vitest/src/node/spec.ts @@ -0,0 +1,54 @@ +import type { TestProject } from './reported-workspace-project' +import type { Pool } from './types/pool-options' +import type { WorkspaceProject } from './workspace' + +export class WorkspaceSpec { + // backwards compatibility + /** + * @deprecated + */ + public readonly 0: WorkspaceProject + /** + * @deprecated + */ + public readonly 1: string + /** + * @deprecated + */ + public readonly 2: { pool: Pool } + + public readonly project: TestProject + public readonly moduleId: string + public readonly pool: Pool + // public readonly location: WorkspaceSpecLocation | undefined + + constructor( + workspaceProject: WorkspaceProject, + moduleId: string, + pool: Pool, + // location?: WorkspaceSpecLocation | undefined, + ) { + this[0] = workspaceProject + this[1] = moduleId + this[2] = { pool } + this.project = workspaceProject.testProject + this.moduleId = moduleId + this.pool = pool + // this.location = location + } + + /** + * for backwards compatibility + * @deprecated + */ + *[Symbol.iterator]() { + yield this.project.workspaceProject + yield this.moduleId + yield this.pool + } +} + +// interface WorkspaceSpecLocation { +// start: number +// end: number +// } diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 494c3147e4cf..be5aa71b8cbc 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -39,6 +39,8 @@ import { CoverageTransform } from './plugins/coverageTransform' import { serializeConfig } from './config/serializeConfig' import type { Vitest } from './core' import { TestProject } from './reported-workspace-project' +import { WorkspaceSpec } from './spec' +import type { WorkspaceSpec as DeprecatedWorkspaceSpec } from './pool' interface InitializeProjectOptions extends UserWorkspaceConfig { workspaceConfigPath: string @@ -151,6 +153,10 @@ export class WorkspaceProject { } } + public createSpec(moduleId: string, pool: string): DeprecatedWorkspaceSpec { + return new WorkspaceSpec(this, moduleId, pool) as DeprecatedWorkspaceSpec + } + async initializeGlobalSetup() { if (this._globalSetups) { return diff --git a/packages/vitest/src/utils/test-helpers.ts b/packages/vitest/src/utils/test-helpers.ts index fd24f3135911..dde813b43c8d 100644 --- a/packages/vitest/src/utils/test-helpers.ts +++ b/packages/vitest/src/utils/test-helpers.ts @@ -30,7 +30,9 @@ export async function groupFilesByEnv( files: Array, ) { const filesWithEnv = await Promise.all( - files.map(async ([project, file]) => { + files.map(async (spec) => { + const file = spec.moduleId + const project = spec.project.workspaceProject const code = await fs.readFile(file, 'utf-8') // 1. Check for control comments in the file diff --git a/test/core/test/sequencers.test.ts b/test/core/test/sequencers.test.ts index 74f13181551a..9a68e6252b98 100644 --- a/test/core/test/sequencers.test.ts +++ b/test/core/test/sequencers.test.ts @@ -1,9 +1,9 @@ -import type { Vitest } from 'vitest' +import type { Vitest, WorkspaceProject } from 'vitest/node' import { describe, expect, test, vi } from 'vitest' -import type { WorkspaceProject } from 'vitest/node' import { RandomSequencer } from '../../../packages/vitest/src/node/sequencers/RandomSequencer' import { BaseSequencer } from '../../../packages/vitest/src/node/sequencers/BaseSequencer' -import type { WorkspaceSpec } from '../../../packages/vitest/src/node/pool' +import { WorkspaceSpec } from '../../../packages/vitest/src/node/spec' +import type { WorkspaceSpec as DeprecatedWorkspaceSpec } from '../../../packages/vitest/src/node/pool' function buildCtx() { return { @@ -19,14 +19,16 @@ function buildCtx() { function buildWorkspace() { return { - getName: () => 'test', + testProject: { + name: 'test', + }, } as any as WorkspaceProject } const workspace = buildWorkspace() function workspaced(files: string[]) { - return files.map(file => [workspace, file, { pool: 'forks' }] satisfies WorkspaceSpec) + return files.map(file => new WorkspaceSpec(workspace, file, 'forks')) as DeprecatedWorkspaceSpec[] } describe('base sequencer', () => {