diff options
Diffstat (limited to 'dom/system/gonk/tests/marionette/test_ril_code_quality.py')
-rw-r--r-- | dom/system/gonk/tests/marionette/test_ril_code_quality.py | 371 |
1 files changed, 0 insertions, 371 deletions
diff --git a/dom/system/gonk/tests/marionette/test_ril_code_quality.py b/dom/system/gonk/tests/marionette/test_ril_code_quality.py deleted file mode 100644 index d741d8a2e..000000000 --- a/dom/system/gonk/tests/marionette/test_ril_code_quality.py +++ /dev/null @@ -1,371 +0,0 @@ -""" -The test performs the static code analysis check by JSHint. - -Target js files: -- RadioInterfaceLayer.js -- ril_worker.js -- ril_consts.js - -If the js file contains the line of 'importScript()' (Ex: ril_worker.js), the -test will perform a special merge step before excuting JSHint. - -Ex: Script A --------------------------------- -importScripts('Script B') -... --------------------------------- - -We merge these two scripts into one by the following way. - --------------------------------- -[Script B (ex: ril_consts.js)] -(function(){ [Script A (ex: ril_worker.js)] -})(); --------------------------------- - -Script A (ril_worker.js) runs global strict mode. -Script B (ril_consts.js) not. - -The above merge way ensures the correct scope of 'strict mode.' -""" - -import bisect -import inspect -import os -import os.path -import re -import unicodedata - -from marionette_harness import MarionetteTestCase - - -class StringUtility: - - """A collection of some string utilities.""" - - @staticmethod - def find_match_lines(lines, pattern): - """Return a list of lines that contains given pattern.""" - return [line for line in lines if pattern in line] - - @staticmethod - def remove_non_ascii(data): - """Remove non ascii characters in data and return it as new string.""" - if type(data).__name__ == 'unicode': - data = unicodedata.normalize( - 'NFKD', data).encode('ascii', 'ignore') - return data - - @staticmethod - def auto_close(lines): - """Ensure every line ends with '\n'.""" - if lines and not lines[-1].endswith('\n'): - lines[-1] += '\n' - return lines - - @staticmethod - def auto_wrap_strict_mode(lines): - """Wrap by function scope if lines contain 'use strict'.""" - if StringUtility.find_match_lines(lines, 'use strict'): - lines[0] = '(function(){' + lines[0] - lines.append('})();\n') - return lines - - @staticmethod - def get_imported_list(lines): - """Get a list of imported items.""" - return [item - for line in StringUtility.find_match_lines(lines, 'importScripts') - for item in StringUtility._get_imported_list_from_line(line)] - - @staticmethod - def _get_imported_list_from_line(line): - """Extract all items from 'importScripts(...)'. - - importScripts("ril_consts.js", "systemlibs.js") - => ['ril_consts', 'systemlibs.js'] - - """ - pattern = re.compile(r'\s*importScripts\((.*)\)') - m = pattern.match(line) - if not m: - raise Exception('Parse importScripts error.') - return [name.translate(None, '\' "') for name in m.group(1).split(',')] - - -class ResourceUriFileReader: - - """Handle the process of reading the source code from system.""" - - URI_PREFIX = 'resource://gre/' - URI_PATH = { - 'RadioInterfaceLayer.js': 'components/RadioInterfaceLayer.js', - 'ril_worker.js': 'modules/ril_worker.js', - 'ril_consts.js': 'modules/ril_consts.js', - 'systemlibs.js': 'modules/systemlibs.js', - 'worker_buf.js': 'modules/workers/worker_buf.js', - } - - CODE_OPEN_CHANNEL_BY_URI = ''' - var Cc = Components.classes; - var Ci = Components.interfaces; - var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); - var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); - global.uri = '%(uri)s'; - global.channel = ios.newChannel2(global.uri, - null, - null, - null, // aLoadingNode - secMan.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); - ''' - - CODE_GET_SPEC = ''' - return global.channel.URI.spec; - ''' - - CODE_READ_CONTENT = ''' - var Cc = Components.classes; - var Ci = Components.interfaces; - - var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); - var inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); - - var jaruri = global.channel.URI.QueryInterface(Ci.nsIJARURI); - var file = jaruri.JARFile.QueryInterface(Ci.nsIFileURL).file; - var entry = jaruri.JAREntry; - zipReader.open(file); - inputStream.init(zipReader.getInputStream(entry)); - var content = inputStream.read(inputStream.available()); - inputStream.close(); - zipReader.close(); - return content; - ''' - - @classmethod - def get_uri(cls, filename): - """Convert filename to URI in system.""" - if filename.startswith(cls.URI_PREFIX): - return filename - else: - return cls.URI_PREFIX + cls.URI_PATH[filename] - - def __init__(self, marionette): - self.runjs = lambda x: marionette.execute_script(x, - new_sandbox=False, - sandbox='system') - - def read_file(self, filename): - """Read file and return the contents as string.""" - content = self._read_uri(self.get_uri(filename)) - content = content.replace('"use strict";', '') - return StringUtility.remove_non_ascii(content) - - def _read_uri(self, uri): - """Read URI in system and return the contents as string.""" - # Open the uri as a channel. - self.runjs(self.CODE_OPEN_CHANNEL_BY_URI % {'uri': uri}) - - # Make sure spec is a jar uri, and not recursive. - # Ex: 'jar:file:///system/b2g/omni.ja!/modules/ril_worker.js' - # - # For simplicity, we don't handle other special cases in this test. - # If B2G build system changes in the future, such as put the jar in - # another jar, the test case will fail. - spec = self.runjs(self.CODE_GET_SPEC) - if (not spec.startswith('jar:file://')) or (spec.count('jar:') != 1): - raise Exception('URI resolve error') - - # Read the content from channel. - content = self.runjs(self.CODE_READ_CONTENT) - return content - - -class JSHintEngine: - - """Invoke jshint script on system.""" - - CODE_INIT_JSHINT = ''' - %(script)s; - global.JSHINT = JSHINT; - global.options = JSON.parse(%(config_string)s); - global.globals = global.options.globals; - delete global.options.globals; - ''' - - CODE_RUN_JSHINT = ''' - global.script = %(code)s; - return global.JSHINT(global.script, global.options, global.globals); - ''' - - CODE_GET_JSHINT_ERROR = ''' - return global.JSHINT.errors; - ''' - - def __init__(self, marionette, script, config): - # Remove single line comment in config. - config = '\n'.join([line.partition('//')[0] - for line in config.splitlines()]) - - # Set global (JSHINT, options, global) in js environment. - self.runjs = lambda x: marionette.execute_script(x, - new_sandbox=False, - sandbox='system') - self.runjs(self.CODE_INIT_JSHINT % - {'script': script, 'config_string': repr(config)}) - - def run(self, code, filename=''): - """Excute JShint check for the given code.""" - check_pass = self.runjs(self.CODE_RUN_JSHINT % {'code': repr(code)}) - errors = self.runjs(self.CODE_GET_JSHINT_ERROR) - return check_pass, self._get_error_messages(errors, filename) - - def _get_error_messages(self, errors, filename=''): - """ - Convert an error object to a list of readable string. - - [{"a": null, "c": null, "code": "W033", "d": null, "character": 6, - "evidence": "var a", "raw": "Missing semicolon.", - "reason": "Missing semicolon.", "b": null, "scope": "(main)", "line": 1, - "id": "(error)"}] - => line 1, col 6, Missing semicolon. - - """ - LINE, COL, REASON = u'line', u'character', u'reason' - return ["%s: line %s, col %s, %s" % - (filename, error[LINE], error[COL], error[REASON]) - for error in errors if error] - - -class Linter: - - """Handle the linting related process.""" - - def __init__(self, code_reader, jshint, reporter=None): - """Set the linter with code_reader, jshint engine, and reporter. - - Should have following functionality. - - code_reader.read_file(filename) - - jshint.run(code, filename) - - reporter([...]) - - """ - self.code_reader = code_reader - self.jshint = jshint - if reporter is None: - self.reporter = lambda x: '\n'.join(x) - else: - self.reporter = reporter - - def lint_file(self, filename): - """Lint the file and return (pass, error_message).""" - # Get code contents. - code = self.code_reader.read_file(filename) - lines = code.splitlines() - import_list = StringUtility.get_imported_list(lines) - if not import_list: - check_pass, error_message = self.jshint.run(code, filename) - else: - newlines, info = self._merge_multiple_codes(filename, import_list) - # Each line of |newlines| contains '\n'. - check_pass, error_message = self.jshint.run(''.join(newlines)) - error_message = self._convert_merged_result(error_message, info) - # Only keep errors for this file. - error_message = [line for line in error_message - if line.startswith(filename)] - check_pass = (len(error_message) == 0) - return check_pass, self.reporter(error_message) - - def _merge_multiple_codes(self, filename, import_list): - """Merge multiple codes from filename and import_list.""" - dirname, filename = os.path.split(filename) - dst_line = 1 - dst_results = [] - info = [] - - # Put the imported script first, and then the original script. - for f in import_list + [filename]: - filepath = os.path.join(dirname, f) - - # Maintain a mapping table. - # New line number after merge => original file and line number. - info.append((dst_line, filepath, 1)) - try: - code = self.code_reader.read_file(filepath) - lines = code.splitlines(True) # Keep '\n'. - src_results = StringUtility.auto_wrap_strict_mode( - StringUtility.auto_close(lines)) - dst_results.extend(src_results) - dst_line += len(src_results) - except: - info.pop() - return dst_results, info - - def _convert_merged_result(self, error_lines, line_info): - pattern = re.compile(r'(.*): line (\d+),(.*)') - start_line = [info[0] for info in line_info] - new_result_lines = [] - for line in error_lines: - m = pattern.match(line) - if not m: - continue - - line_number, remain = int(m.group(2)), m.group(3) - - # [1, 2, 7, 8] - # ^ for 7, pos = 3 - # ^ for 6, pos = 2 - pos = bisect.bisect_right(start_line, line_number) - dst_line, name, src_line = line_info[pos - 1] - real_line_number = line_number - dst_line + src_line - new_result_lines.append( - "%s: line %s,%s" % (name, real_line_number, remain)) - return new_result_lines - - -class TestRILCodeQuality(MarionetteTestCase): - - JSHINT_PATH = 'ril_jshint/jshint.js' - JSHINTRC_PATH = 'ril_jshint/jshintrc' - - def _read_local_file(self, filepath): - """Read file content from local (folder of this test case).""" - test_dir = os.path.dirname(inspect.getfile(TestRILCodeQuality)) - return open(os.path.join(test_dir, filepath)).read() - - def _get_extended_error_message(self, error_message): - return '\n'.join(['See errors below and more information in Bug 880643', - '\n'.join(error_message), - 'See errors above and more information in Bug 880643']) - - def _check(self, filename): - check_pass, error_message = self.linter.lint_file(filename) - self.assertTrue(check_pass, error_message) - - def setUp(self): - MarionetteTestCase.setUp(self) - self.linter = Linter( - ResourceUriFileReader(self.marionette), - JSHintEngine(self.marionette, - self._read_local_file(self.JSHINT_PATH), - self._read_local_file(self.JSHINTRC_PATH)), - self._get_extended_error_message) - - def tearDown(self): - MarionetteTestCase.tearDown(self) - - def test_RadioInterfaceLayer(self): - self._check('RadioInterfaceLayer.js') - - # Bug 936504. Disable the test for 'ril_worker.js'. It sometimes runs very - # slow and causes the timeout fail on try server. - #def test_ril_worker(self): - # self._check('ril_worker.js') - - def test_ril_consts(self): - self._check('ril_consts.js') - - def test_worker_buf(self): - self._check('worker_buf.js') |