Skip to content

Commit f07b4ba

Browse files
committed
Merge pull request microsoft#8774 from Microsoft/parallel-tests
run tests in parallel by equally dividing them between workers
2 parents 3515652 + eab2511 commit f07b4ba

File tree

8 files changed

+266
-63
lines changed

8 files changed

+266
-63
lines changed

Jakefile.js

Lines changed: 89 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -680,9 +680,9 @@ function cleanTestDirs() {
680680
}
681681

682682
// used to pass data from jake command line directly to run.js
683-
function writeTestConfigFile(tests, light, testConfigFile) {
684-
console.log('Running test(s): ' + tests);
685-
var testConfigContents = JSON.stringify({ test: [tests], light: light });
683+
function writeTestConfigFile(tests, light, taskConfigsFolder, workerCount, testConfigFile) {
684+
var testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, light: light, workerCount: workerCount, taskConfigsFolder: taskConfigsFolder });
685+
console.log('Running tests with config: ' + testConfigContents);
686686
fs.writeFileSync('test.config', testConfigContents);
687687
}
688688

@@ -692,7 +692,7 @@ function deleteTemporaryProjectOutput() {
692692
}
693693
}
694694

695-
function runConsoleTests(defaultReporter, defaultSubsets) {
695+
function runConsoleTests(defaultReporter, runInParallel) {
696696
cleanTestDirs();
697697
var debug = process.env.debug || process.env.d;
698698
tests = process.env.test || process.env.tests || process.env.t;
@@ -701,9 +701,22 @@ function runConsoleTests(defaultReporter, defaultSubsets) {
701701
if(fs.existsSync(testConfigFile)) {
702702
fs.unlinkSync(testConfigFile);
703703
}
704+
var workerCount, taskConfigsFolder;
705+
if (runInParallel) {
706+
// generate name to store task configuration files
707+
var prefix = os.tmpdir() + "/ts-tests";
708+
var i = 1;
709+
do {
710+
taskConfigsFolder = prefix + i;
711+
i++;
712+
} while (fs.existsSync(taskConfigsFolder));
713+
fs.mkdirSync(taskConfigsFolder);
714+
715+
workerCount = process.env.workerCount || os.cpus().length;
716+
}
704717

705-
if (tests || light) {
706-
writeTestConfigFile(tests, light, testConfigFile);
718+
if (tests || light || taskConfigsFolder) {
719+
writeTestConfigFile(tests, light, taskConfigsFolder, workerCount, testConfigFile);
707720
}
708721

709722
if (tests && tests.toLocaleLowerCase() === "rwc") {
@@ -717,61 +730,93 @@ function runConsoleTests(defaultReporter, defaultSubsets) {
717730

718731
// timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally
719732
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
720-
var subsetRegexes;
721-
if(defaultSubsets.length === 0) {
722-
subsetRegexes = [tests];
733+
if(!runInParallel) {
734+
tests = tests ? ' -g "' + tests + '"' : '';
735+
var cmd = "mocha" + (debug ? " --debug-brk" : "") + " -R " + reporter + tests + colors + ' -t ' + testTimeout + ' ' + run;
736+
console.log(cmd);
737+
exec(cmd, function () {
738+
runLinter();
739+
finish();
740+
}, function(e, status) {
741+
finish(status);
742+
});
743+
723744
}
724745
else {
725-
var subsets = tests ? tests.split("|") : defaultSubsets;
726-
subsetRegexes = subsets.map(function (sub) { return "^" + sub + ".*$"; });
727-
subsetRegexes.push("^(?!" + subsets.join("|") + ").*$");
728-
}
729-
var counter = subsetRegexes.length;
730-
var errorStatus;
731-
subsetRegexes.forEach(function (subsetRegex, i) {
732-
tests = subsetRegex ? ' -g "' + subsetRegex + '"' : '';
733-
var cmd = "mocha" + (debug ? " --debug-brk" : "") + " -R " + reporter + tests + colors + ' -t ' + testTimeout + ' ' + run;
746+
// run task to load all tests and partition them between workers
747+
var cmd = "mocha " + " -R min " + colors + run;
734748
console.log(cmd);
735-
function finish(status) {
736-
counter--;
737-
// save first error status
738-
if (status !== undefined && errorStatus === undefined) {
739-
errorStatus = status;
740-
}
741-
742-
deleteTemporaryProjectOutput();
743-
if (counter !== 0 || errorStatus === undefined) {
744-
// run linter when last worker is finished
745-
if (lintFlag && counter === 0) {
746-
var lint = jake.Task['lint'];
747-
lint.addListener('complete', function () {
749+
exec(cmd, function() {
750+
// read all configuration files and spawn a worker for every config
751+
var configFiles = fs.readdirSync(taskConfigsFolder);
752+
var counter = configFiles.length;
753+
var firstErrorStatus;
754+
// schedule work for chunks
755+
configFiles.forEach(function (f) {
756+
var configPath = path.join(taskConfigsFolder, f);
757+
var workerCmd = "mocha" + " -t " + testTimeout + " -R " + reporter + " " + colors + " " + run + " --config='" + configPath + "'";
758+
console.log(workerCmd);
759+
exec(workerCmd, finishWorker, finishWorker)
760+
});
761+
762+
function finishWorker(e, errorStatus) {
763+
counter--;
764+
if (firstErrorStatus === undefined && errorStatus !== undefined) {
765+
firstErrorStatus = errorStatus;
766+
}
767+
if (counter !== 0) {
768+
complete();
769+
}
770+
else {
771+
// last worker clean everything and runs linter in case if there were no errors
772+
deleteTemporaryProjectOutput();
773+
jake.rmRf(taskConfigsFolder);
774+
if (firstErrorStatus === undefined) {
775+
runLinter();
748776
complete();
749-
});
750-
lint.invoke();
777+
}
778+
else {
779+
failWithStatus(firstErrorStatus);
780+
}
751781
}
752-
complete();
753-
}
754-
else {
755-
fail("Process exited with code " + status);
756782
}
783+
});
784+
}
785+
786+
function failWithStatus(status) {
787+
fail("Process exited with code " + status);
788+
}
789+
790+
function finish(errorStatus) {
791+
deleteTemporaryProjectOutput();
792+
if (errorStatus !== undefined) {
793+
failWithStatus(errorStatus);
757794
}
758-
exec(cmd, function () {
759-
finish();
760-
}, function(e, status) {
761-
finish(status);
795+
else {
796+
complete();
797+
}
798+
}
799+
function runLinter() {
800+
if (!lintFlag) {
801+
return;
802+
}
803+
var lint = jake.Task['lint'];
804+
lint.addListener('complete', function () {
805+
complete();
762806
});
763-
});
807+
lint.invoke();
808+
}
764809
}
765810

766811
var testTimeout = 20000;
767812
desc("Runs all the tests in parallel using the built run.js file. Optional arguments are: t[ests]=category1|category2|... d[ebug]=true.");
768813
task("runtests-parallel", ["build-rules", "tests", builtLocalDirectory], function() {
769-
runConsoleTests('min', ['compiler', 'conformance', 'Projects', 'fourslash']);
814+
runConsoleTests('min', /*runInParallel*/ true);
770815
}, {async: true});
771816

772817
desc("Runs the tests using the built run.js file. Optional arguments are: t[ests]=regex r[eporter]=[list|spec|json|<more>] d[ebug]=true color[s]=false lint=true.");
773818
task("runtests", ["build-rules", "tests", builtLocalDirectory], function() {
774-
runConsoleTests('mocha-fivemat-progress-reporter', []);
819+
runConsoleTests('mocha-fivemat-progress-reporter', /*runInParallel*/ false);
775820
}, {async: true});
776821

777822
desc("Generates code coverage data via instanbul");

src/harness/compilerRunner.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const enum CompilerTestType {
1212

1313
class CompilerBaselineRunner extends RunnerBase {
1414
private basePath = "tests/cases";
15-
private testSuiteName: string;
15+
private testSuiteName: TestRunnerKind;
1616
private errors: boolean;
1717
private emit: boolean;
1818
private decl: boolean;
@@ -41,6 +41,14 @@ class CompilerBaselineRunner extends RunnerBase {
4141
this.basePath += "/" + this.testSuiteName;
4242
}
4343

44+
public kind() {
45+
return this.testSuiteName;
46+
}
47+
48+
public enumerateTestFiles() {
49+
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
50+
}
51+
4452
private makeUnitName(name: string, root: string) {
4553
return ts.isRootedDiskPath(name) ? name : ts.combinePaths(root, name);
4654
};
@@ -391,7 +399,7 @@ class CompilerBaselineRunner extends RunnerBase {
391399

392400
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
393401
if (this.tests.length === 0) {
394-
const testFiles = this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
402+
const testFiles = this.enumerateTestFiles();
395403
testFiles.forEach(fn => {
396404
fn = fn.replace(/\\/g, "/");
397405
this.checkTestCodeOutput(fn);

src/harness/fourslashRunner.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const enum FourSlashTestType {
1111

1212
class FourSlashRunner extends RunnerBase {
1313
protected basePath: string;
14-
protected testSuiteName: string;
14+
protected testSuiteName: TestRunnerKind;
1515

1616
constructor(private testType: FourSlashTestType) {
1717
super();
@@ -35,9 +35,17 @@ class FourSlashRunner extends RunnerBase {
3535
}
3636
}
3737

38+
public enumerateTestFiles() {
39+
return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
40+
}
41+
42+
public kind() {
43+
return this.testSuiteName;
44+
}
45+
3846
public initializeTests() {
3947
if (this.tests.length === 0) {
40-
this.tests = this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
48+
this.tests = this.enumerateTestFiles();
4149
}
4250

4351
describe(this.testSuiteName + " tests", () => {

src/harness/projectsRunner.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,19 @@ interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult {
3636
}
3737

3838
class ProjectRunner extends RunnerBase {
39+
40+
public enumerateTestFiles() {
41+
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
42+
}
43+
44+
public kind(): TestRunnerKind {
45+
return "project";
46+
}
47+
3948
public initializeTests() {
4049
if (this.tests.length === 0) {
41-
const testFiles = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
50+
const testFiles = this.enumerateTestFiles();
4251
testFiles.forEach(fn => {
43-
fn = fn.replace(/\\/g, "/");
4452
this.runProjectTestCase(fn);
4553
});
4654
}

0 commit comments

Comments
 (0)