Skip to content

Commit 3e68561

Browse files
authored
Add support for sharding tests across multiple workers (microsoft#32173)
* Add support for sharding tests across multiple workers * Disable unittests when runners are expressly provided (unless they contain the unittest runner)
1 parent 055a07e commit 3e68561

File tree

11 files changed

+48
-12
lines changed

11 files changed

+48
-12
lines changed

Gulpfile.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ task("runtests").flags = {
415415
" --no-lint": "Disables lint",
416416
" --timeout=<ms>": "Overrides the default test timeout.",
417417
" --built": "Compile using the built version of the compiler.",
418+
" --shards": "Total number of shards running tests (default: 1)",
419+
" --shardId": "1-based ID of this shard (default: 1)",
418420
}
419421

420422
const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ false);
@@ -430,6 +432,8 @@ task("runtests-parallel").flags = {
430432
" --timeout=<ms>": "Overrides the default test timeout.",
431433
" --built": "Compile using the built version of the compiler.",
432434
" --skipPercent=<number>": "Skip expensive tests with <percent> chance to miss an edit. Default 5%.",
435+
" --shards": "Total number of shards running tests (default: 1)",
436+
" --shardId": "1-based ID of this shard (default: 1)",
433437
};
434438

435439
task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true }));

scripts/build/options.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const os = require("os");
55
/** @type {CommandLineOptions} */
66
module.exports = minimist(process.argv.slice(2), {
77
boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built"],
8-
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"],
8+
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout", "shards", "shardId"],
99
alias: {
1010
"b": "browser",
1111
"d": "debug", "debug-brk": "debug",

scripts/build/tests.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
3636
const testConfigFile = "test.config";
3737
const failed = cmdLineOptions.failed;
3838
const keepFailed = cmdLineOptions.keepFailed;
39+
const shards = +cmdLineOptions.shards || undefined;
40+
const shardId = +cmdLineOptions.shardId || undefined;
3941
if (!cmdLineOptions.dirty) {
4042
await cleanTestDirs();
4143
cancelToken.throwIfCancellationRequested();
@@ -63,8 +65,8 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
6365
testTimeout = 400000;
6466
}
6567

66-
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || skipPercent !== undefined) {
67-
writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed);
68+
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || skipPercent !== undefined || shards || shardId) {
69+
writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed, shards, shardId);
6870
}
6971

