#!/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. from ast import literal_eval 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", "v1_0", "components-chromium", "polymer-externs", "polymer.externs.js") _CHROME_SEND_EXTERNS = os.path.join(_SRC_DIR, "third_party", "closure_compiler", "externs", "chrome_send.js") _CLOSURE_ARGS_GYPI = os.path.join(_SCRIPT_DIR, "closure_args.gypi") _GYPI_DICT = literal_eval(open(_CLOSURE_ARGS_GYPI).read()) _COMMON_CLOSURE_ARGS = _GYPI_DICT["closure_args"] + \ _GYPI_DICT["default_disabled_closure_args"] 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, closure_args=None): file_path = "/script.js" FileCache._cache[file_path] = source_code out_file, out_map = self._createOutFiles() args = _COMMON_CLOSURE_ARGS + (closure_args or []) externs = [_POLYMER_EXTERNS, _CHROME_SEND_EXTERNS] found_errors, stderr = self._checker.check(file_path, externs=externs, out_file=out_file, closure_args=args) 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, closure_args=None): found_errors, stderr, out_file, out_map = self._runChecker(source_code, closure_args) 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""") closure_args=["output_wrapper='(function(){%output%})();'"] self._runCheckerTestExpectSuccess(source_code, expected_output, closure_args) 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, closure_args=_COMMON_CLOSURE_ARGS) 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()