improve `--reduce-test` (#3742)
authorAlex Lam S.L <alexlamsl@gmail.com>
Fri, 20 Mar 2020 21:50:41 +0000 (21:50 +0000)
committerGitHub <noreply@github.com>
Fri, 20 Mar 2020 21:50:41 +0000 (05:50 +0800)
- ignore difference in error messages
- improve readability on trailing whitespace differences
- improve performance & quality via `console.log()` insertions

test/input/reduce/label.reduced.js
test/input/reduce/setter.reduced.js
test/input/reduce/unsafe_math.reduced.js
test/mocha/reduce.js
test/reduce.js

index faaa206..a1e7950 100644 (file)
@@ -1,8 +1,7 @@
+// (beautified)
 var o = this;
 
-for (var k in o) {
-    0;
-}
+for (var k in o) {}
 
 var a;
 
index d947226..e0a3c62 100644 (file)
@@ -1,3 +1,4 @@
+// (beautified)
 console.log(function f(a) {
     ({
         set p(v) {
index 1346042..5e1f0ba 100644 (file)
@@ -1,3 +1,4 @@
+// (beautified)
 var b = 0;
 
 var expr2 = (0 - 1 - .1 - .1).toString();
index a8ab67b..fb3989d 100644 (file)
@@ -53,8 +53,8 @@ describe("test/reduce.js", function() {
         });
         if (result.error) throw result.error;
         assert.strictEqual(result.code, [
-            "// Can't reproduce test failure with minify options provided:",
-            "// {",
+            "// Can't reproduce test failure",
+            "// minify options: {",
             '//   "toplevel": true',
             "// }",
         ].join("\n"));
@@ -70,8 +70,8 @@ describe("test/reduce.js", function() {
         });
         if (result.error) throw result.error;
         assert.strictEqual(result.code, [
-            "// Can't reproduce test failure with minify options provided:",
-            "// {",
+            "// Can't reproduce test failure",
+            "// minify options: {",
             '//   "compress": {',
             '//     "toplevel": true',
             "//   }",
@@ -89,8 +89,8 @@ describe("test/reduce.js", function() {
         });
         if (result.error) throw result.error;
         assert.strictEqual(result.code, [
-            "// Can't reproduce test failure with minify options provided:",
-            "// {",
+            "// Can't reproduce test failure",
+            "// minify options: {",
             '//   "mangle": {',
             '//     "toplevel": true',
             "//   }",
@@ -101,11 +101,8 @@ describe("test/reduce.js", 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',
-            "// }",
+            "// Can't reproduce test failure",
+            "// minify options: {}",
         ].join("\n"));
     });
     it("Should print correct output for irreducible test case", function() {
@@ -121,6 +118,7 @@ describe("test/reduce.js", function() {
         });
         if (result.error) throw result.error;
         assert.strictEqual(result.code, [
+            "// (beautified)",
             "console.log(function f(a) {",
             "    return f.length;",
             "}());",
@@ -169,6 +167,7 @@ describe("test/reduce.js", function() {
         });
         if (result.error) throw result.error;
         assert.strictEqual(result.code, [
+            "// (beautified)",
             code,
             "// output: 0.8",
             "// 1.6",
@@ -189,11 +188,41 @@ describe("test/reduce.js", function() {
     it("Should reduce infinite loops with reasonable performance", function() {
         if (semver.satisfies(process.version, "0.10")) return;
         this.timeout(120000);
+        var result = reduce_test("while (/9/.test(1 - .8));", {
+            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."), [
+            "// (beautified)",
+            "while (/9/.test(1 - .8)) {}",
+            "// output: Error: Script execution timed out.",
+            "// minify: ",
+            "// options: {",
+            '//   "compress": {',
+            '//     "unsafe_math": true',
+            "//   },",
+            '//   "mangle": false',
+            "// }",
+        ].join("\n"));
+    });
+    it("Should ignore difference in Error.message", function() {
+        var result = reduce_test("null[function() {\n}];");
+        if (result.error) throw result.error;
+        assert.strictEqual(result.code, (semver.satisfies(process.version, "0.10") ? [
+            "// Can't reproduce test failure",
+            "// minify options: {}",
+        ] : [
+            "// No differences except in error message",
+            "// minify options: {}",
+        ]).join("\n"));
+    });
+    it("Should report trailing whitespace difference in stringified format", function() {
         var code = [
-            "var a = 9007199254740992, b = 1;",
-            "",
-            "while (a++ + (1 - b) < a) {",
-            "    0;",
+            "for (var a in (1 - .8).toString()) {",
+            "    console.log();",
             "}",
         ].join("\n");
         var result = reduce_test(code, {
@@ -203,14 +232,16 @@ describe("test/reduce.js", function() {
             mangle: false,
         });
         if (result.error) throw result.error;
-        assert.strictEqual(result.code.replace(/ timed out after [0-9]+ms/, " timed out."), [
+        assert.strictEqual(result.code, [
+            "// (beautified)",
             code,
-            "// output: ",
-            "// minify: Error: Script execution timed out.",
+            "// (stringified)",
+            '// output: "\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"',
+            '// minify: "\\n\\n\\n"',
             "// options: {",
             '//   "compress": {',
             '//     "unsafe_math": true',
-            "//   },",
+            '//   },',
             '//   "mangle": false',
             "// }",
         ].join("\n"));
index f9e0825..7d35896 100644 (file)
@@ -20,7 +20,7 @@ var sandbox = require("./sandbox");
 
 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 };
+    minify_options = minify_options || {};
     reduce_options = reduce_options || {};
     var max_iterations = reduce_options.max_iterations || 1000;
     var max_timeout = reduce_options.max_timeout || 10000;
@@ -36,16 +36,29 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
     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)
+            code: [
+                "// Can't reproduce test failure",
+                "// minify options: " + to_comment(minify_options_json)
+            ].join("\n")
         };
     } else if (differs.timed_out) {
         return {
-            code: "// Can't reproduce test failure within " + max_timeout + "ms:"
-                + "\n// " + to_comment(minify_options_json)
+            code: [
+                "// Can't reproduce test failure within " + max_timeout + "ms",
+                "// minify options: " + to_comment(minify_options_json)
+            ].join("\n")
         };
     } else if (differs.error) {
         return differs;
+    } else if (is_error(differs.unminified_result)
+        && is_error(differs.minified_result)
+        && differs.unminified_result.name == differs.minified_result.name) {
+        return {
+            code: [
+                "// No differences except in error message",
+                "// minify options: " + to_comment(minify_options_json)
+            ].join("\n")
+        };
     } else {
         max_timeout = Math.min(100 * differs.elapsed, max_timeout);
         // Replace expressions with constants that will be parsed into
@@ -71,6 +84,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
             // quick ignores
             if (node instanceof U.AST_Accessor) return;
             if (node instanceof U.AST_Directive) return;
+            if (!in_list && node instanceof U.AST_EmptyStatement) return;
             if (node instanceof U.AST_Label) return;
             if (node instanceof U.AST_LabelRef) return;
             if (!in_list && node instanceof U.AST_SymbolDeclaration) return;
@@ -112,10 +126,25 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
             }
             else if (node instanceof U.AST_Binary) {
                 CHANGED = true;
-                return [
+                var permute = ((node.start._permute += step) * steps | 0) % 4;
+                var expr = [
                     node.left,
                     node.right,
-                ][ ((node.start._permute += step) * steps | 0) % 2 ];
+                ][ permute & 1 ];
+                if (permute < 2) return expr;
+                // wrap with console.log()
+                return new U.AST_Call({
+                    expression: new U.AST_Dot({
+                        expression: new U.AST_SymbolRef({
+                            name: "console",
+                            start: {},
+                        }),
+                        property: "log",
+                        start: {},
+                    }),
+                    args: [ expr ],
+                    start: {},
+                });
             }
             else if (node instanceof U.AST_Catch || node instanceof U.AST_Finally) {
                 // drop catch or finally block
@@ -357,15 +386,11 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
             }
 
             // replace this node
-            var newNode = U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
+            var newNode = is_statement(node) ? new U.AST_EmptyStatement({
+                start: {},
+            }) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
                 expression: true,
             });
-            if (is_statement(node)) {
-                newNode = new U.AST_SimpleStatement({
-                    body: newNode,
-                    start: {},
-                });
-            }
             newNode.start._permute = ++node.start._permute;
             CHANGED = true;
             return newNode;
@@ -445,29 +470,62 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
                 console.error("// reduce test pass " + pass + ": " + testcase.length + " bytes");
             }
         }
-        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, "");
+        testcase = try_beautify(result_cache, testcase, minify_options, differs.unminified_result, max_timeout);
+        var lines = [ "" ];
+        var unminified_result = strip_color_codes(differs.unminified_result);
+        var minified_result = strip_color_codes(differs.minified_result);
+        if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
+            lines.push(
+                "// (stringified)",
+                "// output: " + JSON.stringify(unminified_result),
+                "// minify: " + JSON.stringify(minified_result)
+            );
+        } else {
+            lines.push(
+                "// output: " + to_comment(unminified_result),
+                "// minify: " + to_comment(minified_result)
+            );
+        }
+        lines.push("// options: " + to_comment(minify_options_json));
+        testcase.code += lines.join("\n");
         return testcase;
     }
 };
 
+function strip_color_codes(value) {
+    return ("" + value).replace(/\u001b\[\d+m/g, "");
+}
+
 function to_comment(value) {
     return ("" + value).replace(/\n/g, "\n// ");
 }
 
+function trim_trailing_whitespace(value) {
+    return ("" + value).replace(/\s+$/, "");
+}
+
+function try_beautify(result_cache, testcase, minify_options, expected, timeout) {
+    var result = U.minify(testcase, {
+        compress: false,
+        mangle: false,
+        output: {
+            beautify: true,
+            braces: true,
+            comments: true,
+        },
+    });
+    if (result.error) return {
+        code: testcase,
+    };
+    var toplevel = sandbox.has_toplevel(minify_options);
+    var actual = run_code(result_cache, result.code, toplevel, timeout);
+    if (!sandbox.same_stdout(expected, actual)) return {
+        code: testcase,
+    };
+    result.code = "// (beautified)\n" + result.code;
+    return result;
+}
+
 function has_exit(fn) {
     var found = false;
     var tw = new U.TreeWalker(function(node) {