"use strict"; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ /* eslint-disable no-extend-native */ // need to use graceful-fs for single-process code because it opens too many // files var _completions = require("../lib/completions.js"); var _index = require("../lib/values/index.js"); var _realm = require("../lib/realm.js"); var _construct_realm = require("../lib/construct_realm.js"); var _construct_realm2 = _interopRequireDefault(_construct_realm); var _globals = require("../lib/globals.js"); var _globals2 = _interopRequireDefault(_globals); var _arraybuffer = require("../lib/methods/arraybuffer.js"); var _singletons = require("../lib/singletons.js"); var _get = require("../lib/methods/get.js"); var _invariant = require("../lib/invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _jsYaml = require("js-yaml"); var _jsYaml2 = _interopRequireDefault(_jsYaml); var _chalk = require("chalk"); var _chalk2 = _interopRequireDefault(_chalk); var _path = require("path"); var _path2 = _interopRequireDefault(_path); var _gracefulFs = require("graceful-fs"); var _gracefulFs2 = _interopRequireDefault(_gracefulFs); var _cluster = require("cluster"); var _cluster2 = _interopRequireDefault(_cluster); var _os = require("os"); var _os2 = _interopRequireDefault(_os); var _tty = require("tty"); var _tty2 = _interopRequireDefault(_tty); var _minimist = require("minimist"); var _minimist2 = _interopRequireDefault(_minimist); var _process = require("process"); var _process2 = _interopRequireDefault(_process); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var EOL = _os2.default.EOL; var numCPUs = _os2.default.cpus().length; require("source-map-support").install(); // A TestTask is a task for a worker process to execute, which contains a // single test to run var TestTask = function () { function TestTask(file) { _classCallCheck(this, TestTask); this.type = TestTask.sentinel; this.file = file; } // eslint-disable-next-line flowtype/no-weak-types _createClass(TestTask, null, [{ key: "fromObject", value: function fromObject(obj) { // attempt to coerce the object into a test task if ("file" in obj && _typeof(obj.file) === "object") { return new TestTask(TestFileInfo.fromObject(obj.file)); } else { throw new Error("Cannot be converted to a TestTask: " + JSON.stringify(obj)); } } }]); return TestTask; }(); /** * Information about a test file to be run. * */ TestTask.sentinel = "TestTask"; var TestFileInfo = function () { function TestFileInfo(location, isES6) { _classCallCheck(this, TestFileInfo); this.location = location; this.isES6 = isES6; this.groupName = _path2.default.dirname(location); } // eslint-disable-next-line flowtype/no-weak-types // Location of the test on the filesystem, call fs.readFile on this _createClass(TestFileInfo, null, [{ key: "fromObject", value: function fromObject(obj) { // attempt to coerce the object into a TestFileInfo if ("location" in obj && typeof obj.location === "string" && "isES6" in obj && typeof obj.isES6 === "boolean") { return new TestFileInfo(obj.location, obj.isES6); } else { throw new Error("Cannot be converted to a TestFileInfo: " + JSON.stringify(obj)); } } }]); return TestFileInfo; }(); // A Message sent by a worker to the master to say that it has finished its // current task successfully var DoneMessage = function () { function DoneMessage(test) { var testResult = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; _classCallCheck(this, DoneMessage); this.type = DoneMessage.sentinel; this.test = test; this.testResults = testResult; } // eslint-disable-next-line flowtype/no-weak-types _createClass(DoneMessage, null, [{ key: "fromObject", value: function fromObject(obj) { if (!("type" in obj && typeof obj.type === "string" && obj.type === DoneMessage.sentinel)) { throw new Error("Cannot be converted to a DoneMessage: " + JSON.stringify(obj)); } if (!("test" in obj && _typeof(obj.test) === "object")) { throw new Error("A DoneMessage must have a test"); } var msg = new DoneMessage(obj.test); if ("testResults" in obj && _typeof(obj.testResults) === "object" && Array.isArray(obj.testResults)) { msg.testResults = obj.testResults; } return msg; } }]); return DoneMessage; }(); DoneMessage.sentinel = "DoneMessage"; var ErrorMessage = function () { function ErrorMessage(err) { _classCallCheck(this, ErrorMessage); this.type = ErrorMessage.sentinel; this.err = err; } // eslint-disable-next-line flowtype/no-weak-types _createClass(ErrorMessage, null, [{ key: "fromObject", value: function fromObject(obj) { if (!("type" in obj && typeof obj.type === "string" && obj.type === ErrorMessage.sentinel)) { throw new Error("Cannot be converted to an ErrorMessage: " + JSON.stringify(obj)); } if (!("err" in obj && _typeof(obj.err) === "object")) { throw new Error("Cannot be converted to an ErrorMessage: " + JSON.stringify(obj)); } return new ErrorMessage(obj.err); } }]); return ErrorMessage; }(); /** * TestResult contains information about a test that ran. */ ErrorMessage.sentinel = "ErrorMessage"; var TestResult = function TestResult(passed, strict) { var err = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; _classCallCheck(this, TestResult); this.passed = passed; this.strict = strict; this.err = err; }; // A Message sent by the master to workers to say that there is nothing more // to do var QuitMessage = function () { function QuitMessage() { _classCallCheck(this, QuitMessage); this.type = QuitMessage.sentinel; } _createClass(QuitMessage, null, [{ key: "fromObject", value: function fromObject(obj) { return new QuitMessage(); } }]); return QuitMessage; }(); QuitMessage.sentinel = "QuitMessage"; var BannerData = function () { function BannerData() { _classCallCheck(this, BannerData); this.info = ""; this.es5id = ""; this.es6id = ""; this.description = ""; this.flags = []; this.features = []; this.includes = []; this.negative = {}; } // eslint-disable-next-line flowtype/no-weak-types // eslint-disable-next-line flowtype/no-weak-types _createClass(BannerData, null, [{ key: "fromObject", value: function fromObject(obj) { var bd = new BannerData(); if ("info" in obj && typeof obj.info === "string") { bd.info = obj.info; } if ("es5id" in obj && typeof obj.es5id === "string") { bd.es5id = obj.es5id; } if ("es6id" in obj && typeof obj.es6id === "string") { bd.es6id = obj.es6id; } if ("description" in obj && typeof obj.description === "string") { bd.description = obj.description; } if ("flags" in obj && _typeof(obj.flags) === "object" && Array.isArray(obj.flags)) { bd.flags = obj.flags; } if ("features" in obj && _typeof(obj.features) === "object" && Array.isArray(obj.features)) { bd.features = obj.features; } if ("includes" in obj && _typeof(obj.includes) === "object" && Array.isArray(obj.includes)) { bd.includes = obj.includes; } if ("negative" in obj && _typeof(obj.negative) === "object") { bd.negative = obj.negative; } return bd; } }]); return BannerData; }(); var MasterProgramArgs = function MasterProgramArgs(verbose, timeout, bailAfter, cpuScale, statusFile, filterString, singleThreaded, relativeTestPath) { _classCallCheck(this, MasterProgramArgs); this.verbose = verbose; this.timeout = timeout; this.bailAfter = bailAfter; this.cpuScale = cpuScale; this.statusFile = statusFile; this.filterString = filterString; this.singleThreaded = singleThreaded; this.relativeTestPath = relativeTestPath; }; var WorkerProgramArgs = function WorkerProgramArgs(timeout, relativeTestPath) { _classCallCheck(this, WorkerProgramArgs); this.timeout = timeout; this.relativeTestPath = relativeTestPath; }; // NOTE: inheriting from Error does not seem to pass through an instanceof // check var ArgsParseError = function ArgsParseError(message) { _classCallCheck(this, ArgsParseError); this.message = message; }; if (!("toJSON" in Error.prototype)) { // $FlowFixMe this needs to become defined for Error to be serialized Object.defineProperty(Error.prototype, "toJSON", { // eslint-disable-line value: function value() { var alt = {}; Object.getOwnPropertyNames(this).forEach(function (key) { alt[key] = this[key]; }, this); return alt; }, configurable: true, writable: true }); } main(); function main() { try { if (_cluster2.default.isMaster) { var args = masterArgsParse(); masterRun(args); } else if (_cluster2.default.isWorker) { var _args = workerArgsParse(); workerRun(_args); } else { throw new Error("Not a master or a worker"); } } catch (e) { if (e instanceof ArgsParseError) { console.error("Illegal argument: %s.\n%s", e.message, usage()); } else { console.error(e); } _process2.default.exit(1); } return 0; } function usage() { return "Usage: " + _process2.default.argv[0] + " " + _process2.default.argv[1] + " " + EOL + "[--verbose] [--timeout ] [--bailAfter ] " + EOL + "[--cpuScale ] [--statusFile ] [--singleThreaded] [--relativeTestPath ]"; } function masterArgsParse() { var parsedArgs = (0, _minimist2.default)(_process2.default.argv.slice(2), { string: ["statusFile", "relativeTestPath"], boolean: ["verbose", "singleThreaded"], default: { verbose: _process2.default.stdout instanceof _tty2.default.WriteStream ? false : true, statusFile: "", timeout: 10, cpuScale: 1, bailAfter: Infinity, singleThreaded: false, relativeTestPath: "/../test/test262" } }); var filterString = parsedArgs._[0]; if (typeof parsedArgs.verbose !== "boolean") { throw new ArgsParseError("verbose must be a boolean (either --verbose or not)"); } if (typeof parsedArgs.timeout !== "number") { throw new ArgsParseError("timeout must be a number (in seconds) (--timeout 10)"); } if (typeof parsedArgs.bailAfter !== "number") { throw new ArgsParseError("bailAfter must be a number (--bailAfter 10)"); } if (typeof parsedArgs.cpuScale !== "number") { throw new ArgsParseError("cpuScale must be a number (--cpuScale 0.5)"); } if (typeof parsedArgs.statusFile !== "string") { throw new ArgsParseError("statusFile must be a string (--statusFile file.txt)"); } if (typeof parsedArgs.singleThreaded !== "boolean") { throw new ArgsParseError("singleThreaded must be a boolean (either --singleThreaded or not)"); } if (typeof parsedArgs.relativeTestPath !== "string") { throw new ArgsParseError("relativeTestPath must be a string (--relativeTestPath /../test/test262)"); } var programArgs = new MasterProgramArgs(parsedArgs.verbose, parsedArgs.timeout, parsedArgs.bailAfter, parsedArgs.cpuScale, parsedArgs.statusFile, filterString, parsedArgs.singleThreaded, parsedArgs.relativeTestPath); if (programArgs.filterString) { // if filterstring is provided, assume that verbosity is desired programArgs.verbose = true; } return programArgs; } function workerArgsParse() { var parsedArgs = (0, _minimist2.default)(_process2.default.argv.slice(2), { default: { timeout: 10, relativeTestPath: "/../test/test262" } }); if (typeof parsedArgs.timeout !== "number") { throw new ArgsParseError("timeout must be a number (in seconds) (--timeout 10)"); } if (typeof parsedArgs.relativeTestPath !== "string") { throw new ArgsParseError("relativeTestPath must be a string (--relativeTestPath /../test/test262)"); } return new WorkerProgramArgs(parsedArgs.timeout, parsedArgs.relativeTestPath); } function masterRun(args) { var testPath = "" + __dirname + args.relativeTestPath + "/test"; var tests = getFilesSync(testPath); // remove tests that don't need to be run if (args.filterString) tests = tests.filter(function (test) { return test.location.includes(args.filterString); }); var originalTestLength = tests.length; tests = tests.filter(function (test) { return testFilterByMetadata(test); }); var groups = {}; // Now that all the tasks are ready, start up workers to begin processing // if single threaded, use that route instead if (args.singleThreaded) { masterRunSingleProcess(args, groups, tests, originalTestLength - tests.length); } else { masterRunMultiProcess(args, groups, tests, originalTestLength - tests.length); } } function masterRunSingleProcess(args, groups, tests, numFiltered) { console.log("Running " + tests.length + " tests as a single process"); // print out every 5 percent (more granularity than multi-process because multi-process // runs a lot faster) var granularity = Math.floor(tests.length / 20); var harnesses = getHarnesses(args.relativeTestPath); var numLeft = tests.length; var _loop = function _loop(t) { handleTest(t, harnesses, args.timeout, function (err, results) { if (err) { if (args.verbose) { console.error(err); } } else { var ok = handleTestResults(groups, t, results); if (!ok) { // handleTestResults returns false if a failure threshold was exceeded throw new Error("Too many test failures"); } var progress = getProgressBar(numLeft, tests.length, granularity); if (progress) { console.log(progress); } } numLeft--; if (numLeft === 0) { // all done _process2.default.exit(handleFinished(args, groups, numFiltered)); } }); }; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = tests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var t = _step.value; _loop(t); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } function masterRunMultiProcess(args, groups, tests, numFiltered) { if (!_cluster2.default.on) { // stop flow errors on "cluster.on" throw new Error("cluster is malformed"); } var granularity = Math.floor(tests.length / 10); var originalTestLength = tests.length; // Fork workers. var numWorkers = Math.floor(numCPUs * args.cpuScale); console.log("Master starting up, forking " + numWorkers + " workers"); for (var i = 0; i < numWorkers; i++) { _cluster2.default.fork(); } var exitCount = 0; _cluster2.default.on("exit", function (worker, code, signal) { exitCount++; if (exitCount === numWorkers) { _process2.default.exit(handleFinished(args, groups, numFiltered)); } }); var giveTask = function giveTask(worker) { // grab another test to run and give it to the child process if (tests.length === 0) { worker.send(new QuitMessage()); } else { worker.send(new TestTask(tests.pop())); } }; _cluster2.default.on("message", function (worker, message, handle) { switch (message.type) { case ErrorMessage.sentinel: var errMsg = ErrorMessage.fromObject(message); // just skip the error, thus skipping that test if (args.verbose) { console.error("An error occurred in worker #" + worker.process.pid + ":"); console.error(errMsg.err); } giveTask(worker); break; case DoneMessage.sentinel: var done = DoneMessage.fromObject(message); var ok = handleTestResults(groups, done.test, done.testResults); if (!ok) { // bail killWorkers(_cluster2.default.workers); handleFinished(args, groups, numFiltered); _process2.default.exit(1); } giveTask(worker); var progress = getProgressBar(tests.length, originalTestLength, granularity); if (progress) { console.log(progress); } break; default: throw new Error("Master got an unexpected message: " + JSON.stringify(message)); } }); _cluster2.default.on("online", function (worker) { giveTask(worker); }); } function handleFinished(args, groups, earlierNumSkipped) { var numPassed = 0; var numPassedES5 = 0; var numPassedES6 = 0; var numFailed = 0; var numFailedES5 = 0; var numFailedES6 = 0; var numSkipped = earlierNumSkipped; var numTimeouts = 0; var failed_groups = []; for (var group in groups) { // count some totals var group_passed = 0; var group_failed = 0; var group_es5_passed = 0; var group_es5_failed = 0; var group_es6_passed = 0; var group_es6_failed = 0; var groupName = _path2.default.relative(_path2.default.join(__dirname, "..", "..", "test"), group); var msg = ""; var errmsg = ""; msg += groupName + ": "; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = groups[group][Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var t = _step2.value; var testName = _path2.default.relative(group, t.test.location); var all_passed = true; var was_skipped = true; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = t.result[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var testResult = _step3.value; was_skipped = false; if (!testResult.passed) { all_passed = false; if (args.verbose) { errmsg += create_test_message(testName, testResult.passed, testResult.err, t.test.isES6, testResult.strict) + EOL; } if (testResult.err && testResult.err.message === "Timed out") { numTimeouts++; } } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } if (was_skipped) { numSkipped++; } else if (all_passed) { group_passed++; if (t.test.isES6) { group_es6_passed++; } else { group_es5_passed++; } } else { group_failed++; if (t.test.isES6) { group_es6_failed++; } else { group_es5_failed++; } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } msg += "Passed: " + group_passed + " / " + (group_passed + group_failed) + " " + ("(" + toPercentage(group_passed, group_passed + group_failed) + "%) ") + _chalk2.default.yellow("(es5)") + (": " + group_es5_passed + " / ") + (group_es5_passed + group_es5_failed + " ") + ("(" + toPercentage(group_es5_passed, group_es5_passed + group_es5_failed) + "%) ") + _chalk2.default.yellow("(es6)") + (": " + group_es6_passed + " / ") + (group_es6_passed + group_es6_failed + " ") + ("(" + toPercentage(group_es6_passed, group_es6_passed + group_es6_failed) + "%)"); if (args.verbose) { console.log(msg); if (errmsg) { console.error(errmsg); } } if (group_es5_failed + group_es6_failed > 0) { failed_groups.push(msg); } numPassed += group_passed; numPassedES5 += group_es5_passed; numPassedES6 += group_es6_passed; numFailed += group_failed; numFailedES5 += group_es5_failed; numFailedES6 += group_es6_failed; } var status = "=== RESULTS ===" + EOL + ("Passes: " + numPassed + " / " + (numPassed + numFailed) + " ") + ("(" + toPercentage(numPassed, numPassed + numFailed) + "%)") + EOL + ("ES5 passes: " + numPassedES5 + " / " + (numPassedES5 + numFailedES5) + " ") + ("(" + toPercentage(numPassedES5, numPassedES5 + numFailedES5) + "%) ") + EOL + ("ES6 passes: " + numPassedES6 + " / " + (numPassedES6 + numFailedES6) + " ") + ("(" + toPercentage(numPassedES6, numPassedES6 + numFailedES6) + "%)") + EOL + ("Skipped: " + numSkipped) + EOL + ("Timeouts: " + numTimeouts) + EOL; console.log(status); if (failed_groups.length !== 0) { console.log("Groups with failures:"); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = failed_groups[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var groupMessage = _step4.value; console.log(groupMessage); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } if (args.statusFile) { _gracefulFs2.default.writeFileSync(args.statusFile, status); } // exit status if (!args.filterString && (numPassedES5 < 11738 || numPassedES6 < 3981 || numTimeouts > 0)) { console.error(_chalk2.default.red("Overall failure. Expected more tests to pass!")); return 1; } else { // use 0 to avoid the npm error messages return 0; } } function getProgressBar(currentTestLength, originalTestLength, granularity) { if (currentTestLength % granularity === 0 && currentTestLength !== 0) { // print out a percent of tests completed to keep the user informed return "Running... " + toPercentage(originalTestLength - currentTestLength, originalTestLength) + "%"; } else { return ""; } } // Returns false if test processing should stop. function handleTestResults(groups, test, testResults) { // test results are in, add it to its corresponding group if (!(test.groupName in groups)) { groups[test.groupName] = []; } groups[test.groupName].push({ test: test, result: testResults }); return true; } // $FlowFixMe cluster.Worker is marked as not exported by the node API by flow. function killWorkers(workers) { for (var workerID in workers) { workers[workerID].kill(); } } function toPercentage(x, total) { if (total === 0) { return 100; } return Math.floor(x / total * 100); } function create_test_message(name, success, err, isES6, isStrict) { var checkmark = _chalk2.default.green("\u2713"); var xmark = _chalk2.default.red("\u2717"); var msg = "\t"; msg += (success ? checkmark : xmark) + " "; msg += "" + (isES6 ? _chalk2.default.yellow("(es6) ") : "") + (isStrict ? "(strict)" : "(nostrict)") + ": " + name; if (!success) { (0, _invariant2.default)(err, "Error must be non null if success is false"); if (err.message) { // split the message by newline, add tabs, and join var parts = err.message.split(EOL); var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = parts[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var line = _step5.value; msg += EOL + ("\t\t" + line); } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } msg += EOL; } else if (err.stack) { msg += JSON.stringify(err.stack); } } return msg; } function getHarnesses(relativeTestPath) { var harnessPath = "" + __dirname + relativeTestPath + "/harness"; var harnessesList = getFilesSync(harnessPath); // convert to a mapping from harness name to file contents var harnesses = {}; var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = harnessesList[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var harness = _step6.value; // sync is fine, it's an initialization stage and there's not that many // harnesses harnesses[_path2.default.basename(harness.location)] = _gracefulFs2.default.readFileSync(harness.location).toString(); } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } return harnesses; } function workerRun(args) { // NOTE: all harnesses (including contents of harness files) need to be // used on workers. It needs to either be read from the fs once and // distributed via IPC or once from each process. This is the // "once from each process" approach. // get all the harnesses var harnesses = getHarnesses(args.relativeTestPath); // we're a worker, run a portion of the tests _process2.default.on("message", function (message) { switch (message.type) { case TestTask.sentinel: // begin executing this TestTask var task = TestTask.fromObject(message); handleTest(task.file, harnesses, args.timeout, function (err, results) { handleTestResultsMultiProcess(err, task.file, results); }); break; case QuitMessage.sentinel: _process2.default.exit(0); break; default: throw new Error("Worker #" + _process2.default.pid + " got an unexpected message:\n " + JSON.stringify(message)); } }); } function handleTestResultsMultiProcess(err, test, testResults) { if (err) { _process2.default.send(new ErrorMessage(err)); } else { var msg = new DoneMessage(test); var _iteratorNormalCompletion7 = true; var _didIteratorError7 = false; var _iteratorError7 = undefined; try { for (var _iterator7 = testResults[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) { var t = _step7.value; msg.testResults.push(t); } } catch (err) { _didIteratorError7 = true; _iteratorError7 = err; } finally { try { if (!_iteratorNormalCompletion7 && _iterator7.return) { _iterator7.return(); } } finally { if (_didIteratorError7) { throw _iteratorError7; } } } try { _process2.default.send(msg); } catch (jsonCircularSerializationErr) { // JSON circular serialization, ThrowCompletion is too deep to be // serialized! // Solution, truncate the "err" field if this happens var _iteratorNormalCompletion8 = true; var _didIteratorError8 = false; var _iteratorError8 = undefined; try { for (var _iterator8 = msg.testResults[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) { var _t = _step8.value; if (_t.err) { _t.err = new Error(_t.err.message); } } // now try again } catch (err) { _didIteratorError8 = true; _iteratorError8 = err; } finally { try { if (!_iteratorNormalCompletion8 && _iterator8.return) { _iterator8.return(); } } finally { if (_didIteratorError8) { throw _iteratorError8; } } } _process2.default.send(msg); } } } function handleTest(test, harnesses, timeout, cb) { prepareTest(test, testFilterByContents, function (err, banners, testFileContents) { if (err != null) { cb(err, []); return; } if (!banners) { // skip this test cb(null, []); } else { (0, _invariant2.default)(testFileContents, "testFileContents should not be null if banners are not None"); // filter out by flags, features, and includes var keepThisTest = filterFeatures(banners) && filterFlags(banners) && filterIncludes(banners) && filterDescription(banners) && filterCircleCI(banners) && filterSneakyGenerators(banners, testFileContents); var _testResults = []; if (keepThisTest) { // now run the test _testResults = runTestWithStrictness(test, testFileContents, banners, harnesses, timeout); } cb(null, _testResults); } }); } /** * FIXME: this code is unsound in the presence of ENOENT (file not found) * This function returns nested arrays of all the file names. It can be * flattened at the call site, but the type hint is incorrect. * DON'T USE THIS FUNCTION until it is fixed to behave exactly like getFilesSync */ /* function getFiles( filepath: string, ): Promise { return new Promise((resolve, reject) => { fs.stat(filepath, (err, stat) => { if (err !== null) { reject(err); } else { if (stat.isFile()) { // return an array of size 1 resolve([new TestFileInfo(filepath)]); } else if (stat.isDirectory()) { // recurse on its children fs.readdir(filepath, (err, files) => { if (err !== null) { reject(err); } else { // FIXME flattening bug // tmp is Promise[] (array of promises of arrays) // want to flatten that into Promise where each // promise is added to a single array let tmp = files.map(f => getFiles(path.join(filepath, f))); resolve(Promise.all(tmp)); } }); } } }); }); } */ /** * getFilesSync returns a TestFileInfo for each file that is underneath the * directory ${filepath}. If ${filepath} is just a file, then it returns an * array of size 1. * This function synchronously fetches from the filesystem, as such it should * only be used in initialization code that only runs once. */ function getFilesSync(filepath) { var stat = _gracefulFs2.default.statSync(filepath); if (stat.isFile()) { return [new TestFileInfo(filepath, false)]; } else if (stat.isDirectory()) { var subFiles = _gracefulFs2.default.readdirSync(filepath); return flatten(subFiles.map(function (f) { return getFilesSync(_path2.default.join(filepath, f)); })); } else { throw new Error("That type of file is not supported"); } } function flatten(arr) { return arr.reduce(function (a, b) { return a.concat(b); }, []); } /** * prepareTest opens the file corresponding to ${test} and calls ${cb} on the * results, expect the ones for which ${filterFn} returns false. * The value passed to ${cb} will be an error if the file could not be read, * or the banner data for the test if successful. * NOTE: if the test file contents match the filter function given, ${cb} will * not be called for that test. */ function prepareTest(test, filterFn, cb) { _gracefulFs2.default.readFile(test.location, function (err, contents) { if (err != null) { cb(err, null, null); } else { var contentsStr = contents.toString(); // check if this test should be filtered if (!filterFn(test, contentsStr)) { // skip this test cb(null, null, null); } else { try { var banners = getBanners(test, contentsStr); cb(null, banners, contentsStr); } catch (bannerParseErr) { cb(bannerParseErr, null, null); } } } }); } function createRealm(timeout) { // Create a new realm. var realm = (0, _construct_realm2.default)({ strictlyMonotonicDateNow: true, timeout: timeout * 1000 }); (0, _globals2.default)(realm); var executionContext = new _realm.ExecutionContext(); executionContext.realm = realm; realm.pushContext(executionContext); // Create the Host-Defined functions. var $ = new _index.ObjectValue(realm); $.defineNativeMethod("createRealm", 0, function (context) { return createRealm(timeout).$; }); $.defineNativeMethod("detachArrayBuffer", 1, function (context, _ref) { var _ref2 = _slicedToArray(_ref, 1), buffer = _ref2[0]; return (0, _arraybuffer.DetachArrayBuffer)(realm, buffer); }); $.defineNativeMethod("evalScript", 1, function (context, _ref3) { var _ref4 = _slicedToArray(_ref3, 1), sourceText = _ref4[0]; // TODO: eval return realm.intrinsics.undefined; }); $.defineNativeProperty("global", realm.$GlobalObject); var glob = realm.$GlobalObject; glob.defineNativeProperty("$262", $); glob.defineNativeMethod("print", 1, function (context, _ref5) { var _ref6 = _slicedToArray(_ref5, 1), arg = _ref6[0]; return realm.intrinsics.undefined; }); return { realm: realm, $: $ }; } /** * runTest executes the test given by ${test} whose contents are * ${testFileContents}. * It returns None if the test should is skipped, otherwise it returns a * TestResult. */ function runTest(test, testFileContents, data, // eslint-disable-next-line flowtype/no-weak-types harnesses, strict, timeout) { var _createRealm = createRealm(timeout), realm = _createRealm.realm; // Run the test. try { try { // execute the harnesss first var _iteratorNormalCompletion9 = true; var _didIteratorError9 = false; var _iteratorError9 = undefined; try { for (var _iterator9 = ["sta.js", "assert.js"].concat(data.includes || [])[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) { var name = _step9.value; var harness = harnesses[name]; var _completion = realm.$GlobalEnv.execute(harness, name); if (_completion instanceof _completions.ThrowCompletion) throw _completion; } } catch (err) { _didIteratorError9 = true; _iteratorError9 = err; } finally { try { if (!_iteratorNormalCompletion9 && _iterator9.return) { _iterator9.return(); } } finally { if (_didIteratorError9) { throw _iteratorError9; } } } var completion = realm.$GlobalEnv.execute((strict ? '"use strict";' + EOL : "") + testFileContents, test.location); if (completion instanceof _completions.ThrowCompletion) throw completion; if (completion instanceof _completions.AbruptCompletion) return new TestResult(false, strict, new Error("Unexpected abrupt completion")); } catch (err) { if (err.message === "Timed out") return new TestResult(false, strict, err); if (!data.negative || data.negative !== err.name) { throw err; } } if (data.negative.type) { throw new Error("Was supposed to error with type " + data.negative.type + " but passed"); } // succeeded return new TestResult(true, strict); } catch (err) { if (err.value && err.value.$Prototype && err.value.$Prototype.intrinsicName === "SyntaxError.prototype") { return null; } var stack = err.stack; if (data.negative.type) { var type = data.negative.type; if (err && err instanceof _completions.ThrowCompletion && (0, _get.Get)(realm, err.value, "name").value === type) { // Expected an error and got one. return new TestResult(true, strict); } else { // Expected an error, but got something else. if (err && err instanceof _completions.ThrowCompletion) { return new TestResult(false, strict, err); } else { return new TestResult(false, strict, new Error("Expected an error, but got something else: " + err.message)); } } } else { // Not expecting an error, but got one. try { if (err && err instanceof _completions.ThrowCompletion) { var interpreterStack = void 0; if (err.value instanceof _index.ObjectValue) { if (err.value.$HasProperty("stack")) { interpreterStack = _singletons.To.ToStringPartial(realm, (0, _get.Get)(realm, err.value, "stack")); } else { interpreterStack = _singletons.To.ToStringPartial(realm, (0, _get.Get)(realm, err.value, "message")); } // filter out if the error stack is due to async if (interpreterStack.includes("async ")) { return null; } } else if (err.value instanceof _index.StringValue) { interpreterStack = err.value.value; if (interpreterStack === "only plain identifiers are supported in parameter lists") { return null; } } // Many strict-only tests involving eval check if certain SyntaxErrors are thrown. // Some of those would require changes to Babel to support properly, and some we should handle ourselves in Prepack some day. // But for now, ignore. if (testFileContents.includes("eval(") && strict) { return null; } if (interpreterStack) { stack = "Interpreter: " + interpreterStack + EOL + "Native: " + err.nativeStack; } } } catch (_err) { stack = _err.stack; } return new TestResult(false, strict, new Error("Got an error, but was not expecting one:" + EOL + stack)); } } } /** * Returns true if ${test} should be run, false otherwise */ function testFilterByMetadata(test) { // filter hidden files if (_path2.default.basename(test.location)[0] === ".") return false; // emacs! if (test.location.includes("~")) return false; // SIMD isn't in JS yet if (test.location.includes("Simd")) return false; // temporarily disable intl402 tests (ES5) if (test.location.includes("intl402") && !test.location.includes("/Date/prototype/to")) { return false; } // temporarily disable tests which use realm. if (test.location.includes("realm")) return false; // temporarily disable tests which use with. (??) if (test.location.includes("/with/")) return false; // disable tests which use Atomics if (test.location.includes("/Atomics/")) return false; // disable tests which use generators if (test.location.includes("/generators/")) return false; if (test.location.includes("/yield/")) return false; // disable tests which use modules if (test.location.includes("/module-code/")) return false; // disable browser specific tests if (test.location.includes("/annexB/")) return false; // disable tail-call optimization tests if (test.location.includes("tco")) return false; // disable nasty unicode tests. if (test.location.includes("U180") || test.location.includes("u180") || test.location.includes("mongolian")) return false; // disable function toString tests. if (test.location.includes("Function/prototype/toString")) return false; // disable tests that check for detached-buffer-after-toindex if (test.location.includes("detached-buffer-after-toindex")) return false; // disable tests to check for detatched-buffer during iteration if (test.location.includes("detach-typedarray-in-progress.js")) return false; // disable broken RegExp tests if (test.location.includes("RegExp/S15.10.2.12_A1_T1.js")) return false; if (test.location.includes("RegExp/S15.10.2.12_A2_T1.js")) return false; if (test.location.includes("RegExp/prototype/Symbol.search/lastindex-no-restore")) return false; if (test.location.includes("RegExp/prototype/exec/failure-lastindex-no-access.js")) return false; if (test.location.includes("RegExp/prototype/exec/success-lastindex-no-access.js")) return false; // disable RegExp tests that use extended unicode if (test.location.includes("Symbol.match/builtin-success-u-return-val-groups")) return false; // disable SharedArrayBuffer tests if (test.location.includes("sharedarraybuffer") || test.location.includes("SharedArrayBuffer")) return false; return true; } function testFilterByContents(test, testFileContents) { // ES6 tests (can only be verified by contents, not by metadata) var is_es6 = testFileContents.includes(EOL + "es6id: "); test.isES6 = is_es6; // Ignore phase: early tests because those are errors that babel should catch // not issues related to Prepack var phase_early = testFileContents.indexOf(" phase: early"); var end_of_comment = testFileContents.indexOf("---*/"); if (phase_early > 0 && phase_early < end_of_comment) return false; var esid_pending = testFileContents.indexOf("esid: pending"); if (esid_pending > 0 && esid_pending < end_of_comment) return false; // disable tests that require parser to throw SyntaxError in strict Mode if (test.location.includes("/directive-prologue/") && testFileContents.includes("assert.throws(SyntaxError,")) return false; // disable SharedArrayBuffer tests if (testFileContents.includes("SharedArrayBuffer")) return false; return true; } function filterFlags(data) { return !data.flags.includes("async"); } function filterFeatures(data) { var features = data.features; if (features.includes("default-parameters")) return false; if (features.includes("generators")) return false; if (features.includes("generator")) return false; if (features.includes("BigInt")) return false; if (features.includes("class-fields")) return false; if (features.includes("async-iteration")) return false; if (features.includes("Function.prototype.toString")) return false; if (features.includes("SharedArrayBuffer")) return false; if (features.includes("cross-realm")) return false; if (features.includes("atomics")) return false; if (features.includes("u180e")) return false; if (features.includes("Symbol.isConcatSpreadable")) return false; if (features.includes("destructuring-binding")) return false; if (features.includes("IsHTMLDDA")) return false; if (features.includes("regexp-unicode-property-escapes")) return false; if (features.includes("regexp-named-groups")) return false; if (features.includes("regexp-lookbehind")) return false; if (features.includes("regexp-dotall")) return false; if (features.includes("optional-catch-binding")) return false; if (features.includes("Symbol.asyncIterator")) return false; if (features.includes("Promise.prototype.finally")) return false; return true; } function filterIncludes(data) { // disable tail call optimization tests. return !data.includes.includes("tco-helper.js"); } function filterDescription(data) { // For now, "Complex tests" is used in the description of some // encode/decodeURI tests to indicate that they are long running. // Filter these return !data.description.includes("Complex tests") && !data.description.includes("iterating") && !data.description.includes("iterable"); } function filterCircleCI(data) { var skipTests = ["7.8.5_A1.4_T2", "7.8.5_A2.4_T2", "7.8.5_A2.1_T2", "7.8.5_A1.1_T2", "15.1.2.2_A8", "15.1.2.3_A6", "7.4_A5", "7.4_A6", "15.10.2.12_A3_T1", "15.10.2.12_A4_T1", "15.10.2.12_A5_T1", "15.10.2.12_A6_T1"]; var skipTests6 = ["22.1.3.1_3"]; return !!_process2.default.env.NIGHTLY_BUILD || skipTests.indexOf(data.es5id) < 0 && skipTests6.indexOf(data.es6id) < 0; } function filterSneakyGenerators(data, testFileContents) { // There are some sneaky tests that use generators but are not labeled with // the "generators" or "generator" feature tag. Here we use a simple heuristic // to filter out tests with sneaky generators. if (data.features.includes("destructuring-binding")) { return !testFileContents.includes("function*") && !testFileContents.includes("*method"); } return true; } /** * Run a given ${test} whose file contents are ${testFileContents} and return * a list of results, one for each strictness level (strict or not). * If the list's length is less than 2, than the missing tests were skipped. */ function runTestWithStrictness(test, testFileContents, data, // eslint-disable-next-line flowtype/no-weak-types harnesses, timeout) { var fn = function fn(strict) { return runTest(test, testFileContents, data, harnesses, strict, timeout); }; if (data.flags.includes("onlyStrict")) { if (testFileContents.includes("assert.throws(SyntaxError")) return []; var _result = fn(true); return _result ? [_result] : []; } else if (data.flags.includes("noStrict") || test.location.includes("global/global-object.js")) { if (testFileContents.includes('"use strict";') && testFileContents.includes("assert.throws(SyntaxError")) return []; var _result2 = fn(false); return _result2 ? [_result2] : []; } else { // run both strict and non-strict var strictResult = fn(true); var unStrictResult = fn(false); var finalResult = []; if (strictResult) { finalResult.push(strictResult); } if (unStrictResult) { finalResult.push(unStrictResult); } return finalResult; } } /** * Parses the banners, and returns the banners as arbitrary object data if they * were found, or returns an error if the banner it couldn't be parsed. */ function getBanners(test, fileContents) { var banners = fileContents.match(/---[\s\S]+---/); var data = {}; if (banners) { var bannerText = banners[0] || ""; if (bannerText.includes("StrictMode")) { if (bannerText.includes("'arguments'")) return null; if (bannerText.includes("'caller'")) return null; } else if (bannerText.includes('properties "caller" or "arguments"')) { return null; } else if (bannerText.includes("function caller")) { return null; } else if (bannerText.includes("attribute of 'caller' property")) { return null; } else if (bannerText.includes("attribute of 'arguments'")) { return null; } else if (bannerText.includes("poisoned")) return null; try { data = _jsYaml2.default.safeLoad(banners[0].slice(3, -3)); } catch (e) { // Some versions of test262 have comments inside of yaml banners. // parsing these will usually fail. return null; } } return BannerData.fromObject(data); } //# sourceMappingURL=test262-runner.js.map