Skip to content

Commit d1a1df0

Browse files
authored
feat: add sequence.groupOrder option (#7852)
1 parent 05b7dd9 commit d1a1df0

File tree

7 files changed

+210
-43
lines changed

7 files changed

+210
-43
lines changed

docs/config/index.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2034,7 +2034,7 @@ export default defineConfig({
20342034

20352035
### sequence
20362036

2037-
- **Type**: `{ sequencer?, shuffle?, seed?, hooks?, setupFiles? }`
2037+
- **Type**: `{ sequencer?, shuffle?, seed?, hooks?, setupFiles?, groupOrder }`
20382038

20392039
Options for how tests should be sorted.
20402040

@@ -2053,6 +2053,71 @@ A custom class that defines methods for sharding and sorting. You can extend `Ba
20532053

20542054
Sharding is happening before sorting, and only if `--shard` option is provided.
20552055

2056+
If [`sequencer.groupOrder`](#groupOrder) is specified, the sequencer will be called once for each group and pool.
2057+
2058+
#### groupOrder <Version>3.2.0</Version> {#groupOrder}
2059+
2060+
- **Type:** `number`
2061+
- **Default:** `0`
2062+
2063+
Controls the order in which this project runs its tests when using multiple [projects](/guide/projects).
2064+
2065+
- Projects with the same group order number will run together, and groups are run from lowest to highest.
2066+
- If you don’t set this option, all projects run in parallel.
2067+
- If several projects use the same group order, they will run at the same time.
2068+
2069+
This setting only affects the order in which projects run, not the order of tests within a project.
2070+
To control test isolation or the order of tests inside a project, use the [`isolate`](#isolate) and [`sequence.sequencer`](#sequence-sequencer) options.
2071+
2072+
::: details Example
2073+
Consider this example:
2074+
2075+
```ts
2076+
import { defineConfig } from 'vitest/config'
2077+
2078+
export default defineConfig({
2079+
test: {
2080+
projects: [
2081+
{
2082+
test: {
2083+
name: 'slow',
2084+
sequence: {
2085+
groupOrder: 0,
2086+
},
2087+
},
2088+
},
2089+
{
2090+
test: {
2091+
name: 'fast',
2092+
sequence: {
2093+
groupOrder: 0,
2094+
},
2095+
},
2096+
},
2097+
{
2098+
test: {
2099+
name: 'flaky',
2100+
sequence: {
2101+
groupOrder: 1,
2102+
},
2103+
},
2104+
},
2105+
],
2106+
},
2107+
})
2108+
```
2109+
2110+
Tests in these projects will run in this order:
2111+
2112+
```
2113+
0. slow |
2114+
|> running together
2115+
0. fast |
2116+
2117+
1. flaky |> runs after slow and fast alone
2118+
```
2119+
:::
2120+
20562121
#### sequence.shuffle
20572122

20582123
- **Type**: `boolean | { files?, tests? }`

docs/guide/cli-generated.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,7 @@ Hide logs for skipped tests
8989
- **CLI:** `--reporter <name>`
9090
- **Config:** [reporters](/config/#reporters)
9191

92-
Specify which reporter(s) to use. Available values:
93-
94-
`default`, `basic`, `blob`, `verbose`, `dot`, `json`, `tap`, `tap-flat`, `junit`, `hanging-process`, `github-actions`.
92+
Specify reporters (default, basic, blob, verbose, dot, json, tap, tap-flat, junit, hanging-process, github-actions)
9593

9694
### outputFile
9795

packages/vitest/src/node/cli/cli-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ export const cliOptionsConfig: VitestCLIOptions = {
549549
'Changes the order in which setup files are executed. Accepted values are: "list" and "parallel". If set to "list", will run setup files in the order they are defined. If set to "parallel", will run setup files in parallel (default: `"parallel"`)',
550550
argument: '<order>',
551551
},
552+
groupOrder: null,
552553
},
553554
},
554555
inspect: {

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,7 @@ export function resolveConfig(
772772
? RandomSequencer
773773
: BaseSequencer
774774
}
775+
resolved.sequence.groupOrder ??= 0
775776
resolved.sequence.hooks ??= 'stack'
776777
if (resolved.sequence.sequencer === RandomSequencer) {
777778
resolved.sequence.seed ??= Date.now()

packages/vitest/src/node/pool.ts

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export function createPool(ctx: Vitest): ProcessPool {
148148
}
149149
}
150150

151+
const poolConcurrentPromises = new Map<string, Promise<ProcessPool>>()
151152
const customPools = new Map<string, ProcessPool>()
152153
async function resolveCustomPool(filepath: string) {
153154
if (customPools.has(filepath)) {
@@ -178,26 +179,44 @@ export function createPool(ctx: Vitest): ProcessPool {
178179
return poolInstance as ProcessPool
179180
}
180181

181-
const filesByPool: Record<LocalPool, TestSpecification[]> = {
182-
forks: [],
183-
threads: [],
184-
vmThreads: [],
185-
vmForks: [],
186-
typescript: [],
182+
function getConcurrentPool(pool: string, fn: () => Promise<ProcessPool>) {
183+
if (poolConcurrentPromises.has(pool)) {
184+
return poolConcurrentPromises.get(pool)!
185+
}
186+
const promise = fn().finally(() => {
187+
poolConcurrentPromises.delete(pool)
188+
})
189+
poolConcurrentPromises.set(pool, promise)
190+
return promise
191+
}
192+
193+
function getCustomPool(pool: string) {
194+
return getConcurrentPool(pool, () => resolveCustomPool(pool))
187195
}
188196

197+
function getBrowserPool() {
198+
return getConcurrentPool('browser', async () => {
199+
const { createBrowserPool } = await import('@vitest/browser')
200+
return createBrowserPool(ctx)
201+
})
202+
}
203+
204+
const groupedSpecifications: Record<string, TestSpecification[]> = {}
205+
const groups = new Set<number>()
206+
189207
const factories: Record<LocalPool, () => ProcessPool> = {
190208
vmThreads: () => createVmThreadsPool(ctx, options),
209+
vmForks: () => createVmForksPool(ctx, options),
191210
threads: () => createThreadsPool(ctx, options),
192211
forks: () => createForksPool(ctx, options),
193-
vmForks: () => createVmForksPool(ctx, options),
194212
typescript: () => createTypecheckPool(ctx),
195213
}
196214

197215
for (const spec of files) {
198-
const { pool } = spec[2]
199-
filesByPool[pool] ??= []
200-
filesByPool[pool].push(spec)
216+
const group = spec[0].config.sequence.groupOrder ?? 0
217+
groups.add(group)
218+
groupedSpecifications[group] ??= []
219+
groupedSpecifications[group].push(spec)
201220
}
202221

203222
const Sequencer = ctx.config.sequence.sequencer
@@ -210,35 +229,55 @@ export function createPool(ctx: Vitest): ProcessPool {
210229
return sequencer.sort(specs)
211230
}
212231

213-
await Promise.all(
214-
Object.entries(filesByPool).map(async (entry) => {
215-
const [pool, files] = entry as [Pool, TestSpecification[]]
216-
217-
if (!files.length) {
218-
return null
219-
}
220-
221-
const specs = await sortSpecs(files)
222-
223-
if (pool in factories) {
224-
const factory = factories[pool]
225-
pools[pool] ??= factory()
226-
return pools[pool]![method](specs, invalidate)
227-
}
228-
229-
if (pool === 'browser') {
230-
pools[pool] ??= await (async () => {
231-
const { createBrowserPool } = await import('@vitest/browser')
232-
return createBrowserPool(ctx)
233-
})()
234-
return pools[pool]![method](specs, invalidate)
235-
}
236-
237-
const poolHandler = await resolveCustomPool(pool)
238-
pools[poolHandler.name] ??= poolHandler
239-
return poolHandler[method](specs, invalidate)
240-
}),
241-
)
232+
const sortedGroups = Array.from(groups).sort()
233+
for (const group of sortedGroups) {
234+
const specifications = groupedSpecifications[group]
235+
236+
if (!specifications?.length) {
237+
continue
238+
}
239+
240+
const filesByPool: Record<LocalPool, TestSpecification[]> = {
241+
forks: [],
242+
threads: [],
243+
vmThreads: [],
244+
vmForks: [],
245+
typescript: [],
246+
}
247+
248+
specifications.forEach((specification) => {
249+
const pool = specification[2].pool
250+
filesByPool[pool] ??= []
251+
filesByPool[pool].push(specification)
252+
})
253+
254+
await Promise.all(
255+
Object.entries(filesByPool).map(async (entry) => {
256+
const [pool, files] = entry as [Pool, TestSpecification[]]
257+
258+
if (!files.length) {
259+
return null
260+
}
261+
262+
const specs = await sortSpecs(files)
263+
264+
if (pool in factories) {
265+
const factory = factories[pool]
266+
pools[pool] ??= factory()
267+
return pools[pool]![method](specs, invalidate)
268+
}
269+
270+
if (pool === 'browser') {
271+
pools.browser ??= await getBrowserPool()
272+
return pools.browser[method](specs, invalidate)
273+
}
274+
275+
const poolHandler = await getCustomPool(pool)
276+
pools[poolHandler.name] ??= poolHandler
277+
return poolHandler[method](specs, invalidate)
278+
}),
279+
)
280+
}
242281
}
243282

244283
return {

packages/vitest/src/node/types/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ interface SequenceOptions {
6464
* @default BaseSequencer
6565
*/
6666
sequencer?: TestSequencerConstructor
67+
/**
68+
* Controls the order in which this project runs its tests when using multiple [projects](/guide/projects).
69+
*
70+
* - Projects with the same group order number will run together, and groups are run from lowest to highest.
71+
* - If you don’t set this option, all projects run in parallel.
72+
* - If several projects use the same group order, they will run at the same time.
73+
* @default 0
74+
*/
75+
groupOrder?: number
6776
/**
6877
* Should files and tests run in random order.
6978
* @default false
@@ -1066,6 +1075,7 @@ export interface ResolvedConfig
10661075
shuffle?: boolean
10671076
concurrent?: boolean
10681077
seed: number
1078+
groupOrder: number
10691079
}
10701080

10711081
typecheck: Omit<TypecheckConfig, 'enabled'> & {

test/cli/test/group-order.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { expect, test } from 'vitest'
2+
import { runInlineTests } from '../../test-utils'
3+
4+
test('tests run according to the group order', async () => {
5+
const { stdout, stderr } = await runInlineTests({
6+
'example.1.test.ts': `test('1')`,
7+
'example.2.test.ts': `test('2')`,
8+
'example.2-2.test.ts': `test('2-2')`,
9+
'example.3.test.ts': `test('3')`,
10+
}, {
11+
globals: true,
12+
// run projects in the opposite order!
13+
projects: [
14+
{
15+
test: {
16+
name: '3',
17+
include: ['./example.3.test.ts'],
18+
sequence: {
19+
groupOrder: 1,
20+
},
21+
},
22+
},
23+
{
24+
test: {
25+
include: ['./example.2.test.ts', './example.2-2.test.ts'],
26+
name: '2',
27+
sequence: {
28+
groupOrder: 2,
29+
},
30+
},
31+
},
32+
{
33+
test: {
34+
name: '1',
35+
include: ['./example.1.test.ts'],
36+
sequence: {
37+
groupOrder: 3,
38+
},
39+
},
40+
},
41+
],
42+
})
43+
expect(stderr).toBe('')
44+
45+
const tests = stdout.split('\n').filter(c => c.startsWith(' ✓')).join('\n').replace(/\d+ms/g, '<time>')
46+
47+
expect(tests).toMatchInlineSnapshot(`
48+
" ✓ |3| example.3.test.ts > 3 <time>
49+
✓ |2| example.2-2.test.ts > 2-2 <time>
50+
✓ |2| example.2.test.ts > 2 <time>
51+
✓ |1| example.1.test.ts > 1 <time>"
52+
`)
53+
})

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