TypeScript/scripts/build/tests.js
Eli Barzilay 556098ed50 Avoid hard-wired build-tree paths
Instead, search for stuff up the directory tree, with the main
functionality being to look for `Gulpfile.js` and assume the resulting
directory is the root.

(Unfortunatley, this is implemented twice, one in `scripts` and another
in `src`.  It's not possible to use a single implementation for both
since that would require assuming a directory structure which this is
intended to avoid.)

Also, in `scripts/build/projects.js`, abstracdt common exec
functionality into a local helper, and use full paths based on the above
search instead of assuming relative paths assuming CWD being in the
project root.
2021-10-18 17:43:45 -04:00

217 lines
7.6 KiB
TypeScript

// @ts-check
const gulp = require("gulp");
const del = require("del");
const fs = require("fs");
const os = require("os");
const path = require("path");
const mkdirP = require("mkdirp");
const log = require("fancy-log");
const cmdLineOptions = require("./options");
const { CancellationToken } = require("prex");
const { exec } = require("./utils");
const { findUpFile } = require("./findUpDir");
const mochaJs = require.resolve("mocha/bin/_mocha");
exports.localBaseline = "tests/baselines/local/";
exports.refBaseline = "tests/baselines/reference/";
exports.localRwcBaseline = "internal/baselines/rwc/local";
exports.refRwcBaseline = "internal/baselines/rwc/reference";
exports.localTest262Baseline = "internal/baselines/test262/local";
/**
* @param {string} runJs
* @param {string} defaultReporter
* @param {boolean} runInParallel
* @param {boolean} watchMode
* @param {import("prex").CancellationToken} [cancelToken]
*/
async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, cancelToken = CancellationToken.none) {
let testTimeout = cmdLineOptions.timeout;
let tests = cmdLineOptions.tests;
const inspect = cmdLineOptions.break || cmdLineOptions.inspect;
const runners = cmdLineOptions.runners;
const light = cmdLineOptions.light;
const stackTraceLimit = cmdLineOptions.stackTraceLimit;
const testConfigFile = "test.config";
const failed = cmdLineOptions.failed;
const keepFailed = cmdLineOptions.keepFailed;
const shards = +cmdLineOptions.shards || undefined;
const shardId = +cmdLineOptions.shardId || undefined;
if (!cmdLineOptions.dirty) {
await cleanTestDirs();
cancelToken.throwIfCancellationRequested();
}
if (fs.existsSync(testConfigFile)) {
fs.unlinkSync(testConfigFile);
}
let workerCount, taskConfigsFolder;
if (runInParallel) {
// generate name to store task configuration files
const prefix = os.tmpdir() + "/ts-tests";
let i = 1;
do {
taskConfigsFolder = prefix + i;
i++;
} while (fs.existsSync(taskConfigsFolder));
fs.mkdirSync(taskConfigsFolder);
workerCount = cmdLineOptions.workers;
}
if (tests && tests.toLocaleLowerCase() === "rwc") {
testTimeout = 400000;
}
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || shards || shardId) {
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed, shards, shardId);
}
const colors = cmdLineOptions.colors;
const reporter = cmdLineOptions.reporter || defaultReporter;
/** @type {string[]} */
let args = [];
// timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
if (!runInParallel) {
args.push(mochaJs);
args.push("-R", findUpFile("scripts/failed-tests.js"));
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
if (tests) {
args.push("-g", `"${tests}"`);
}
if (failed) {
const grep = fs.readFileSync(".failed-tests", "utf8")
.split(/\r?\n/g)
.map(test => test.trim())
.filter(test => test.length > 0)
.map(regExpEscape)
.join("|");
const file = path.join(os.tmpdir(), ".failed-tests.json");
fs.writeFileSync(file, JSON.stringify({ grep }), "utf8");
args.push("--config", file);
}
if (colors) {
args.push("--colors");
}
else {
args.push("--no-colors");
}
if (inspect !== undefined) {
args.unshift((inspect == "" || inspect === true) ? "--inspect-brk" : "--inspect-brk="+inspect);
args.push("-t", "0");
}
else {
args.push("-t", "" + testTimeout);
}
args.push(runJs);
}
else {
// run task to load all tests and partition them between workers
args.push(runJs);
}
/** @type {number | undefined} */
let errorStatus;
/** @type {Error | undefined} */
let error;
try {
setNodeEnvToDevelopment();
const { exitCode } = await exec("node", args, { cancelToken });
if (exitCode !== 0) {
errorStatus = exitCode;
error = new Error(`Process exited with status code ${errorStatus}.`);
}
else if (process.env.CI === "true") {
// finally, do a sanity check and build the compiler with the built version of itself
log.info("Starting sanity check build...");
// Cleanup everything except lint rules (we'll need those later and would rather not waste time rebuilding them)
await exec("gulp", ["clean-tsc", "clean-services", "clean-tsserver", "clean-lssl", "clean-tests"], { cancelToken });
const { exitCode } = await exec("gulp", ["local", "--lkg=false"], { cancelToken });
if (exitCode !== 0) {
errorStatus = exitCode;
error = new Error(`Sanity check build process exited with status code ${errorStatus}.`);
}
}
}
catch (e) {
errorStatus = undefined;
error = e;
}
finally {
restoreSavedNodeEnv();
}
await del("test.config");
await deleteTemporaryProjectOutput();
if (error !== undefined) {
process.exitCode = typeof errorStatus === "number" ? errorStatus : 2;
throw error;
}
}
exports.runConsoleTests = runConsoleTests;
async function cleanTestDirs() {
await del([exports.localBaseline, exports.localRwcBaseline])
mkdirP.sync(exports.localRwcBaseline);
mkdirP.sync(exports.localBaseline);
}
exports.cleanTestDirs = cleanTestDirs;
/**
* used to pass data from gulp command line directly to run.js
* @param {string} tests
* @param {string} runners
* @param {boolean} light
* @param {string} [taskConfigsFolder]
* @param {string | number} [workerCount]
* @param {string} [stackTraceLimit]
* @param {string | number} [timeout]
* @param {boolean} [keepFailed]
* @param {number | undefined} [shards]
* @param {number | undefined} [shardId]
*/
function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed, shards, shardId) {
const testConfigContents = JSON.stringify({
test: tests ? [tests] : undefined,
runners: runners ? runners.split(",") : undefined,
light,
workerCount,
stackTraceLimit,
taskConfigsFolder,
noColor: !cmdLineOptions.colors,
timeout,
keepFailed,
shards,
shardId
});
log.info("Running tests with config: " + testConfigContents);
fs.writeFileSync("test.config", testConfigContents);
}
exports.writeTestConfigFile = writeTestConfigFile;
/** @type {string} */
let savedNodeEnv;
function setNodeEnvToDevelopment() {
savedNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "development";
}
function restoreSavedNodeEnv() {
process.env.NODE_ENV = savedNodeEnv;
}
function deleteTemporaryProjectOutput() {
return del(path.join(exports.localBaseline, "projectOutput/"));
}
function regExpEscape(text) {
return text.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&');
}