aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2020-02-17 15:35:07 +0000
committerGitHub <noreply@github.com>2020-02-17 15:35:07 +0000
commit53517db3e40e9a2b2a6cc64b0dd7dcef0305ec1e (patch)
tree1e9e4cc8aa519f8a569b1628dee6924393dd6814
parentc13caf487614d15cdc7d984fc5b4c558e728f0cf (diff)
downloadtracifyjs-53517db3e40e9a2b2a6cc64b0dd7dcef0305ec1e.tar.gz
tracifyjs-53517db3e40e9a2b2a6cc64b0dd7dcef0305ec1e.zip
speed up `--reduce-test` (#3726)
- avoid pathological test case branches via adaptive time-out - use initial test case elapsed time to adjust maximum time-out - index output cache using hash instead of raw source
-rw-r--r--test/mocha/reduce.js32
-rw-r--r--test/reduce.js93
2 files changed, 86 insertions, 39 deletions
diff --git a/test/mocha/reduce.js b/test/mocha/reduce.js
index f55c278d..c7d5a7de 100644
--- a/test/mocha/reduce.js
+++ b/test/mocha/reduce.js
@@ -2,6 +2,7 @@ var assert = require("assert");
var exec = require("child_process").exec;
var fs = require("fs");
var reduce_test = require("../reduce");
+var semver = require("semver");
function read(path) {
return fs.readFileSync(path, "utf8");
@@ -43,7 +44,6 @@ describe("test/reduce.js", function() {
"// {",
'// "toplevel": true',
"// }",
- "",
].join("\n"));
});
it("Should handle test result of NaN", function() {
@@ -55,7 +55,6 @@ describe("test/reduce.js", function() {
'// "compress": {},',
'// "mangle": false',
"// }",
- "",
].join("\n"));
});
it("Should print correct output for irreducible test case", function() {
@@ -136,4 +135,33 @@ describe("test/reduce.js", function() {
"// }",
].join("\n"));
});
+ it("Should reduce infinite loops with reasonable performance", function() {
+ if (semver.satisfies(process.version, "0.10")) return;
+ this.timeout(120000);
+ var code = [
+ "var a = 9007199254740992, b = 1;",
+ "",
+ "while (a++ + (1 - b) < a) {",
+ " 0;",
+ "}",
+ ].join("\n");
+ var result = reduce_test(code, {
+ compress: {
+ unsafe_math: true,
+ },
+ mangle: false,
+ });
+ if (result.error) throw result.error;
+ assert.strictEqual(result.code.replace(/ timed out after [0-9]+ms/, " timed out."), [
+ code,
+ "// output: ",
+ "// minify: Error: Script execution timed out.",
+ "// options: {",
+ '// "compress": {',
+ '// "unsafe_math": true',
+ "// },",
+ '// "mangle": false',
+ "// }",
+ ].join("\n"));
+ });
});
diff --git a/test/reduce.js b/test/reduce.js
index 0b31ea8e..0d803aad 100644
--- a/test/reduce.js
+++ b/test/reduce.js
@@ -1,3 +1,4 @@
+var crypto = require("crypto");
var U = require("./node");
var List = U.List;
var sandbox = require("./sandbox");
@@ -17,21 +18,32 @@ var sandbox = require("./sandbox");
// Returns a `minify` result object with an additonal boolean property `reduced`.
module.exports = function reduce_test(testcase, minify_options, reduce_options) {
+ if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
minify_options = minify_options || { compress: {}, mangle: false };
reduce_options = reduce_options || {};
var max_iterations = reduce_options.max_iterations || 1000;
- var max_timeout = reduce_options.max_timeout || 15000;
+ var max_timeout = reduce_options.max_timeout || 10000;
var verbose = reduce_options.verbose;
var minify_options_json = JSON.stringify(minify_options, null, 2);
- var timeout = 1000; // start with a low timeout
var result_cache = Object.create(null);
- var differs;
-
- if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
-
// the initial timeout to assess the viability of the test case must be large
- if (differs = producesDifferentResultWhenMinified(result_cache, testcase, minify_options, max_timeout)) {
- if (differs.error) return differs;
+ var differs = producesDifferentResultWhenMinified(result_cache, testcase, minify_options, max_timeout);
+
+ if (!differs) {
+ // same stdout result produced when minified
+ return {
+ code: "// Can't reproduce test failure with minify options provided:"
+ + "\n// " + to_comment(minify_options_json)
+ };
+ } else if (differs.timed_out) {
+ return {
+ code: "// Can't reproduce test failure within " + max_timeout + "ms:"
+ + "\n// " + to_comment(minify_options_json)
+ };
+ } else if (differs.error) {
+ return differs;
+ } else {
+ max_timeout = Math.min(100 * differs.elapsed, max_timeout);
// Replace expressions with constants that will be parsed into
// AST_Nodes as required. Each AST_Node has its own permutation count,
// so these replacements can't be shared.
@@ -400,15 +412,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
console.error("*** Discarding permutation and continuing.");
continue;
}
- var diff = producesDifferentResultWhenMinified(result_cache, code, minify_options, timeout);
+ var diff = producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout);
if (diff) {
if (diff.timed_out) {
// can't trust the validity of `code_ast` and `code` when timed out.
// no harm done - just ignore latest change and continue iterating.
- if (timeout < max_timeout) {
- timeout += 250;
- result_cache = Object.create(null);
- }
} else if (diff.error) {
// something went wrong during minify() - could be malformed AST or genuine bug.
// no harm done - just log code & error, ignore latest change and continue iterating.
@@ -433,23 +441,23 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
}
}
- testcase += "\n// output: " + to_comment(differs.unminified_result)
- + "\n// minify: " + to_comment(differs.minified_result)
- + "\n// options: " + to_comment(minify_options_json);
- } else {
- // same stdout result produced when minified
- testcase = "// Can't reproduce test failure with minify options provided:"
- + "\n// " + to_comment(minify_options_json);
+ testcase = U.minify(testcase, {
+ compress: false,
+ mangle: false,
+ output: {
+ beautify: true,
+ braces: true,
+ comments: true,
+ },
+ });
+ testcase.code += [
+ "",
+ "// output: " + to_comment(differs.unminified_result),
+ "// minify: " + to_comment(differs.minified_result),
+ "// options: " + to_comment(minify_options_json),
+ ].join("\n").replace(/\u001b\[\d+m/g, "");
+ return testcase;
}
- return U.minify(testcase.replace(/\u001b\[\d+m/g, ""), {
- compress: false,
- mangle: false,
- output: {
- beautify: true,
- braces: true,
- comments: true,
- }
- });
};
function to_comment(value) {
@@ -489,6 +497,10 @@ function is_error(result) {
return typeof result == "object" && typeof result.name == "string" && typeof result.message == "string";
}
+function is_timed_out(result) {
+ return is_error(result) && /timed out/.test(result);
+}
+
function is_statement(node) {
return node instanceof U.AST_Statement && !(node instanceof U.AST_Function);
}
@@ -519,23 +531,30 @@ function to_statement(node) {
}
function run_code(result_cache, code, toplevel, timeout) {
- return result_cache[code] || (result_cache[code] = sandbox.run_code(code, toplevel, timeout));
+ var key = crypto.createHash("sha1").update(code).digest("base64");
+ return result_cache[key] || (result_cache[key] = sandbox.run_code(code, toplevel, timeout));
}
-function producesDifferentResultWhenMinified(result_cache, code, minify_options, timeout) {
+function producesDifferentResultWhenMinified(result_cache, code, minify_options, max_timeout) {
var minified = U.minify(code, minify_options);
if (minified.error) return minified;
var toplevel = minify_options.toplevel;
- var unminified_result = run_code(result_cache, code, toplevel, timeout);
- if (/timed out/i.test(unminified_result)) return false;
-
+ var elapsed = Date.now();
+ var unminified_result = run_code(result_cache, code, toplevel, max_timeout);
+ elapsed = Date.now() - elapsed;
+ var timeout = Math.min(100 * elapsed, max_timeout);
var minified_result = run_code(result_cache, minified.code, toplevel, timeout);
- if (/timed out/i.test(minified_result)) return { timed_out: true };
- return !sandbox.same_stdout(unminified_result, minified_result) ? {
+ if (sandbox.same_stdout(unminified_result, minified_result)) {
+ return is_timed_out(unminified_result) && is_timed_out(minified_result) && {
+ timed_out: true,
+ };
+ }
+ return {
unminified_result: unminified_result,
minified_result: minified_result,
- } : false;
+ elapsed: elapsed,
+ };
}
Error.stackTraceLimit = Infinity;