Repository navigation
Expand file tree
/
Copy pathsequencer.ts
More file actions
67 lines (61 loc) · 2.7 KB
/
Copy pathsequencer.ts
File metadata and controls
67 lines (61 loc) · 2.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import type { BaseSequencer, TestSpecification, Vitest } from 'vite-plus/test/node';
import { integrationTimings } from './integration-timings.ts';
import { balanceTests, compareKeys, shuffleTests, testFileKey } from './test-sharding.ts';
/** Balance shards and schedule each phase by cost across project boundaries. */
export class SuiteSequencer {
private readonly ctx: Vitest;
private baseSequencer: BaseSequencer | undefined;
constructor(ctx: Vitest) {
this.ctx = ctx;
}
private async base(): Promise<BaseSequencer> {
// The config is also loaded by ordinary build commands. Only the test
// runner needs to pay the startup cost of importing Vitest's Node API.
this.baseSequencer ??= new (await import('vite-plus/test/node')).BaseSequencer(this.ctx);
return this.baseSequencer;
}
private key(test: TestSpecification): string {
return testFileKey(test.moduleId, this.ctx.config.root);
}
private duration(test: TestSpecification): number {
return integrationTimings[this.key(test)] ?? 5000;
}
async shard(tests: Array<TestSpecification>): Promise<Array<TestSpecification>> {
const shard = this.ctx.config.shard;
if (!shard || tests.some((test) => test.project.name !== 'parallel-integration')) {
return (await this.base()).shard(tests);
}
return balanceTests(
tests,
shard.count,
(test) => this.key(test),
(test) => this.duration(test),
)[shard.index - 1];
}
async sort(tests: Array<TestSpecification>): Promise<Array<TestSpecification>> {
const sequence = this.ctx.config.sequence;
if (sequence.shuffle) {
const stable = [...tests].sort((a, b) =>
compareKeys(`${a.project.name}:${this.key(a)}`, `${b.project.name}:${this.key(b)}`),
);
return shuffleTests(stable, sequence.seed);
}
const sorted = await (await this.base()).sort(tests);
const results = new Map(
sorted.map((test) => [test, this.ctx.cache.getTestSpecificationResult(test)]),
);
const cost = (test: TestSpecification) =>
results.get(test)?.duration ??
(test.project.name === 'parallel-integration' ? this.duration(test) : 5000);
// BaseSequencer puts project names ahead of duration. That leaves expensive
// unit files queued behind integration builds even when they share a phase.
// Preserve phase barriers and failure priority, then fill the shared workers
// longest-first. Base ordering breaks ties (including cold file-size order).
return sorted.sort(
(a, b) =>
a.project.config.sequence.groupOrder - b.project.config.sequence.groupOrder ||
Number(results.get(b)?.failed ?? false) - Number(results.get(a)?.failed ?? false) ||
cost(b) - cost(a),
);
}
}