Skip to content

Commit d3ef4f2

Browse files
authored
perf(pool): resolve all environments first (#8759)
1 parent 08498f0 commit d3ef4f2

File tree

2 files changed

+34
-39
lines changed

2 files changed

+34
-39
lines changed

packages/vitest/src/node/pool.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { version as viteVersion } from 'vite'
1111
import { rootDir } from '../paths'
1212
import { isWindows } from '../utils/env'
1313
import { getWorkerMemoryLimit, stringToBytes } from '../utils/memory-limit'
14-
import { groupFilesByEnv } from '../utils/test-helpers'
14+
import { getSpecificationsEnvironments } from '../utils/test-helpers'
1515
import { createBrowserPool } from './pools/browser'
1616
import { Pool } from './pools/pool'
1717

@@ -86,6 +86,7 @@ export function createPool(ctx: Vitest): ProcessPool {
8686
let workerId = 0
8787

8888
const sorted = await sequencer.sort(specs)
89+
const environments = await getSpecificationsEnvironments(specs)
8990
const groups = groupSpecs(sorted)
9091

9192
for (const group of groups) {
@@ -108,16 +109,18 @@ export function createPool(ctx: Vitest): ProcessPool {
108109
continue
109110
}
110111

111-
const byEnv = await groupFilesByEnv(specs)
112-
const env = Object.values(byEnv)[0][0]
112+
const environment = environments.get(specs[0])!
113+
if (!environment) {
114+
throw new Error(`Cannot find the environment. This is a bug in Vitest.`)
115+
}
113116

114117
taskGroup.push({
115118
context: {
116119
pool,
117120
config: project.serializedConfig,
118121
files: specs.map(spec => ({ filepath: spec.moduleId, testLocations: spec.testLines })),
119122
invalidates,
120-
environment: env.environment,
123+
environment,
121124
projectName: project.name,
122125
providedContext: project.getProvidedContext(),
123126
workerId: workerId++,
@@ -313,7 +316,7 @@ function groupSpecs(specs: TestSpecification[]) {
313316

314317
// Tests in a single group are executed with `maxWorkers` parallelism.
315318
// Next group starts running after previous finishes - allows real sequential tests.
316-
interface Groups { specs: SpecsForRunner[]; maxWorkers: number }
319+
interface Groups { specs: SpecsForRunner[]; maxWorkers: number; typecheck?: boolean }
317320
const groups: Groups[] = []
318321

319322
// Files without file parallelism but without explicit sequence.groupOrder
@@ -349,11 +352,17 @@ function groupSpecs(specs: TestSpecification[]) {
349352
groups[order].specs.push([spec])
350353
})
351354

352-
for (const project in typechecks) {
353-
const order = Math.max(0, ...groups.keys()) + 1
355+
let order = Math.max(0, ...groups.keys()) + 1
356+
357+
for (const projectName in typechecks) {
358+
const maxWorkers = resolveMaxWorkers(typechecks[projectName][0].project)
359+
const previous = groups[order - 1]
360+
if (previous && previous.typecheck && maxWorkers !== previous.maxWorkers) {
361+
order += 1
362+
}
354363

355-
groups[order] ||= { specs: [], maxWorkers: resolveMaxWorkers(typechecks[project][0].project) }
356-
groups[order].specs.push(typechecks[project])
364+
groups[order] ||= { specs: [], maxWorkers, typecheck: true }
365+
groups[order].specs.push(typechecks[projectName])
357366
}
358367

359368
if (sequential.specs.length) {
Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
1-
import type { TestProject } from '../node/project'
21
import type { TestSpecification } from '../node/spec'
32
import type { EnvironmentOptions, VitestEnvironment } from '../node/types/config'
43
import type { ContextTestEnvironment } from '../types/worker'
54
import { promises as fs } from 'node:fs'
6-
import { groupBy } from './base'
75

8-
export const envsOrder: string[] = ['node', 'jsdom', 'happy-dom', 'edge-runtime']
9-
10-
export interface FileByEnv {
11-
file: string
12-
env: VitestEnvironment
13-
envOptions: EnvironmentOptions | null
14-
}
15-
16-
export async function groupFilesByEnv(
17-
files: Array<TestSpecification>,
18-
): Promise<Record<string, {
19-
file: { filepath: string; testLocations: number[] | undefined }
20-
project: TestProject
21-
environment: ContextTestEnvironment
22-
}[]>> {
23-
const filesWithEnv = await Promise.all(
24-
files.map(async ({ moduleId: filepath, project, testLines }) => {
25-
const code = await fs.readFile(filepath, 'utf-8')
6+
export async function getSpecificationsEnvironments(
7+
specifications: Array<TestSpecification>,
8+
): Promise<WeakMap<TestSpecification, ContextTestEnvironment>> {
9+
const environments = new WeakMap<TestSpecification, ContextTestEnvironment>()
10+
const cache = new Map<string, string>()
11+
await Promise.all(
12+
specifications.map(async (spec) => {
13+
const { moduleId: filepath, project } = spec
14+
// reuse if projects have the same test files
15+
let code = cache.get(filepath)
16+
if (!code) {
17+
code = await fs.readFile(filepath, 'utf-8')
18+
cache.set(filepath, code)
19+
}
2620

2721
// 1. Check for control comments in the file
2822
let env = code.match(/@(?:vitest|jest)-environment\s+([\w-]+)\b/)?.[1]
@@ -43,16 +37,8 @@ export async function groupFilesByEnv(
4337
? ({ [envKey]: envOptions } as EnvironmentOptions)
4438
: null,
4539
}
46-
return {
47-
file: {
48-
filepath,
49-
testLocations: testLines,
50-
},
51-
project,
52-
environment,
53-
}
40+
environments.set(spec, environment)
5441
}),
5542
)
56-
57-
return groupBy(filesWithEnv, ({ environment }) => environment.name)
43+
return environments
5844
}

0 commit comments

Comments
 (0)