7072
const colors = cmdLineOptions.colors;
@@ -165,8 +167,10 @@ exports.cleanTestDirs = cleanTestDirs;
165167
* @param {string} [stackTraceLimit]
166168
* @param {string | number} [timeout]
167169
* @param {boolean} [keepFailed]
170+
* @param {number | undefined} [shards]
171+
* @param {number | undefined} [shardId]
168172
*/
169-
function writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed) {
173+
function writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed, shards, shardId) {
170174
const testConfigContents = JSON.stringify({
171175
test: tests ? [tests] : undefined,
172176
runners: runners ? runners.split(",") : undefined,
@@ -177,7 +181,9 @@ function writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFold
177181
taskConfigsFolder,
178182
noColor: !cmdLineOptions.colors,
179183
timeout,
180-
keepFailed
184+
keepFailed,
185+
shards,
186+
shardId
181187
});
182188
log.info("Running tests with config: " + testConfigContents);
183189
fs.writeFileSync("test.config", testConfigContents);

src/harness/harness.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ namespace Harness {
501501
}
502502

503503
function enumerateTestFiles(runner: RunnerBase) {
504-
return runner.enumerateTestFiles();
504+
return runner.getTestFiles();
505505
}
506506

507507
function listFiles(path: string, spec: RegExp, options: { recursive?: boolean } = {}) {

src/harness/runnerbase.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" |
22
type CompilerTestKind = "conformance" | "compiler";
33
type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server";
44

5+
let shards = 1;
6+
let shardId = 1;
7+
58
abstract class RunnerBase {
69
// contains the tests to run
710
public tests: (string | Harness.FileBasedTest)[] = [];
@@ -19,6 +22,14 @@ abstract class RunnerBase {
1922

2023
abstract enumerateTestFiles(): (string | Harness.FileBasedTest)[];
2124

25+
getTestFiles(): ReturnType<this["enumerateTestFiles"]> {
26+
const all = this.enumerateTestFiles();
27+
if (shards === 1) {
28+
return all as ReturnType<this["enumerateTestFiles"]>;
29+
}
30+
return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType<this["enumerateTestFiles"]>;
31+
}
32+
2233
/** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */
2334
public workingDirectory = "";
2435

src/testRunner/externalCompileRunner.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
2424
*/
2525
initializeTests(): void {
2626
// Read in and evaluate the test list
27-
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
27+
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
2828

2929
// tslint:disable-next-line:no-this-assignment
3030
const cls = this;
@@ -113,7 +113,7 @@ class DockerfileRunner extends ExternalCompileRunnerBase {
113113
}
114114
initializeTests(): void {
115115
// Read in and evaluate the test list
116-
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
116+
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
117117

118118
// tslint:disable-next-line:no-this-assignment
119119
const cls = this;

src/testRunner/parallel/host.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ namespace Harness.Parallel.Host {
222222
console.log("Discovering runner-based tests...");
223223
const discoverStart = +(new Date());
224224
for (const runner of runners) {
225-
for (const test of runner.enumerateTestFiles()) {
225+
for (const test of runner.getTestFiles()) {
226226
const file = typeof test === "string" ? test : test.file;
227227
let size: number;
228228
if (!perfData) {

src/testRunner/projectsRunner.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ namespace project {
3232

3333
export class ProjectRunner extends RunnerBase {
3434
public enumerateTestFiles() {
35-
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
35+
const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
36+
if (shards === 1) {
37+
return all;
38+
}
39+
return all.filter((_val, idx) => idx % shards === (shardId - 1));
3640
}
3741

3842
public kind(): TestRunnerKind {

src/testRunner/runner.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ interface TestConfig {
8080
timeout?: number;
8181
keepFailed?: boolean;
8282
skipPercent?: number;
83+
shardId?: number;
84+
shards?: number;
8385
}
8486

8587
interface TaskSet {
@@ -114,6 +116,12 @@ function handleTestConfig() {
114116
if (testConfig.skipPercent !== undefined) {
115117
skipPercent = testConfig.skipPercent;
116118
}
119+
if (testConfig.shardId) {
120+
shardId = testConfig.shardId;
121+
}
122+
if (testConfig.shards) {
123+
shards = testConfig.shards;
124+
}
117125

118126
if (testConfig.stackTraceLimit === "full") {
119127
(<any>Error).stackTraceLimit = Infinity;
@@ -129,6 +137,9 @@ function handleTestConfig() {
129137

130138
const runnerConfig = testConfig.runners || testConfig.test;
131139
if (runnerConfig && runnerConfig.length > 0) {
140+
if (testConfig.runners) {
141+
runUnitTests = runnerConfig.indexOf("unittest") !== -1;
142+
}
132143
for (const option of runnerConfig) {
133144
if (!option) {
134145
continue;

src/testRunner/rwcRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ class RWCRunner extends RunnerBase {
225225
*/
226226
public initializeTests(): void {
227227
// Read in and evaluate the test list
228-
for (const test of this.tests && this.tests.length ? this.tests : this.enumerateTestFiles()) {
228+
for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) {
229229
this.runTest(typeof test === "string" ? test : test.file);
230230
}
231231
}

src/testRunner/test262Runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Test262BaselineRunner extends RunnerBase {
9797
public initializeTests() {
9898
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
9999
if (this.tests.length === 0) {
100-
const testFiles = this.enumerateTestFiles();
100+
const testFiles = this.getTestFiles();
101101
testFiles.forEach(fn => {
102102
this.runTest(fn);
103103
});

0 commit comments

Comments
 (0)