Skip to content

Commit 40cdc7f

Browse files
hi-ogawaOpenCode (claude-opus-4-8)
andauthored
fix: fix per-project sequence config (#10659)
Co-authored-by: Hiroshi Ogawa <[email protected]> Co-authored-by: OpenCode (claude-opus-4-8) <[email protected]>
1 parent 1a20dd2 commit 40cdc7f

8 files changed

Lines changed: 100 additions & 17 deletions

File tree

docs/config/sequence.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,16 @@ If you want files and tests to run randomly, you can enable it with this option,
9999

100100
Vitest usually uses cache to sort tests, so long-running tests start earlier, which makes tests run faster. If your files and tests run in random order, you will lose this performance improvement, but it may be useful to track tests that accidentally depend on another test run previously.
101101

102-
### sequence.shuffle.files {#sequence-shuffle-files}
102+
### sequence.shuffle.files <CRoot /> {#sequence-shuffle-files}
103103

104104
- **Type:** `boolean`
105105
- **Default:** `false`
106106
- **CLI:** `--sequence.shuffle.files`, `--sequence.shuffle.files=false`
107107

108108
Whether to randomize files, be aware that long running tests will not start earlier if you enable this option.
109109

110+
Because file ordering is shared across [projects](/guide/projects), this option is resolved from the root config only. A project can still randomize its own tests with [`sequence.shuffle.tests`](#sequence-shuffle-tests).
111+
110112
### sequence.shuffle.tests {#sequence-shuffle-tests}
111113

112114
- **Type:** `boolean`

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -790,10 +790,7 @@ export function resolveConfig(
790790
}
791791
resolved.sequence.groupOrder ??= 0
792792
resolved.sequence.hooks ??= 'stack'
793-
// Set seed if either files or tests are shuffled
794-
if (resolved.sequence.sequencer === RandomSequencer || resolved.sequence.shuffle) {
795-
resolved.sequence.seed ??= Date.now()
796-
}
793+
resolved.sequence.seed ??= Date.now()
797794

798795
resolved.typecheck = {
799796
...configDefaults.typecheck,

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ export function serializeConfig(project: TestProject): SerializedConfig {
8787
?? globalConfig.snapshotOptions.expand,
8888
},
8989
sequence: {
90-
shuffle: globalConfig.sequence.shuffle,
91-
concurrent: globalConfig.sequence.concurrent,
90+
shuffle: config.sequence.shuffle,
91+
concurrent: config.sequence.concurrent,
92+
// `seed` and `sequencer` drive cross-project file ordering, so they are
93+
// resolved from the root config and shared across all projects.
9294
seed: globalConfig.sequence.seed,
93-
hooks: globalConfig.sequence.hooks,
94-
setupFiles: globalConfig.sequence.setupFiles,
95+
hooks: config.sequence.hooks,
96+
setupFiles: config.sequence.setupFiles,
9597
},
9698
inspect: globalConfig.inspect,
9799
inspectBrk: globalConfig.inspectBrk,

packages/vitest/src/node/core.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import { HangingProcessReporter } from './reporters/hanging-process'
5151
import { createReport } from './reporters/report'
5252
import { createReporters } from './reporters/utils'
5353
import { VitestResolver } from './resolver'
54+
import { RandomSequencer } from './sequencers/RandomSequencer'
5455
import { VitestSpecifications } from './specifications'
5556
import { StateManager } from './state'
5657
import { populateProjectsTags } from './tags'
@@ -680,7 +681,11 @@ export class Vitest {
680681
* Returns the seed, if tests are running in a random order.
681682
*/
682683
public getSeed(): number | null {
683-
return this.config.sequence.seed ?? null
684+
// Tests can be shuffled per project, so check projects as well.
685+
const randomized = this.config.sequence.sequencer === RandomSequencer
686+
|| !!this.config.sequence.shuffle
687+
|| this.projects.some(p => !!p.config.sequence.shuffle)
688+
return randomized ? this.config.sequence.seed : null
684689
}
685690

686691
/** @internal */

packages/vitest/src/node/logger.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import c from 'tinyrainbow'
99
import { highlightCode } from '../utils/colors'
1010
import { capturePrintError, printError } from './printError'
1111
import { divider, errorBanner, formatProjectName, withLabel } from './reporters/renderers/utils'
12-
import { RandomSequencer } from './sequencers/RandomSequencer'
1312

1413
export interface ErrorOptions {
1514
type?: string
@@ -235,9 +234,9 @@ export class Logger {
235234

236235
this.log(withLabel(color, mode, `v${this.ctx.version} `) + c.gray(this.ctx.config.root))
237236

238-
// Log seed if either files (RandomSequencer) or tests are shuffled
239-
if (this.ctx.config.sequence.sequencer === RandomSequencer || this.ctx.config.sequence.shuffle) {
240-
this.log(PAD + c.gray(`Running tests with seed "${this.ctx.config.sequence.seed}"`))
237+
const seed = this.ctx.getSeed()
238+
if (seed != null) {
239+
this.log(PAD + c.gray(`Running tests with seed "${seed}"`))
241240
}
242241

243242
if (this.ctx.config.ui) {

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,11 +1296,15 @@ export interface ServerDepsOptions {
12961296
export type ProjectConfig = Omit<
12971297
InlineConfig,
12981298
NonProjectOptions
1299-
| 'sequencer'
1299+
| 'sequence'
13001300
| 'deps'
13011301
> & {
13021302
mode?: string
1303-
sequencer?: Omit<SequenceOptions, 'sequencer' | 'seed'>
1303+
sequence?: Omit<SequenceOptions, 'sequencer' | 'seed' | 'shuffle'> & {
1304+
// `shuffle.files` controls cross-project file ordering, which is resolved
1305+
// from the root config only, so projects can only shuffle their own tests.
1306+
shuffle?: boolean | { tests?: boolean }
1307+
}
13041308
deps?: Omit<DepsOptions, 'moduleDirectories'>
13051309
}
13061310

test/e2e/test/config/sequence-shuffle.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,25 @@ test('should log seed when shuffle is true', async () => {
148148
expect(stdout).toContain('Running tests with seed "67890"')
149149
})
150150

151+
test('should log seed when a project shuffles tests', async () => {
152+
const { stdout, ctx } = await runInlineTests({
153+
'a.test.js': /* js */ `
154+
import { test } from 'vitest'
155+
test('example', () => {})
156+
`,
157+
}, {
158+
projects: [
159+
{ test: { sequence: { shuffle: { tests: true } } } },
160+
],
161+
})
162+
163+
const seed = ctx?.getSeed()
164+
expect(seed).toEqual(expect.any(Number))
165+
expect(stdout).toContain(`Running tests with seed "${seed}"`)
166+
})
167+
151168
test('should not log seed when shuffle is disabled', async () => {
152-
const { stdout } = await runInlineTests({
169+
const { stdout, ctx } = await runInlineTests({
153170
'basic.test.js': /* js */ `
154171
import { test } from 'vitest'
155172
test('example', () => {})
@@ -160,5 +177,6 @@ test('should not log seed when shuffle is disabled', async () => {
160177
},
161178
})
162179

180+
expect(ctx?.getSeed()).toBe(null)
163181
expect(stdout).not.toContain('Running tests with seed')
164182
})

test/e2e/test/sequence-concurrent.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,59 @@ test('should run suites and tests sequentially unless concurrent specified when
6565
}
6666
`)
6767
})
68+
69+
test('applies sequence.concurrent per project (not just the root config)', async () => {
70+
// The root config leaves `sequence.concurrent` at its default (false). Each
71+
// project sets it independently, so the fixtures only pass if the project's
72+
// value reaches the runtime. The fixtures self-assert `task.concurrent` and
73+
// their completion order via staggered delays, so this is deterministic.
74+
const { stderr, errorTree } = await runVitest({
75+
root: './fixtures/sequence-concurrent',
76+
projects: [
77+
{
78+
test: {
79+
name: 'concurrent',
80+
include: ['sequence-concurrent-true-concurrent.test.ts'],
81+
sequence: {
82+
concurrent: true,
83+
},
84+
},
85+
},
86+
{
87+
test: {
88+
name: 'sequential',
89+
include: ['sequence-concurrent-false-sequential.test.ts'],
90+
sequence: {
91+
concurrent: false,
92+
},
93+
},
94+
},
95+
],
96+
})
97+
98+
expect(stderr).toBe('')
99+
expect(errorTree({ project: true })).toMatchInlineSnapshot(`
100+
{
101+
"concurrent": {
102+
"sequence-concurrent-true-concurrent.test.ts": {
103+
"concurrent suite": {
104+
"first test completes last": "passed",
105+
"second test completes third": "passed",
106+
},
107+
"last test completes first": "passed",
108+
"third test completes second": "passed",
109+
},
110+
},
111+
"sequential": {
112+
"sequence-concurrent-false-sequential.test.ts": {
113+
"last test completes last": "passed",
114+
"sequential suite": {
115+
"first test completes first": "passed",
116+
"second test completes second": "passed",
117+
},
118+
"third test completes third": "passed",
119+
},
120+
},
121+
}
122+
`)
123+
})

0 commit comments

Comments
 (0)