Skip to content

Commit 024d9de

Browse files
authored
Add option to run compiler tests in parallel (#873)
1 parent 228afea commit 024d9de

File tree

3 files changed

+128
-44
lines changed

3 files changed

+128
-44
lines changed

package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@types/node": "^12.7.5",
2323
"browser-process-hrtime": "^1.0.0",
2424
"diff": "^4.0.1",
25+
"physical-cpu-count": "^2.0.0",
2526
"ts-loader": "^6.1.1",
2627
"ts-node": "^6.2.0",
2728
"tslint": "^5.20.0",

tests/compiler.js

+121-44
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const startTime = Date.now();
12
const fs = require("fs");
23
const path = require("path");
34
const os = require("os");
@@ -8,6 +9,8 @@ const optionsUtil = require("../cli/util/options");
89
const diff = require("./util/diff");
910
const asc = require("../cli/asc.js");
1011
const rtrace = require("../lib/rtrace");
12+
const cluster = require("cluster");
13+
const coreCount = require("physical-cpu-count");
1114

1215
const config = {
1316
"create": {
@@ -34,13 +37,18 @@ const config = {
3437
"Enables verbose rtrace output."
3538
]
3639
},
40+
"parallel": {
41+
"description": [
42+
"Runs tests in parallel."
43+
]
44+
},
3745
"help": {
3846
"description": "Prints this message and exits.",
3947
"type": "b",
4048
"alias": "h"
4149
}
4250
};
43-
const opts = optionsUtil.parse(process.argv.slice(2),config);
51+
const opts = optionsUtil.parse(process.argv.slice(2), config);
4452
const args = opts.options;
4553
const argv = opts.arguments;
4654

@@ -65,23 +73,23 @@ var skippedMessages = new Map();
6573

6674
const basedir = path.join(__dirname, "compiler");
6775

68-
// Get a list of all tests
69-
var tests = glob.sync("**/!(_*).ts", { cwd: basedir });
70-
71-
// Run specific tests only if arguments are provided
72-
if (argv.length) {
73-
tests = tests.filter(filename => argv.indexOf(filename.replace(/\.ts$/, "")) >= 0);
74-
if (!tests.length) {
75-
console.error("No matching tests: " + argv.join(" "));
76-
process.exit(1);
76+
// Gets a list of all relevant tests
77+
function getTests() {
78+
var tests = glob.sync("**/!(_*).ts", { cwd: basedir }).map(name => name.replace(/\.ts$/, ""));
79+
if (argv.length) { // run matching tests only
80+
tests = tests.filter(filename => argv.indexOf(filename.replace(/\.ts$/, "")) >= 0);
81+
if (!tests.length) {
82+
console.error("No matching tests: " + argv.join(" "));
83+
process.exit(1);
84+
}
7785
}
86+
return tests;
7887
}
7988

80-
// TODO: asc's callback is synchronous here. This might change.
81-
tests.forEach(filename => {
82-
console.log(colorsUtil.white("Testing compiler/" + filename) + "\n");
89+
// Runs a single test
90+
function runTest(basename) {
91+
console.log(colorsUtil.white("Testing compiler/" + basename) + "\n");
8392

84-
const basename = filename.replace(/\.ts$/, "");
8593
const configPath = path.join(basedir, basename + ".json");
8694
const config = fs.existsSync(configPath)
8795
? require(configPath)
@@ -118,6 +126,7 @@ tests.forEach(filename => {
118126
console.log("- " + colorsUtil.yellow("feature SKIPPED") + " (" + missing_features.join(", ") + ")\n");
119127
skippedTests.add(basename);
120128
skippedMessages.set(basename, "feature not enabled");
129+
if (cluster.isWorker) process.send({ cmd: "skipped", message: skippedMessages.get(basename) });
121130
return;
122131
}
123132
}
@@ -129,11 +138,9 @@ tests.forEach(filename => {
129138

130139
var failed = false;
131140

132-
// TODO: also save stdout/stderr and diff it (-> expected failures)
133-
134141
// Build unoptimized
135142
var cmd = [
136-
filename,
143+
basename + ".ts",
137144
"--baseDir", basedir,
138145
"--validate",
139146
"--measure",
@@ -142,10 +149,7 @@ tests.forEach(filename => {
142149
];
143150
if (asc_flags)
144151
Array.prototype.push.apply(cmd, asc_flags);
145-
if (args.createBinary)
146-
cmd.push("--binaryFile", basename + ".untouched.wasm");
147-
else
148-
cmd.push("--binaryFile", "temp.wasm");
152+
cmd.push("--binaryFile", basename + ".untouched.wasm");
149153
asc.main(cmd, {
150154
stdout: stdout,
151155
stderr: stderr
@@ -214,7 +218,7 @@ tests.forEach(filename => {
214218

215219
// Build optimized
216220
var cmd = [
217-
filename,
221+
basename + ".ts",
218222
"--baseDir", basedir,
219223
"--validate",
220224
"--measure",
@@ -237,7 +241,7 @@ tests.forEach(filename => {
237241
failedTests.add(basename);
238242
return 1;
239243
}
240-
let untouchedBuffer = fs.readFileSync(path.join(basedir, "temp.wasm"));
244+
let untouchedBuffer = fs.readFileSync(path.join(basedir, basename + ".untouched.wasm"));
241245
let optimizedBuffer = stdout.toBuffer();
242246
const gluePath = path.join(basedir, basename + ".js");
243247
var glue = {};
@@ -258,29 +262,11 @@ tests.forEach(filename => {
258262
if (failed) return 1;
259263
});
260264
if (v8_no_flags) v8.setFlagsFromString(v8_no_flags);
261-
});
262-
263-
if (skippedTests.size) {
264-
console.log(colorsUtil.yellow("WARNING: ") + colorsUtil.white(skippedTests.size + " compiler tests have been skipped:\n"));
265-
skippedTests.forEach(name => {
266-
var message = skippedMessages.has(name) ? colorsUtil.gray("[" + skippedMessages.get(name) + "]") : "";
267-
console.log(" " + name + " " + message);
268-
});
269-
console.log();
270-
}
271-
if (failedTests.size) {
272-
process.exitCode = 1;
273-
console.log(colorsUtil.red("ERROR: ") + colorsUtil.white(failedTests.size + " compiler tests had failures:\n"));
274-
failedTests.forEach(name => {
275-
var message = failedMessages.has(name) ? colorsUtil.gray("[" + failedMessages.get(name) + "]") : "";
276-
console.log(" " + name + " " + message);
277-
});
278-
console.log();
279-
}
280-
if (!process.exitCode) {
281-
console.log("[ " + colorsUtil.white("OK") + " ]");
265+
if (!args.createBinary) fs.unlink(path.join(basedir, basename + ".untouched.wasm"), err => {});
266+
if (cluster.isWorker) process.send({ cmd: "done", failed: failed, message: failedMessages.get(basename) });
282267
}
283268

269+
// Tests if instantiation of a module succeeds
284270
function testInstantiate(basename, binaryBuffer, name, glue) {
285271
var failed = false;
286272
try {
@@ -370,3 +356,94 @@ function testInstantiate(basename, binaryBuffer, name, glue) {
370356
}
371357
return false;
372358
}
359+
360+
// Evaluates the overall test result
361+
function evaluateResult() {
362+
if (skippedTests.size) {
363+
console.log(colorsUtil.yellow("WARNING: ") + colorsUtil.white(skippedTests.size + " compiler tests have been skipped:\n"));
364+
skippedTests.forEach(name => {
365+
var message = skippedMessages.has(name) ? colorsUtil.gray("[" + skippedMessages.get(name) + "]") : "";
366+
console.log(" " + name + " " + message);
367+
});
368+
console.log();
369+
}
370+
if (failedTests.size) {
371+
process.exitCode = 1;
372+
console.log(colorsUtil.red("ERROR: ") + colorsUtil.white(failedTests.size + " compiler tests had failures:\n"));
373+
failedTests.forEach(name => {
374+
var message = failedMessages.has(name) ? colorsUtil.gray("[" + failedMessages.get(name) + "]") : "";
375+
console.log(" " + name + " " + message);
376+
});
377+
console.log();
378+
}
379+
console.log("Time: " + (Date.now() - startTime) + " ms\n");
380+
if (!process.exitCode) {
381+
console.log("[ " + colorsUtil.white("OK") + " ]");
382+
}
383+
}
384+
385+
// Run tests in parallel if requested
386+
if (args.parallel && coreCount > 1) {
387+
if (cluster.isWorker) {
388+
colorsUtil.supported = true;
389+
process.on("message", msg => {
390+
if (msg.cmd != "run") throw Error("invalid command: " + msg.cmd);
391+
try {
392+
runTest(msg.test);
393+
} catch (e) {
394+
process.send({ cmd: "done", failed: true, message: e.message });
395+
}
396+
});
397+
process.send({ cmd: "ready" });
398+
} else {
399+
const tests = getTests();
400+
// const sizes = new Map();
401+
// tests.forEach(name => sizes.set(name, fs.statSync(path.join(basedir, name + ".ts")).size));
402+
// tests.sort((a, b) => sizes.get(b) - sizes.get(a));
403+
const workers = [];
404+
const current = [];
405+
const outputs = [];
406+
let numWorkers = Math.min(coreCount - 1, tests.length);
407+
console.log("Spawning " + numWorkers + " workers ...");
408+
cluster.settings.silent = true;
409+
let index = 0;
410+
for (let i = 0; i < numWorkers; ++i) {
411+
let worker = cluster.fork();
412+
workers[i] = worker;
413+
current[i] = null;
414+
outputs[i] = [];
415+
worker.process.stdout.on("data", buf => outputs[i].push(buf));
416+
worker.process.stderr.on("data", buf => outputs[i].push(buf));
417+
worker.on("message", msg => {
418+
if (msg.cmd == "done") {
419+
process.stdout.write(Buffer.concat(outputs[i]).toString());
420+
if (msg.failed) failedTests.add(current[i]);
421+
if (msg.message) failedMessages.set(current[i], msg.message);
422+
} else if (msg.cmd == "skipped") {
423+
process.stdout.write(Buffer.concat(outputs[i]).toString());
424+
skippedTests.add(current[i]);
425+
if (msg.message) skippedMessages.set(current[i], msg.message);
426+
} else if (msg.cmd != "ready") {
427+
throw Error("invalid command: " + msg.cmd);
428+
}
429+
if (index >= tests.length) {
430+
workers[i] = null;
431+
worker.kill();
432+
return;
433+
}
434+
current[i] = tests[index++];
435+
outputs[i] = [];
436+
worker.send({ cmd: "run", test: current[i] });
437+
});
438+
worker.on("disconnect", () => {
439+
if (workers[i]) throw Error("worker#" + i + " died unexpectedly");
440+
if (!--numWorkers) evaluateResult();
441+
});
442+
}
443+
}
444+
445+
// Otherwise run tests sequentially
446+
} else {
447+
getTests().forEach(runTest);
448+
evaluateResult();
449+
}

0 commit comments

Comments
 (0)