From b467a3c877c8a953bf945d892a5c1bdb9573336c Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Sun, 3 Aug 2014 20:48:59 +0300 Subject: [PATCH] Added generative testing for AST conversions. --- lib/mozilla-ast.js | 11 +++-- package.json | 12 ++++-- test/mozilla-ast.js | 103 ++++++++++++++++++++++++++++++++++++++++++++ test/run-tests.js | 6 +++ 4 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 test/mozilla-ast.js diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index 7f0995c0..602ef0e6 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -303,12 +303,11 @@ }); def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) { - var key; - if (is_identifier_string(M.key) && !RESERVED_WORDS(M.key)) { - key = {type: "Identifier", name: M.key}; - } else { - key = {type: "Literal", value: M.key}; - } + var key = ( + is_identifier(M.key) + ? {type: "Identifier", name: M.key} + : {type: "Literal", value: M.key} + ); var kind; if (M instanceof AST_ObjectKeyVal) { kind = "init"; diff --git a/package.json b/package.json index 26d8f766..90d587fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "uglify-js", - "description": "JavaScript parser, mangler/compressor and beautifier toolkit", + "name": "uglify-js", + "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", "version": "2.4.15", @@ -17,9 +17,15 @@ "dependencies": { "async" : "~0.2.6", "source-map" : "0.1.34", - "optimist" : "~0.3.5", + "optimist": "~0.3.5", "uglify-to-browserify": "~1.0.0" }, + "devDependencies": { + "acorn": "~0.6.0", + "escodegen": "~1.3.3", + "esfuzz": "~0.3.1", + "estraverse": "~1.5.1" + }, "browserify": { "transform": [ "uglify-to-browserify" ] }, diff --git a/test/mozilla-ast.js b/test/mozilla-ast.js new file mode 100644 index 00000000..02628676 --- /dev/null +++ b/test/mozilla-ast.js @@ -0,0 +1,103 @@ +// Testing UglifyJS <-> SpiderMonkey AST conversion +// through generative testing. + +var UglifyJS = require(".."), + escodegen = require("escodegen"), + esfuzz = require("esfuzz"), + estraverse = require("estraverse"), + prefix = Array(20).join("\b") + " "; + +// Normalizes input AST for UglifyJS in order to get correct comparison. + +function normalizeInput(ast) { + return estraverse.replace(ast, { + enter: function(node, parent) { + switch (node.type) { + // Internally mark all the properties with semi-standard type "Property". + case "ObjectExpression": + node.properties.forEach(function (property) { + property.type = "Property"; + }); + break; + + // Since UglifyJS doesn"t recognize different types of property keys, + // decision on SpiderMonkey node type is based on check whether key + // can be valid identifier or not - so we do in input AST. + case "Property": + var key = node.key; + if (key.type === "Literal" && typeof key.value === "string" && UglifyJS.is_identifier(key.value)) { + node.key = { + type: "Identifier", + name: key.value + }; + } else if (key.type === "Identifier" && !UglifyJS.is_identifier(key.name)) { + node.key = { + type: "Literal", + value: key.name + }; + } + break; + + // UglifyJS internally flattens all the expression sequences - either + // to one element (if sequence contains only one element) or flat list. + case "SequenceExpression": + node.expressions = node.expressions.reduce(function flatten(list, expr) { + return list.concat(expr.type === "SequenceExpression" ? expr.expressions.reduce(flatten, []) : [expr]); + }, []); + if (node.expressions.length === 1) { + return node.expressions[0]; + } + break; + } + } + }); +} + +module.exports = function(options) { + console.log("--- UglifyJS <-> Mozilla AST conversion"); + + for (var counter = 0; counter < options.iterations; counter++) { + process.stdout.write(prefix + counter + "/" + options.iterations); + + var ast1 = normalizeInput(esfuzz.generate({ + maxDepth: options.maxDepth + })); + + var ast2 = + UglifyJS + .AST_Node + .from_mozilla_ast(ast1) + .to_mozilla_ast(); + + var astPair = [ + {name: 'expected', value: ast1}, + {name: 'actual', value: ast2} + ]; + + var jsPair = astPair.map(function(item) { + return { + name: item.name, + value: escodegen.generate(item.value) + } + }); + + if (jsPair[0].value !== jsPair[1].value) { + var fs = require("fs"); + var acorn = require("acorn"); + + fs.existsSync("tmp") || fs.mkdirSync("tmp"); + + jsPair.forEach(function (item) { + var fileName = "tmp/dump_" + item.name; + var ast = acorn.parse(item.value); + fs.writeFileSync(fileName + ".js", item.value); + fs.writeFileSync(fileName + ".json", JSON.stringify(ast, null, 2)); + }); + + process.stdout.write("\n"); + throw new Error("Got different outputs, check out tmp/dump_*.{js,json} for codes and ASTs."); + } + } + + process.stdout.write(prefix + "Probability of error is less than " + (100 / options.iterations) + "%, stopping.\n"); +}; \ No newline at end of file diff --git a/test/run-tests.js b/test/run-tests.js index f8e88d48..94bf6ad9 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -17,6 +17,12 @@ if (failures) { process.exit(1); } +var run_ast_conversion_tests = require("./mozilla-ast"); + +run_ast_conversion_tests({ + iterations: 1000 +}); + /* -----[ utils ]----- */ function tmpl() { -- 2.34.1