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