fix corner cases in `test/reduce` (#3709)
authorAlex Lam S.L <alexlamsl@gmail.com>
Fri, 7 Feb 2020 02:41:07 +0000 (02:41 +0000)
committerGitHub <noreply@github.com>
Fri, 7 Feb 2020 02:41:07 +0000 (02:41 +0000)
test/mocha/reduce.js [new file with mode: 0644]
test/reduce.js
test/sandbox.js

diff --git a/test/mocha/reduce.js b/test/mocha/reduce.js
new file mode 100644 (file)
index 0000000..99fc794
--- /dev/null
@@ -0,0 +1,60 @@
+var assert = require("assert");
+var exec = require("child_process").exec;
+var reduce_test = require("../reduce");
+
+describe("test/reduce.js", function() {
+    it("Should handle test cases with --toplevel", function() {
+        var result = reduce_test([
+            "var Infinity = 42;",
+            "console.log(Infinity);",
+        ].join("\n"), {
+            toplevel: true,
+        });
+        if (result.error) throw result.error;
+        assert.strictEqual(result.code, [
+            "// Can't reproduce test failure with minify options provided:",
+            '// {"toplevel":true}',
+            "",
+        ].join("\n"));
+    });
+    it("Should handle test result of NaN", function() {
+        var result = reduce_test("throw 0 / 0;");
+        if (result.error) throw result.error;
+        assert.strictEqual(result.code, [
+            "// Can't reproduce test failure with minify options provided:",
+            '// {"compress":{},"mangle":false}',
+            "",
+        ].join("\n"));
+    });
+    it("Should print correct output for irreducible test case", function() {
+        var result = reduce_test([
+            "console.log(function f(a) {",
+            "    return f.length;",
+            "}());",
+        ].join("\n"), {
+            compress: {
+                keep_fargs: false,
+            },
+            mangle: false,
+        });
+        if (result.error) throw result.error;
+        assert.strictEqual(result.code, [
+            "console.log(function f(a) {",
+            "    return f.length;",
+            "}());",
+            "// output: 1",
+            "// minify: 0",
+            '// options: {"compress":{"keep_fargs":false},"mangle":false}',
+        ].join("\n"));
+    });
+    it("Should fail when invalid option is supplied", function() {
+        var result = reduce_test("", {
+            compress: {
+                unsafe_regex: true,
+            },
+        });
+        var err = result.error;
+        assert.ok(err instanceof Error);
+        assert.strictEqual(err.stack.split(/\n/)[0], "DefaultsError: `unsafe_regex` is not a supported option");
+    });
+});
index 40e57de..a54bd1a 100644 (file)
@@ -1,6 +1,6 @@
 var U = require("./node");
 var List = U.List;
-var run_code = require("./sandbox").run_code;
+var sandbox = require("./sandbox");
 
 // Reduce a ufuzz-style `console.log` based test case by iteratively replacing
 // AST nodes with various permutations. Each AST_Statement in the tree is also
@@ -19,16 +19,18 @@ var run_code = require("./sandbox").run_code;
 module.exports = function reduce_test(testcase, minify_options, reduce_options) {
     minify_options = minify_options || { compress: {}, mangle: false };
     reduce_options = reduce_options || {};
-    var timeout = 1500; // start with a low timeout
     var max_iterations = reduce_options.max_iterations || 1000;
+    var max_timeout = reduce_options.max_timeout || 15000;
     var verbose = reduce_options.verbose;
     var minify_options_json = JSON.stringify(minify_options);
-    var reduced = false;
+    var timeout = 1000; // start with a low timeout
+    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 (producesDifferentResultWhenMinified(testcase, minify_options, 5000)) {
+    if (differs = producesDifferentResultWhenMinified(testcase, minify_options, max_timeout)) {
+        if (differs.error) return differs;
         // 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.
@@ -323,18 +325,17 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                     console.error("*** Discarding permutation and continuing.");
                 }
                 if (code) {
-                    var differs = producesDifferentResultWhenMinified(code, minify_options, timeout);
-                    if (differs) {
-                        if (differs.timed_out) {
+                    var diff = producesDifferentResultWhenMinified(code, minify_options, 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 < 5000) timeout += 100;
+                            if (timeout < max_timeout) timeout += 250;
                         } else {
                             // latest permutation is valid, so use it as the basis of new changes
                             testcase_ast = code_ast;
                             testcase = code;
-                            var testcase_unminified_result = differs.unminified_result;
-                            var testcase_minified_result = differs.minified_result;
+                            differs = diff;
                         }
                     }
                 }
@@ -344,16 +345,15 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                 console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
             }
         }
-        reduced = true;
-        testcase += "\n// output: " + testcase_unminified_result
-            + "\n// minify: " + testcase_minified_result
+        testcase += "\n// output: " + differs.unminified_result
+            + "\n// minify: " + differs.minified_result
             + "\n// options: " + minify_options_json;
     } else {
         // same stdout result produced when minified
         testcase = "// Can't reproduce test failure with minify options provided:"
             + "\n// " + minify_options_json;
     }
-    var result = U.minify(testcase, {
+    var result = U.minify(testcase.replace(/\u001b\[\d+m/g, ""), {
         compress: false,
         mangle: false,
         output: {
@@ -362,21 +362,22 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
             comments: true,
         }
     });
-    result.reduced = reduced;
     return result;
 };
 
 function producesDifferentResultWhenMinified(code, minify_options, timeout) {
-    var toplevel = undefined;
-    var unminified_result = run_code(code, toplevel, timeout);
+    var minified = U.minify(code, minify_options);
+    if (minified.error) return minified;
+    var toplevel = minify_options.toplevel;
+    var unminified_result = sandbox.run_code(code, toplevel, timeout);
     if (/timed out/i.test(unminified_result)) return false;
     if (/^\s*$|Error/.test(unminified_result)) return false;
 
-    var minified_result = run_code(U.minify(code, minify_options).code, toplevel, timeout);
+    var minified_result = sandbox.run_code(minified.code, toplevel, timeout);
     if (/timed out/i.test(minified_result)) return { timed_out: true };
     if (/^\s*$/.test(minified_result)) return false;
 
-    return unminified_result !== minified_result ? {
+    return !sandbox.same_stdout(unminified_result, minified_result) ? {
         unminified_result: unminified_result,
         minified_result: minified_result,
     } : false;
index 32599c0..ef77d2a 100644 (file)
@@ -77,8 +77,9 @@ function strip_func_ids(text) {
 
 exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
     if (typeof expected != typeof actual) return false;
-    if (typeof expected != "string") {
-        if (expected.name != actual.name) return false;
+    if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
+        if (expected.name !== actual.name) return false;
+        if (typeof actual.message != "string") return false;
         expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
         actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1);
     }