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