• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// @ts-check
2const gulp = require("gulp");
3const del = require("del");
4const fs = require("fs");
5const os = require("os");
6const path = require("path");
7const mkdirP = require("mkdirp");
8const log = require("fancy-log");
9const cmdLineOptions = require("./options");
10const { CancellationToken } = require("prex");
11const { exec } = require("./utils");
12
13const mochaJs = require.resolve("mocha/bin/_mocha");
14exports.localBaseline = "tests/baselines/local/";
15exports.refBaseline = "tests/baselines/reference/";
16exports.localRwcBaseline = "internal/baselines/rwc/local";
17exports.refRwcBaseline = "internal/baselines/rwc/reference";
18exports.localTest262Baseline = "internal/baselines/test262/local";
19
20/**
21 * @param {string} runJs
22 * @param {string} defaultReporter
23 * @param {boolean} runInParallel
24 * @param {boolean} watchMode
25 * @param {import("prex").CancellationToken} [cancelToken]
26 */
27async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, cancelToken = CancellationToken.none) {
28    let testTimeout = cmdLineOptions.timeout;
29    let tests = cmdLineOptions.tests;
30    const debug = cmdLineOptions.debug;
31    const inspect = cmdLineOptions.inspect;
32    const runners = cmdLineOptions.runners;
33    const light = cmdLineOptions.light;
34    const stackTraceLimit = cmdLineOptions.stackTraceLimit;
35    const testConfigFile = "test.config";
36    const failed = cmdLineOptions.failed;
37    const keepFailed = cmdLineOptions.keepFailed;
38    const shards = +cmdLineOptions.shards || undefined;
39    const shardId = +cmdLineOptions.shardId || undefined;
40    if (!cmdLineOptions.dirty) {
41        await cleanTestDirs();
42        cancelToken.throwIfCancellationRequested();
43    }
44
45    if (fs.existsSync(testConfigFile)) {
46        fs.unlinkSync(testConfigFile);
47    }
48
49    let workerCount, taskConfigsFolder;
50    if (runInParallel) {
51        // generate name to store task configuration files
52        const prefix = os.tmpdir() + "/ts-tests";
53        let i = 1;
54        do {
55            taskConfigsFolder = prefix + i;
56            i++;
57        } while (fs.existsSync(taskConfigsFolder));
58        fs.mkdirSync(taskConfigsFolder);
59
60        workerCount = cmdLineOptions.workers;
61    }
62
63    if (tests && tests.toLocaleLowerCase() === "rwc") {
64        testTimeout = 400000;
65    }
66
67    if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || shards || shardId) {
68        writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed, shards, shardId);
69    }
70
71    const colors = cmdLineOptions.colors;
72    const reporter = cmdLineOptions.reporter || defaultReporter;
73
74    /** @type {string[]} */
75    let args = [];
76
77    // timeout normally isn"t necessary but Travis-CI has been timing out on compiler baselines occasionally
78    // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
79    if (!runInParallel) {
80        args.push(mochaJs);
81        args.push("-R", "scripts/failed-tests");
82        args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
83        if (tests) {
84            args.push("-g", `"${tests}"`);
85        }
86        if (failed) {
87            const grep = fs.readFileSync(".failed-tests", "utf8")
88                .split(/\r?\n/g)
89                .map(test => test.trim())
90                .filter(test => test.length > 0)
91                .map(regExpEscape)
92                .join("|");
93            const file = path.join(os.tmpdir(), ".failed-tests.json");
94            fs.writeFileSync(file, JSON.stringify({ grep }), "utf8");
95            args.push("--config", file);
96        }
97        if (colors) {
98            args.push("--colors");
99        }
100        else {
101            args.push("--no-colors");
102        }
103        if (inspect !== undefined) {
104            args.unshift(inspect == "" ? "--inspect-brk" : "--inspect-brk="+inspect);
105            args.push("-t", "0");
106        }
107        else if (debug) {
108            args.unshift("--debug-brk");
109            args.push("-t", "0");
110        }
111        else {
112            args.push("-t", "" + testTimeout);
113        }
114        args.push(runJs);
115    }
116    else {
117        // run task to load all tests and partition them between workers
118        args.push(runJs);
119    }
120
121    /** @type {number | undefined} */
122    let errorStatus;
123
124    /** @type {Error | undefined} */
125    let error;
126
127    try {
128        setNodeEnvToDevelopment();
129        const { exitCode } = await exec("node", args, { cancelToken });
130        if (exitCode !== 0) {
131            errorStatus = exitCode;
132            error = new Error(`Process exited with status code ${errorStatus}.`);
133        }
134        else if (process.env.CI === "true") {
135            // finally, do a sanity check and build the compiler with the built version of itself
136            log.info("Starting sanity check build...");
137            // Cleanup everything except lint rules (we'll need those later and would rather not waste time rebuilding them)
138            await exec("gulp", ["clean-tsc", "clean-services", "clean-tsserver", "clean-lssl", "clean-tests"], { cancelToken });
139            const { exitCode } = await exec("gulp", ["local", "--lkg=false"], { cancelToken });
140            if (exitCode !== 0) {
141                errorStatus = exitCode;
142                error = new Error(`Sanity check build process exited with status code ${errorStatus}.`);
143            }
144        }
145    }
146    catch (e) {
147        errorStatus = undefined;
148        error = e;
149    }
150    finally {
151        restoreSavedNodeEnv();
152    }
153
154    await del("test.config");
155    await deleteTemporaryProjectOutput();
156
157    if (error !== undefined) {
158        process.exitCode = typeof errorStatus === "number" ? errorStatus : 2;
159        throw error;
160    }
161}
162exports.runConsoleTests = runConsoleTests;
163
164async function cleanTestDirs() {
165    await del([exports.localBaseline, exports.localRwcBaseline])
166    mkdirP.sync(exports.localRwcBaseline);
167    mkdirP.sync(exports.localBaseline);
168}
169exports.cleanTestDirs = cleanTestDirs;
170
171/**
172 * used to pass data from gulp command line directly to run.js
173 * @param {string} tests
174 * @param {string} runners
175 * @param {boolean} light
176 * @param {string} [taskConfigsFolder]
177 * @param {string | number} [workerCount]
178 * @param {string} [stackTraceLimit]
179 * @param {string | number} [timeout]
180 * @param {boolean} [keepFailed]
181 * @param {number | undefined} [shards]
182 * @param {number | undefined} [shardId]
183 */
184function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed, shards, shardId) {
185    const testConfigContents = JSON.stringify({
186        test: tests ? [tests] : undefined,
187        runners: runners ? runners.split(",") : undefined,
188        light,
189        workerCount,
190        stackTraceLimit,
191        taskConfigsFolder,
192        noColor: !cmdLineOptions.colors,
193        timeout,
194        keepFailed,
195        shards,
196        shardId
197    });
198    log.info("Running tests with config: " + testConfigContents);
199    fs.writeFileSync("test.config", testConfigContents);
200}
201exports.writeTestConfigFile = writeTestConfigFile;
202
203/** @type {string} */
204let savedNodeEnv;
205function setNodeEnvToDevelopment() {
206    savedNodeEnv = process.env.NODE_ENV;
207    process.env.NODE_ENV = "development";
208}
209
210function restoreSavedNodeEnv() {
211    process.env.NODE_ENV = savedNodeEnv;
212}
213
214function deleteTemporaryProjectOutput() {
215    return del(path.join(exports.localBaseline, "projectOutput/"));
216}
217
218function regExpEscape(text) {
219    return text.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&');
220}
221