mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
perf: pass testfiles at once when --no-isolate --maxWorkers=1 (#8835)
This commit is contained in:
parent
7ee283c965
commit
584aa7148d
3
.gitignore
vendored
3
.gitignore
vendored
@ -16,6 +16,7 @@ dist
|
||||
.vite-node
|
||||
ltex*
|
||||
.DS_Store
|
||||
.zed
|
||||
bench/test/*/*/
|
||||
**/bench.json
|
||||
**/browser/browser.json
|
||||
@ -35,4 +36,4 @@ test/browser/html/
|
||||
test/core/html/
|
||||
.vitest-attachments
|
||||
explainFiles.txt
|
||||
.vitest-dump
|
||||
.vitest-dump
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { Awaitable } from '@vitest/utils'
|
||||
import type { ContextTestEnvironment } from '../types/worker'
|
||||
import type { Vitest } from './core'
|
||||
import type { PoolTask } from './pools/types'
|
||||
import type { TestProject } from './project'
|
||||
@ -87,7 +88,7 @@ export function createPool(ctx: Vitest): ProcessPool {
|
||||
|
||||
const sorted = await sequencer.sort(specs)
|
||||
const environments = await getSpecificationsEnvironments(specs)
|
||||
const groups = groupSpecs(sorted)
|
||||
const groups = groupSpecs(sorted, environments)
|
||||
|
||||
const projectEnvs = new WeakMap<TestProject, Partial<NodeJS.ProcessEnv>>()
|
||||
const projectExecArgvs = new WeakMap<TestProject, string[]>()
|
||||
@ -330,9 +331,8 @@ function getMemoryLimit(config: ResolvedConfig, pool: string) {
|
||||
return null
|
||||
}
|
||||
|
||||
function groupSpecs(specs: TestSpecification[]) {
|
||||
// Test files are passed to test runner one at a time, except Typechecker.
|
||||
// TODO: Should non-isolated test files be passed to test runner all at once?
|
||||
function groupSpecs(specs: TestSpecification[], environments: Awaited<ReturnType<typeof getSpecificationsEnvironments>>) {
|
||||
// Test files are passed to test runner one at a time, except for Typechecker or when "--maxWorker=1 --no-isolate"
|
||||
type SpecsForRunner = TestSpecification[]
|
||||
|
||||
// Tests in a single group are executed with `maxWorkers` parallelism.
|
||||
@ -346,6 +346,43 @@ function groupSpecs(specs: TestSpecification[]) {
|
||||
// Type tests are run in a single group, per project
|
||||
const typechecks: Record<string, TestSpecification[]> = {}
|
||||
|
||||
const serializedEnvironmentOptions = new Map<ContextTestEnvironment, string>()
|
||||
|
||||
function getSerializedOptions(env: ContextTestEnvironment) {
|
||||
const options = serializedEnvironmentOptions.get(env)
|
||||
|
||||
if (options) {
|
||||
return options
|
||||
}
|
||||
|
||||
const serialized = JSON.stringify(env.options)
|
||||
serializedEnvironmentOptions.set(env, serialized)
|
||||
return serialized
|
||||
}
|
||||
|
||||
function isEqualEnvironments(a: TestSpecification, b: TestSpecification) {
|
||||
const aEnv = environments.get(a)
|
||||
const bEnv = environments.get(b)
|
||||
|
||||
if (!aEnv && !bEnv) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!aEnv || !bEnv || aEnv.name !== bEnv.name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!aEnv.options && !bEnv.options) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!aEnv.options || !bEnv.options) {
|
||||
return false
|
||||
}
|
||||
|
||||
return getSerializedOptions(aEnv) === getSerializedOptions(bEnv)
|
||||
}
|
||||
|
||||
specs.forEach((spec) => {
|
||||
if (spec.pool === 'typescript') {
|
||||
typechecks[spec.project.name] ||= []
|
||||
@ -361,6 +398,7 @@ function groupSpecs(specs: TestSpecification[]) {
|
||||
}
|
||||
|
||||
const maxWorkers = resolveMaxWorkers(spec.project)
|
||||
const isolate = spec.project.config.isolate
|
||||
groups[order] ||= { specs: [], maxWorkers }
|
||||
|
||||
// Multiple projects with different maxWorkers but same groupId
|
||||
@ -370,6 +408,15 @@ function groupSpecs(specs: TestSpecification[]) {
|
||||
throw new Error(`Projects "${last}" and "${spec.project.name}" have different 'maxWorkers' but same 'sequence.groupId'.\nProvide unique 'sequence.groupId' for them.`)
|
||||
}
|
||||
|
||||
// Non-isolated single worker can receive all files at once
|
||||
if (isolate === false && maxWorkers === 1) {
|
||||
const previous = groups[order].specs[0]?.[0]
|
||||
|
||||
if (previous && previous.project.name === spec.project.name && isEqualEnvironments(spec, previous)) {
|
||||
return groups[order].specs[0].push(spec)
|
||||
}
|
||||
}
|
||||
|
||||
groups[order].specs.push([spec])
|
||||
})
|
||||
|
||||
|
||||
3
test/config/fixtures/pool/a.test.ts
Normal file
3
test/config/fixtures/pool/a.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { test } from "vitest"
|
||||
|
||||
test("a", () => { })
|
||||
3
test/config/fixtures/pool/b.test.ts
Normal file
3
test/config/fixtures/pool/b.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { test } from "vitest"
|
||||
|
||||
test("b", () => { })
|
||||
3
test/config/fixtures/pool/c.test.ts
Normal file
3
test/config/fixtures/pool/c.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { test } from "vitest"
|
||||
|
||||
test("c", () => { })
|
||||
6
test/config/fixtures/pool/print-testfiles.test.ts
Normal file
6
test/config/fixtures/pool/print-testfiles.test.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { test } from 'vitest'
|
||||
|
||||
test('print config', () => {
|
||||
// @ts-expect-error -- internal
|
||||
console.log(JSON.stringify(globalThis.__vitest_worker__.ctx.files.map(file => file.filepath)))
|
||||
})
|
||||
@ -1,7 +1,8 @@
|
||||
import type { SerializedConfig } from 'vitest'
|
||||
import type { TestUserConfig } from 'vitest/node'
|
||||
import { normalize } from 'pathe'
|
||||
import { assert, describe, expect, test } from 'vitest'
|
||||
import { runVitest } from '../../test-utils'
|
||||
import { runVitest, StableTestFileOrderSorter } from '../../test-utils'
|
||||
|
||||
describe.each(['forks', 'threads', 'vmThreads', 'vmForks'])('%s', async (pool) => {
|
||||
test('resolves top-level pool', async () => {
|
||||
@ -51,8 +52,39 @@ test('project level pool options overwrites top-level', async () => {
|
||||
expect(config.fileParallelism).toBe(false)
|
||||
})
|
||||
|
||||
async function getConfig(options: Partial<TestUserConfig>, cliOptions: Partial<TestUserConfig> = {}) {
|
||||
let config: SerializedConfig | undefined
|
||||
test('isolated single worker pool receives single testfile at once', async () => {
|
||||
const files = await getConfig<string[]>({
|
||||
maxWorkers: 1,
|
||||
isolate: true,
|
||||
sequence: { sequencer: StableTestFileOrderSorter },
|
||||
}, { include: ['print-testfiles.test.ts', 'a.test.ts', 'b.test.ts', 'c.test.ts'] })
|
||||
|
||||
expect(files.map(normalizeFilename)).toMatchInlineSnapshot(`
|
||||
[
|
||||
"<process-cwd>/fixtures/pool/print-testfiles.test.ts",
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
test('non-isolated single worker pool receives all testfiles at once', async () => {
|
||||
const files = await getConfig<string[]>({
|
||||
maxWorkers: 1,
|
||||
isolate: false,
|
||||
sequence: { sequencer: StableTestFileOrderSorter },
|
||||
}, { include: ['print-testfiles.test.ts', 'a.test.ts', 'b.test.ts', 'c.test.ts'] })
|
||||
|
||||
expect(files.map(normalizeFilename)).toMatchInlineSnapshot(`
|
||||
[
|
||||
"<process-cwd>/fixtures/pool/a.test.ts",
|
||||
"<process-cwd>/fixtures/pool/b.test.ts",
|
||||
"<process-cwd>/fixtures/pool/c.test.ts",
|
||||
"<process-cwd>/fixtures/pool/print-testfiles.test.ts",
|
||||
]
|
||||
`)
|
||||
})
|
||||
|
||||
async function getConfig<T = SerializedConfig>(options: Partial<TestUserConfig>, cliOptions: Partial<TestUserConfig> = {}): Promise<T> {
|
||||
let config: T | undefined
|
||||
|
||||
await runVitest({
|
||||
root: './fixtures/pool',
|
||||
@ -66,3 +98,8 @@ async function getConfig(options: Partial<TestUserConfig>, cliOptions: Partial<T
|
||||
assert(config)
|
||||
return config
|
||||
}
|
||||
|
||||
function normalizeFilename(filename: string) {
|
||||
return normalize(filename)
|
||||
.replace(normalize(process.cwd()), '<process-cwd>')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user