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