prepack/scripts/test-error-handler.js
Herman Venter 80b0757f67 Allow required modules to throw conditionally
Summary:
Instead of delaying modules that throw conditionally, let the exception bubble up to the require call and then forget it (after emitting a warning). This allows more global state to be optimized and should be OK if it is understood that throwing an unhandled exception in module initialization code is not a supported scenario.

Probably, the temporal point where the require call happens should contain a conditional throw statement, which would be equivalent to current behavior. For now, this causes invariants to fire in the serializer, probably because of bugs in how the state at the time of the exception is restored and presented to the throw statement.

It is also an option to let the exception escape the require call itself and possibly bubble all the way to the top level. This would be more correct than the current behavior since it should match the runtime behavior of the unprepacked code. This too is currently buggy. It also a bit of performance concern because it uses much more saved state.
Closes https://github.com/facebook/prepack/pull/1104

Differential Revision: D6189032

Pulled By: hermanventer

fbshipit-source-id: 71c6352eddca4d88ae030fdcbfb76e7a39839e73
2017-10-30 13:00:01 -07:00

132 lines
3.8 KiB
JavaScript

/**
* 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.
*/
/* @flow */
import { type CompilerDiagnostic, type ErrorHandlerResult, FatalError } from "../lib/errors.js";
import { prepackFileSync } from "../lib/prepack-node.js";
import invariant from "../lib/invariant.js";
let chalk = require("chalk");
let path = require("path");
let fs = require("fs");
function search(dir, relative) {
let tests = [];
if (fs.existsSync(dir)) {
for (let name of fs.readdirSync(dir)) {
let loc = path.join(dir, name);
let stat = fs.statSync(loc);
if (stat.isFile()) {
tests.push({
file: fs.readFileSync(loc, "utf8"),
name: path.join(relative, name),
});
} else if (stat.isDirectory()) {
tests = tests.concat(search(loc, path.join(relative, name)));
}
}
}
return tests;
}
let tests = search(`${__dirname}/../test/error-handler`, "test/error-handler");
function errorHandler(
retval: ErrorHandlerResult,
errors: Array<CompilerDiagnostic>,
error: CompilerDiagnostic
): ErrorHandlerResult {
errors.push(error);
return retval;
}
function runTest(name: string, code: string): boolean {
console.log(chalk.inverse(name));
let recover = code.includes("// recover-from-errors");
let additionalFunctions = code.includes("// additional functions");
let delayUnsupportedRequires = code.includes("// delay unsupported requires");
let expectedErrors = code.match(/\/\/\s*expected errors:\s*(.*)/);
invariant(expectedErrors);
invariant(expectedErrors.length > 1);
expectedErrors = expectedErrors[1];
expectedErrors = eval(expectedErrors); // eslint-disable-line no-eval
invariant(expectedErrors.constructor === Array);
let errors = [];
try {
let options = {
internalDebug: false,
delayUnsupportedRequires,
mathRandomSeed: "0",
errorHandler: errorHandler.bind(null, recover ? "Recover" : "Fail", errors),
serialize: true,
initializeMoreModules: false,
};
if (additionalFunctions) (options: any).additionalFunctions = ["global.additional1", "global['additional2']"];
prepackFileSync([name], options);
if (!recover) {
console.log(chalk.red("Serialization succeeded though it should have failed"));
return false;
}
} catch (e) {
if (!(e instanceof FatalError)) {
console.log(chalk.red(`Unexpected error: ${e.message}`));
}
}
if (errors.length !== expectedErrors.length) {
console.log(chalk.red(`Expected ${expectedErrors.length} errors, but found ${errors.length}`));
return false;
}
for (let i = 0; i < expectedErrors.length; ++i) {
for (let prop in expectedErrors[i]) {
let expected = expectedErrors[i][prop];
let actual = (errors[i]: any)[prop];
if (prop === "location") {
if (actual) delete actual.filename;
actual = JSON.stringify(actual);
expected = JSON.stringify(expected);
}
if (expected !== actual) {
console.log(chalk.red(`Error ${i + 1}: Expected ${expected} errors, but found ${actual}`));
return false;
}
}
}
return true;
}
function run() {
let failed = 0;
let passed = 0;
let total = 0;
for (let test of tests) {
// filter hidden files
if (path.basename(test.name)[0] === ".") continue;
if (test.name.endsWith("~")) continue;
total++;
if (runTest(test.name, test.file)) passed++;
else failed++;
}
console.log("Passed:", `${passed}/${total}`, (Math.round(passed / total * 100) || 0) + "%");
return failed === 0;
}
if (!run()) process.exit(1);