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