diff options
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/style')
6 files changed, 361 insertions, 12 deletions
diff --git a/WebKitTools/Scripts/webkitpy/style/checker.py b/WebKitTools/Scripts/webkitpy/style/checker.py index e3c56c5..ee33003 100644 --- a/WebKitTools/Scripts/webkitpy/style/checker.py +++ b/WebKitTools/Scripts/webkitpy/style/checker.py @@ -38,6 +38,7 @@ from checkers.common import categories as CommonCategories from checkers.common import CarriageReturnChecker from checkers.cpp import CppChecker from checkers.python import PythonChecker +from checkers.test_expectations import TestExpectationsChecker from checkers.text import TextChecker from error_handlers import DefaultStyleErrorHandler from filter import FilterConfiguration @@ -234,6 +235,7 @@ def _all_categories(): """Return the set of all categories used by check-webkit-style.""" # Take the union across all checkers. categories = CommonCategories.union(CppChecker.categories) + categories = categories.union(TestExpectationsChecker.categories) # FIXME: Consider adding all of the pep8 categories. Since they # are not too meaningful for documentation purposes, for @@ -399,10 +401,15 @@ class CheckerDispatcher(object): # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make # an exception to prevent files like "LayoutTests/ChangeLog" and # "LayoutTests/ChangeLog-2009-06-16" from being skipped. + # Files like 'test_expectations.txt' and 'drt_expectations.txt' + # are also should not be skipped. # # FIXME: Figure out a good way to avoid having to add special logic # for this special case. - if os.path.basename(file_path).startswith('ChangeLog'): + basename = os.path.basename(file_path) + if basename.startswith('ChangeLog'): + return False + elif basename == 'test_expectations.txt' or basename == 'drt_expectations.txt': return False for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING: if file_path.find(skipped_file) >= 0: @@ -442,7 +449,11 @@ class CheckerDispatcher(object): elif file_type == FileType.PYTHON: checker = PythonChecker(file_path, handle_style_error) elif file_type == FileType.TEXT: - checker = TextChecker(file_path, handle_style_error) + basename = os.path.basename(file_path) + if basename == 'test_expectations.txt' or basename == 'drt_expectations.txt': + checker = TestExpectationsChecker(file_path, handle_style_error) + else: + checker = TextChecker(file_path, handle_style_error) else: raise ValueError('Invalid file type "%(file_type)s": the only valid file types ' "are %(NONE)s, %(CPP)s, and %(TEXT)s." diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/common.py b/WebKitTools/Scripts/webkitpy/style/checkers/common.py index a2d933f..76aa956 100644 --- a/WebKitTools/Scripts/webkitpy/style/checkers/common.py +++ b/WebKitTools/Scripts/webkitpy/style/checkers/common.py @@ -30,7 +30,7 @@ # into a shared location and refactoring appropriately. categories = set([ "whitespace/carriage_return", -]) + "whitespace/tab"]) class CarriageReturnChecker(object): @@ -55,3 +55,20 @@ class CarriageReturnChecker(object): lines[line_number] = lines[line_number].rstrip("\r") return lines + + +class TabChecker(object): + + """Supports checking for and handling tabs.""" + + def __init__(self, file_path, handle_style_error): + self.file_path = file_path + self.handle_style_error = handle_style_error + + def check(self, lines): + # FIXME: share with cpp_style. + for line_number, line in enumerate(lines): + if "\t" in line: + self.handle_style_error(line_number + 1, + "whitespace/tab", 5, + "Line contains tab character.") diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py index b67b7b0..1fe1263 100644 --- a/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py +++ b/WebKitTools/Scripts/webkitpy/style/checkers/common_unittest.py @@ -25,7 +25,7 @@ import unittest from common import CarriageReturnChecker - +from common import TabChecker # FIXME: The unit tests for the cpp, text, and common checkers should # share supporting test code. This can include, for example, the @@ -92,3 +92,33 @@ class CarriageReturnCheckerTest(unittest.TestCase): self.assert_carriage_return(["line1", "line2\r", "line3\r"], ["line1", "line2", "line3"], [2, 3]) + + +class TabCheckerTest(unittest.TestCase): + + """Tests for TabChecker.""" + + def assert_tab(self, input_lines, error_lines): + """Assert when the given lines contain tabs.""" + self._error_lines = [] + + def style_error_handler(line_number, category, confidence, message): + self.assertEqual(category, 'whitespace/tab') + self.assertEqual(confidence, 5) + self.assertEqual(message, 'Line contains tab character.') + self._error_lines.append(line_number) + + checker = TabChecker('', style_error_handler) + checker.check(input_lines) + self.assertEquals(self._error_lines, error_lines) + + def test_notab(self): + self.assert_tab([''], []) + self.assert_tab(['foo', 'bar'], []) + + def test_tab(self): + self.assert_tab(['\tfoo'], [1]) + self.assert_tab(['line1', '\tline2', 'line3\t'], [2, 3]) + +if __name__ == '__main__': + unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py new file mode 100644 index 0000000..ddc3983 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations.py @@ -0,0 +1,124 @@ +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Checks WebKit style for test_expectations files.""" + +import logging +import os +import re +import sys + +from common import TabChecker +from webkitpy.style_references import port +from webkitpy.style_references import test_expectations + +_log = logging.getLogger("webkitpy.style.checkers.test_expectations") + + +class ChromiumOptions(object): + """A mock object for creating chromium port object. + + port.get() requires an options object which has 'chromium' attribute to create + chromium port object for each platform. This class mocks such object. + """ + def __init__(self): + self.chromium = True + self.use_drt = True + + +class TestExpectationsChecker(object): + """Processes test_expectations.txt lines for validating the syntax.""" + + categories = set(['test/expectations']) + + def __init__(self, file_path, handle_style_error): + self._file_path = file_path + self._handle_style_error = handle_style_error + self._tab_checker = TabChecker(file_path, handle_style_error) + self._output_regex = re.compile('Line:(?P<line>\d+)\s*(?P<message>.+)') + # Determining the port of this expectations. + try: + port_name = self._file_path.split(os.sep)[-2] + if port_name == "chromium": + options = ChromiumOptions() + self._port_obj = port.get(port_name=None, options=options) + else: + self._port_obj = port.get(port_name=port_name) + except: + # Using 'test' port when we couldn't determine the port for this + # expectations. + _log.warn("Could not determine the port for %s. " + "Using 'test' port, but platform-specific expectations " + "will fail the check." % self._file_path) + self._port_obj = port.get('test') + self._port_to_check = self._port_obj.test_platform_name() + # Suppress error messages of test_expectations module since they will be + # reported later. + log = logging.getLogger("webkitpy.layout_tests.layout_package." + "test_expectations") + log.setLevel(logging.CRITICAL) + + def _handle_error_message(self, lineno, message, confidence): + pass + + def check_test_expectations(self, expectations_str, tests=None, overrides=None): + errors = [] + expectations = None + try: + expectations = test_expectations.TestExpectationsFile( + port=self._port_obj, expectations=expectations_str, full_test_list=tests, + test_platform_name=self._port_to_check, is_debug_mode=False, + is_lint_mode=True, suppress_errors=False, tests_are_present=True, + overrides=overrides) + except SyntaxError, error: + errors = str(error).splitlines() + + for error in errors: + matched = self._output_regex.match(error) + if matched: + lineno, message = matched.group('line', 'message') + self._handle_style_error(int(lineno), 'test/expectations', 5, message) + + if expectations: + for error in expectations.get_non_fatal_errors(): + matched = self._output_regex.match(error) + if matched: + lineno, message = matched.group('line', 'message') + self._handle_style_error(int(lineno), 'test/expectations', 2, message) + + def check_tabs(self, lines): + self._tab_checker.check(lines) + + def check(self, lines): + overrides = self._port_obj.test_expectations_overrides() + expectations = '\n'.join(lines) + self.check_test_expectations(expectations_str=expectations, + tests=None, + overrides=overrides) + # Warn tabs in lines as well + self.check_tabs(lines) diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py new file mode 100644 index 0000000..aa219b2 --- /dev/null +++ b/WebKitTools/Scripts/webkitpy/style/checkers/test_expectations_unittest.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Unit tests for test_expectations.py.""" + +import os +import sys +import unittest + +# We need following workaround hack to run this unit tests in stand-alone. +try: + d = os.path.dirname(__file__) +except NameError: + d = os.path.dirname(sys.argv[0]) +sys.path.append(os.path.abspath(os.path.join(d, '../../../'))) + +from test_expectations import TestExpectationsChecker +from webkitpy.style_references import port +from webkitpy.style_references import test_expectations as test_expectations_style + + +class ErrorCollector(object): + """An error handler class for unit tests.""" + + def __init__(self): + self._errors = [] + + def __call__(self, lineno, category, confidence, message): + self._errors.append('%s [%s] [%d]' % (message, category, confidence)) + + def get_errors(self): + return ''.join(self._errors) + + def reset_errors(self): + self._errors = [] + + +class TestExpectationsTestCase(unittest.TestCase): + """TestCase for test_expectations.py""" + + def setUp(self): + self._error_collector = ErrorCollector() + port_obj = port.get('test') + self._test_file = os.path.join(port_obj.layout_tests_dir(), 'misc/passing.html') + + def process_expectations(self, expectations, overrides=None): + self._checker = TestExpectationsChecker() + + def assert_lines_lint(self, lines, expected): + self._error_collector.reset_errors() + checker = TestExpectationsChecker('test/test_expectations.txt', + self._error_collector) + checker.check_test_expectations(expectations_str='\n'.join(lines), + tests=[self._test_file], + overrides=None) + checker.check_tabs(lines) + self.assertEqual(expected, self._error_collector.get_errors()) + + def test_valid_expectations(self): + self.assert_lines_lint( + ["misc/passing.html = PASS"], + "") + self.assert_lines_lint( + ["misc/passing.html = FAIL PASS"], + "") + self.assert_lines_lint( + ["misc/passing.html = CRASH TIMEOUT FAIL PASS"], + "") + self.assert_lines_lint( + ["BUG1234 TEST : misc/passing.html = PASS FAIL"], + "") + self.assert_lines_lint( + ["SKIP BUG1234 : misc/passing.html = TIMEOUT PASS"], + "") + self.assert_lines_lint( + ["BUG1234 DEBUG : misc/passing.html = TIMEOUT PASS"], + "") + self.assert_lines_lint( + ["BUG1234 DEBUG SKIP : misc/passing.html = TIMEOUT PASS"], + "") + self.assert_lines_lint( + ["BUG1234 TEST DEBUG SKIP : misc/passing.html = TIMEOUT PASS"], + "") + self.assert_lines_lint( + ["BUG1234 DEBUG TEST : misc/passing.html = TIMEOUT PASS"], + "") + self.assert_lines_lint( + ["SLOW DEFER BUG1234 : misc/passing.html = PASS"], + "") + self.assert_lines_lint( + ["WONTFIX SKIP : misc/passing.html = TIMEOUT"], + "") + + def test_valid_modifiers(self): + self.assert_lines_lint( + ["INVALID-MODIFIER : misc/passing.html = PASS"], + "Invalid modifier for test: invalid-modifier " + "misc/passing.html [test/expectations] [5]") + self.assert_lines_lint( + ["SKIP : misc/passing.html = PASS"], + "Test lacks BUG modifier. " + "misc/passing.html [test/expectations] [2]") + self.assert_lines_lint( + ["WONTFIX DEFER : misc/passing.html = PASS"], + "Test cannot be both DEFER and WONTFIX. " + "misc/passing.html [test/expectations] [5]") + + def test_expectation_errors(self): + self.assert_lines_lint( + ["missing expectations"], + "Missing expectations. ['missing expectations'] [test/expectations] [5]") + self.assert_lines_lint( + ["SLOW : misc/passing.html = TIMEOUT"], + "A test can not be both slow and timeout. " + "If it times out indefinitely, then it should be just timeout. " + "misc/passing.html [test/expectations] [5]") + self.assert_lines_lint( + ["does/not/exist.html = FAIL"], + "Path does not exist. does/not/exist.html [test/expectations] [2]") + + def test_parse_expectations(self): + self.assert_lines_lint( + ["misc/passing.html = PASS"], + "") + self.assert_lines_lint( + ["misc/passing.html = UNSUPPORTED"], + "Unsupported expectation: unsupported " + "misc/passing.html [test/expectations] [5]") + self.assert_lines_lint( + ["misc/passing.html = PASS UNSUPPORTED"], + "Unsupported expectation: unsupported " + "misc/passing.html [test/expectations] [5]") + + def test_already_seen_test(self): + self.assert_lines_lint( + ["misc/passing.html = PASS", + "misc/passing.html = TIMEOUT"], + "Duplicate expectation. %s [test/expectations] [5]" % self._test_file) + + def test_tab(self): + self.assert_lines_lint( + ["\tmisc/passing.html = PASS"], + "Line contains tab character. [whitespace/tab] [5]") + +if __name__ == '__main__': + unittest.main() diff --git a/WebKitTools/Scripts/webkitpy/style/checkers/text.py b/WebKitTools/Scripts/webkitpy/style/checkers/text.py index 0d03938..1147658 100644 --- a/WebKitTools/Scripts/webkitpy/style/checkers/text.py +++ b/WebKitTools/Scripts/webkitpy/style/checkers/text.py @@ -29,6 +29,7 @@ """Checks WebKit style for text files.""" +from common import TabChecker class TextChecker(object): @@ -37,16 +38,10 @@ class TextChecker(object): def __init__(self, file_path, handle_style_error): self.file_path = file_path self.handle_style_error = handle_style_error + self._tab_checker = TabChecker(file_path, handle_style_error) def check(self, lines): - lines = (["// adjust line numbers to make the first line 1."] + lines) - - # FIXME: share with cpp_style. - for line_number, line in enumerate(lines): - if "\t" in line: - self.handle_style_error(line_number, - "whitespace/tab", 5, - "Line contains tab character.") + self._tab_checker.check(lines) # FIXME: Remove this function (requires refactoring unit tests). |