| #!/usr/bin/env python |
| # Copyright 2014 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import os |
| import tempfile |
| import unittest |
| |
| from compile import Checker |
| from processor import FileCache, Processor |
| |
| |
| _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| _SRC_DIR = os.path.join(_SCRIPT_DIR, os.pardir, os.pardir) |
| _RESOURCES_DIR = os.path.join(_SRC_DIR, "ui", "webui", "resources", "js") |
| _ASSERT_JS = os.path.join(_RESOURCES_DIR, "assert.js") |
| _CR_JS = os.path.join(_RESOURCES_DIR, "cr.js") |
| _CR_UI_JS = os.path.join(_RESOURCES_DIR, "cr", "ui.js") |
| _POLYMER_EXTERNS = os.path.join(_SRC_DIR, "third_party", "polymer", "v0_8", |
| "components-chromium", "polymer-externs", |
| "polymer.externs.js") |
| |
| |
| class CompilerTest(unittest.TestCase): |
| _ASSERT_DEFINITION = Processor(_ASSERT_JS).contents |
| _CR_DEFINE_DEFINITION = Processor(_CR_JS).contents |
| _CR_UI_DECORATE_DEFINITION = Processor(_CR_UI_JS).contents |
| |
| def setUp(self): |
| self._checker = Checker() |
| self._tmp_files = [] |
| |
| def tearDown(self): |
| for file in self._tmp_files: |
| if os.path.exists(file): |
| os.remove(file) |
| |
| def _runChecker(self, source_code, output_wrapper=None): |
| file_path = "/script.js" |
| FileCache._cache[file_path] = source_code |
| out_file, out_map = self._createOutFiles() |
| |
| found_errors, stderr = self._checker.check(file_path, |
| externs=[_POLYMER_EXTERNS], |
| out_file=out_file, |
| output_wrapper=output_wrapper) |
| return found_errors, stderr, out_file, out_map |
| |
| def _runCheckerTestExpectError(self, source_code, expected_error): |
| _, stderr, out_file, out_map = self._runChecker(source_code) |
| |
| self.assertTrue(expected_error in stderr, |
| msg="Expected chunk: \n%s\n\nOutput:\n%s\n" % ( |
| expected_error, stderr)) |
| self.assertFalse(os.path.exists(out_file)) |
| self.assertFalse(os.path.exists(out_map)) |
| |
| def _runCheckerTestExpectSuccess(self, source_code, expected_output=None, |
| output_wrapper=None): |
| found_errors, stderr, out_file, out_map = self._runChecker(source_code, |
| output_wrapper) |
| |
| self.assertFalse(found_errors, |
| msg="Expected success, but got failure\n\nOutput:\n%s\n" % stderr) |
| |
| self.assertTrue(os.path.exists(out_map)) |
| self.assertTrue(os.path.exists(out_file)) |
| if expected_output: |
| with open(out_file, "r") as file: |
| self.assertEquals(file.read(), expected_output) |
| |
| def _createOutFiles(self): |
| out_file = tempfile.NamedTemporaryFile(delete=False) |
| out_map = "%s.map" % out_file.name |
| |
| self._tmp_files.append(out_file.name) |
| self._tmp_files.append(out_map) |
| return out_file.name, out_map |
| |
| def testGetInstance(self): |
| self._runCheckerTestExpectError(""" |
| var cr = { |
| /** @param {!Function} ctor */ |
| addSingletonGetter: function(ctor) { |
| ctor.getInstance = function() { |
| return ctor.instance_ || (ctor.instance_ = new ctor()); |
| }; |
| } |
| }; |
| |
| /** @constructor */ |
| function Class() { |
| /** @param {number} num */ |
| this.needsNumber = function(num) {}; |
| } |
| |
| cr.addSingletonGetter(Class); |
| Class.getInstance().needsNumber("wrong type"); |
| """, "ERROR - actual parameter 1 of Class.needsNumber does not match formal " |
| "parameter") |
| |
| def testCrDefineFunctionDefinition(self): |
| self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """ |
| cr.define('a.b.c', function() { |
| /** @param {number} num */ |
| function internalName(num) {} |
| |
| return { |
| needsNumber: internalName |
| }; |
| }); |
| |
| a.b.c.needsNumber("wrong type"); |
| """, "ERROR - actual parameter 1 of a.b.c.needsNumber does not match formal " |
| "parameter") |
| |
| def testCrDefineFunctionAssignment(self): |
| self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """ |
| cr.define('a.b.c', function() { |
| /** @param {number} num */ |
| var internalName = function(num) {}; |
| |
| return { |
| needsNumber: internalName |
| }; |
| }); |
| |
| a.b.c.needsNumber("wrong type"); |
| """, "ERROR - actual parameter 1 of a.b.c.needsNumber does not match formal " |
| "parameter") |
| |
| def testCrDefineConstructorDefinitionPrototypeMethod(self): |
| self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """ |
| cr.define('a.b.c', function() { |
| /** @constructor */ |
| function ClassInternalName() {} |
| |
| ClassInternalName.prototype = { |
| /** @param {number} num */ |
| method: function(num) {} |
| }; |
| |
| return { |
| ClassExternalName: ClassInternalName |
| }; |
| }); |
| |
| new a.b.c.ClassExternalName().method("wrong type"); |
| """, "ERROR - actual parameter 1 of a.b.c.ClassExternalName.prototype.method " |
| "does not match formal parameter") |
| |
| def testCrDefineConstructorAssignmentPrototypeMethod(self): |
| self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """ |
| cr.define('a.b.c', function() { |
| /** @constructor */ |
| var ClassInternalName = function() {}; |
| |
| ClassInternalName.prototype = { |
| /** @param {number} num */ |
| method: function(num) {} |
| }; |
| |
| return { |
| ClassExternalName: ClassInternalName |
| }; |
| }); |
| |
| new a.b.c.ClassExternalName().method("wrong type"); |
| """, "ERROR - actual parameter 1 of a.b.c.ClassExternalName.prototype.method " |
| "does not match formal parameter") |
| |
| def testCrDefineEnum(self): |
| self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """ |
| cr.define('a.b.c', function() { |
| /** @enum {string} */ |
| var internalNameForEnum = {key: 'wrong_type'}; |
| |
| return { |
| exportedEnum: internalNameForEnum |
| }; |
| }); |
| |
| /** @param {number} num */ |
| function needsNumber(num) {} |
| |
| needsNumber(a.b.c.exportedEnum.key); |
| """, "ERROR - actual parameter 1 of needsNumber does not match formal " |
| "parameter") |
| |
| def testObjectDefineProperty(self): |
| self._runCheckerTestExpectSuccess(""" |
| /** @constructor */ |
| function Class() {} |
| |
| Object.defineProperty(Class.prototype, 'myProperty', {}); |
| |
| alert(new Class().myProperty); |
| """) |
| |
| def testCrDefineProperty(self): |
| self._runCheckerTestExpectSuccess(self._CR_DEFINE_DEFINITION + """ |
| /** @constructor */ |
| function Class() {} |
| |
| cr.defineProperty(Class.prototype, 'myProperty', cr.PropertyKind.JS); |
| |
| alert(new Class().myProperty); |
| """) |
| |
| def testCrDefinePropertyTypeChecking(self): |
| self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """ |
| /** @constructor */ |
| function Class() {} |
| |
| cr.defineProperty(Class.prototype, 'booleanProp', cr.PropertyKind.BOOL_ATTR); |
| |
| /** @param {number} num */ |
| function needsNumber(num) {} |
| |
| needsNumber(new Class().booleanProp); |
| """, "ERROR - actual parameter 1 of needsNumber does not match formal " |
| "parameter") |
| |
| def testCrDefineOnCrWorks(self): |
| self._runCheckerTestExpectSuccess(self._CR_DEFINE_DEFINITION + """ |
| cr.define('cr', function() { |
| return {}; |
| }); |
| """) |
| |
| def testAssertWorks(self): |
| self._runCheckerTestExpectSuccess(self._ASSERT_DEFINITION + """ |
| /** @return {?string} */ |
| function f() { |
| return "string"; |
| } |
| |
| /** @type {!string} */ |
| var a = assert(f()); |
| """) |
| |
| def testAssertInstanceofWorks(self): |
| self._runCheckerTestExpectSuccess(self._ASSERT_DEFINITION + """ |
| /** @constructor */ |
| function Class() {} |
| |
| /** @return {Class} */ |
| function f() { |
| var a = document.createElement('div'); |
| return assertInstanceof(a, Class); |
| } |
| """) |
| |
| def testCrUiDecorateWorks(self): |
| self._runCheckerTestExpectSuccess(self._CR_DEFINE_DEFINITION + |
| self._CR_UI_DECORATE_DEFINITION + """ |
| /** @constructor */ |
| function Class() {} |
| |
| /** @return {Class} */ |
| function f() { |
| var a = document.createElement('div'); |
| cr.ui.decorate(a, Class); |
| return a; |
| } |
| """) |
| |
| def testValidScriptCompilation(self): |
| self._runCheckerTestExpectSuccess(""" |
| var testScript = function() { |
| console.log("hello world") |
| }; |
| """, |
| """'use strict';var testScript=function(){console.log("hello world")};\n""") |
| |
| def testOutputWrapper(self): |
| source_code = """ |
| var testScript = function() { |
| console.log("hello world"); |
| }; |
| """ |
| expected_output = ("""(function(){'use strict';var testScript=function()""" |
| """{console.log("hello world")};})();\n""") |
| output_wrapper="(function(){%output%})();" |
| self._runCheckerTestExpectSuccess(source_code, expected_output, |
| output_wrapper=output_wrapper) |
| |
| def testCheckMultiple(self): |
| source_file1 = tempfile.NamedTemporaryFile(delete=False) |
| with open(source_file1.name, "w") as f: |
| f.write(""" |
| goog.provide('testScript'); |
| |
| var testScript = function() {}; |
| """) |
| self._tmp_files.append(source_file1.name) |
| |
| source_file2 = tempfile.NamedTemporaryFile(delete=False) |
| with open(source_file2.name, "w") as f: |
| f.write(""" |
| goog.require('testScript'); |
| |
| testScript(); |
| """) |
| self._tmp_files.append(source_file2.name) |
| |
| out_file, out_map = self._createOutFiles() |
| sources = [source_file1.name, source_file2.name] |
| externs = [_POLYMER_EXTERNS] |
| found_errors, stderr = self._checker.check_multiple(sources, |
| externs=externs, |
| out_file=out_file) |
| self.assertFalse(found_errors, |
| msg="Expected success, but got failure\n\nOutput:\n%s\n" % stderr) |
| |
| expected_output = "'use strict';var testScript=function(){};testScript();\n" |
| self.assertTrue(os.path.exists(out_map)) |
| self.assertTrue(os.path.exists(out_file)) |
| with open(out_file, "r") as file: |
| self.assertEquals(file.read(), expected_output) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |