WebKitTools/Scripts/webkitpy/style/checker_unittest.py
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 #!/usr/bin/python
       
     2 # -*- coding: utf-8; -*-
       
     3 #
       
     4 # Copyright (C) 2009 Google Inc. All rights reserved.
       
     5 # Copyright (C) 2009 Torch Mobile Inc.
       
     6 # Copyright (C) 2009 Apple Inc. All rights reserved.
       
     7 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com)
       
     8 #
       
     9 # Redistribution and use in source and binary forms, with or without
       
    10 # modification, are permitted provided that the following conditions are
       
    11 # met:
       
    12 #
       
    13 #    * Redistributions of source code must retain the above copyright
       
    14 # notice, this list of conditions and the following disclaimer.
       
    15 #    * Redistributions in binary form must reproduce the above
       
    16 # copyright notice, this list of conditions and the following disclaimer
       
    17 # in the documentation and/or other materials provided with the
       
    18 # distribution.
       
    19 #    * Neither the name of Google Inc. nor the names of its
       
    20 # contributors may be used to endorse or promote products derived from
       
    21 # this software without specific prior written permission.
       
    22 #
       
    23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
    24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
    25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
       
    26 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
       
    27 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
       
    28 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       
    29 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    30 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    31 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    32 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    33 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    34 
       
    35 """Unit tests for style.py."""
       
    36 
       
    37 import logging
       
    38 import os
       
    39 import unittest
       
    40 
       
    41 import checker as style
       
    42 from webkitpy.style_references import LogTesting
       
    43 from webkitpy.style_references import TestLogStream
       
    44 from checker import _BASE_FILTER_RULES
       
    45 from checker import _MAX_REPORTS_PER_CATEGORY
       
    46 from checker import _PATH_RULES_SPECIFIER as PATH_RULES_SPECIFIER
       
    47 from checker import _all_categories
       
    48 from checker import check_webkit_style_configuration
       
    49 from checker import check_webkit_style_parser
       
    50 from checker import configure_logging
       
    51 from checker import CheckerDispatcher
       
    52 from checker import ProcessorBase
       
    53 from checker import StyleProcessor
       
    54 from checker import StyleProcessorConfiguration
       
    55 from checkers.cpp import CppChecker
       
    56 from checkers.python import PythonChecker
       
    57 from checkers.text import TextChecker
       
    58 from error_handlers import DefaultStyleErrorHandler
       
    59 from filter import validate_filter_rules
       
    60 from filter import FilterConfiguration
       
    61 from optparser import ArgumentParser
       
    62 from optparser import CommandOptionValues
       
    63 from webkitpy.common.system.logtesting import LoggingTestCase
       
    64 from webkitpy.style.filereader import TextFileReader
       
    65 
       
    66 
       
    67 class ConfigureLoggingTestBase(unittest.TestCase):
       
    68 
       
    69     """Base class for testing configure_logging().
       
    70 
       
    71     Sub-classes should implement:
       
    72 
       
    73       is_verbose: The is_verbose value to pass to configure_logging().
       
    74 
       
    75     """
       
    76 
       
    77     def setUp(self):
       
    78         is_verbose = self.is_verbose
       
    79 
       
    80         log_stream = TestLogStream(self)
       
    81 
       
    82         # Use a logger other than the root logger or one prefixed with
       
    83         # webkit so as not to conflict with test-webkitpy logging.
       
    84         logger = logging.getLogger("unittest")
       
    85 
       
    86         # Configure the test logger not to pass messages along to the
       
    87         # root logger.  This prevents test messages from being
       
    88         # propagated to loggers used by test-webkitpy logging (e.g.
       
    89         # the root logger).
       
    90         logger.propagate = False
       
    91 
       
    92         self._handlers = configure_logging(stream=log_stream, logger=logger,
       
    93                                            is_verbose=is_verbose)
       
    94         self._log = logger
       
    95         self._log_stream = log_stream
       
    96 
       
    97     def tearDown(self):
       
    98         """Reset logging to its original state.
       
    99 
       
   100         This method ensures that the logging configuration set up
       
   101         for a unit test does not affect logging in other unit tests.
       
   102 
       
   103         """
       
   104         logger = self._log
       
   105         for handler in self._handlers:
       
   106             logger.removeHandler(handler)
       
   107 
       
   108     def assert_log_messages(self, messages):
       
   109         """Assert that the logged messages equal the given messages."""
       
   110         self._log_stream.assertMessages(messages)
       
   111 
       
   112 
       
   113 class ConfigureLoggingTest(ConfigureLoggingTestBase):
       
   114 
       
   115     """Tests the configure_logging() function."""
       
   116 
       
   117     is_verbose = False
       
   118 
       
   119     def test_warning_message(self):
       
   120         self._log.warn("test message")
       
   121         self.assert_log_messages(["WARNING: test message\n"])
       
   122 
       
   123     def test_below_warning_message(self):
       
   124         # We test the boundary case of a logging level equal to 29.
       
   125         # In practice, we will probably only be calling log.info(),
       
   126         # which corresponds to a logging level of 20.
       
   127         level = logging.WARNING - 1  # Equals 29.
       
   128         self._log.log(level, "test message")
       
   129         self.assert_log_messages(["test message\n"])
       
   130 
       
   131     def test_debug_message(self):
       
   132         self._log.debug("test message")
       
   133         self.assert_log_messages([])
       
   134 
       
   135     def test_two_messages(self):
       
   136         self._log.info("message1")
       
   137         self._log.info("message2")
       
   138         self.assert_log_messages(["message1\n", "message2\n"])
       
   139 
       
   140 
       
   141 class ConfigureLoggingVerboseTest(ConfigureLoggingTestBase):
       
   142 
       
   143     """Tests the configure_logging() function with is_verbose True."""
       
   144 
       
   145     is_verbose = True
       
   146 
       
   147     def test_debug_message(self):
       
   148         self._log.debug("test message")
       
   149         self.assert_log_messages(["unittest: DEBUG    test message\n"])
       
   150 
       
   151 
       
   152 class GlobalVariablesTest(unittest.TestCase):
       
   153 
       
   154     """Tests validity of the global variables."""
       
   155 
       
   156     def _all_categories(self):
       
   157         return _all_categories()
       
   158 
       
   159     def defaults(self):
       
   160         return style._check_webkit_style_defaults()
       
   161 
       
   162     def test_webkit_base_filter_rules(self):
       
   163         base_filter_rules = _BASE_FILTER_RULES
       
   164         defaults = self.defaults()
       
   165         already_seen = []
       
   166         validate_filter_rules(base_filter_rules, self._all_categories())
       
   167         # Also do some additional checks.
       
   168         for rule in base_filter_rules:
       
   169             # Check no leading or trailing white space.
       
   170             self.assertEquals(rule, rule.strip())
       
   171             # All categories are on by default, so defaults should
       
   172             # begin with -.
       
   173             self.assertTrue(rule.startswith('-'))
       
   174             # Check no rule occurs twice.
       
   175             self.assertFalse(rule in already_seen)
       
   176             already_seen.append(rule)
       
   177 
       
   178     def test_defaults(self):
       
   179         """Check that default arguments are valid."""
       
   180         default_options = self.defaults()
       
   181 
       
   182         # FIXME: We should not need to call parse() to determine
       
   183         #        whether the default arguments are valid.
       
   184         parser = ArgumentParser(all_categories=self._all_categories(),
       
   185                                 base_filter_rules=[],
       
   186                                 default_options=default_options)
       
   187         # No need to test the return value here since we test parse()
       
   188         # on valid arguments elsewhere.
       
   189         #
       
   190         # The default options are valid: no error or SystemExit.
       
   191         parser.parse(args=[])
       
   192 
       
   193     def test_path_rules_specifier(self):
       
   194         all_categories = self._all_categories()
       
   195         for (sub_paths, path_rules) in PATH_RULES_SPECIFIER:
       
   196             validate_filter_rules(path_rules, self._all_categories())
       
   197 
       
   198         config = FilterConfiguration(path_specific=PATH_RULES_SPECIFIER)
       
   199 
       
   200         def assertCheck(path, category):
       
   201             """Assert that the given category should be checked."""
       
   202             message = ('Should check category "%s" for path "%s".'
       
   203                        % (category, path))
       
   204             self.assertTrue(config.should_check(category, path))
       
   205 
       
   206         def assertNoCheck(path, category):
       
   207             """Assert that the given category should not be checked."""
       
   208             message = ('Should not check category "%s" for path "%s".'
       
   209                        % (category, path))
       
   210             self.assertFalse(config.should_check(category, path), message)
       
   211 
       
   212         assertCheck("random_path.cpp",
       
   213                     "build/include")
       
   214         assertNoCheck("WebKitTools/WebKitAPITest/main.cpp",
       
   215                       "build/include")
       
   216         assertNoCheck("WebKit/qt/QGVLauncher/main.cpp",
       
   217                       "build/include")
       
   218         assertNoCheck("WebKit/qt/QGVLauncher/main.cpp",
       
   219                       "readability/streams")
       
   220 
       
   221         assertCheck("random_path.cpp",
       
   222                     "readability/naming")
       
   223         assertNoCheck("WebKit/gtk/webkit/webkit.h",
       
   224                       "readability/naming")
       
   225         assertNoCheck("WebKitTools/DumpRenderTree/gtk/DumpRenderTree.cpp",
       
   226                       "readability/null")
       
   227         assertNoCheck("WebKit/efl/ewk/ewk_view.h",
       
   228                       "readability/naming")
       
   229         assertNoCheck("WebCore/css/CSSParser.cpp",
       
   230                       "readability/naming")
       
   231         assertNoCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp",
       
   232                       "readability/naming")
       
   233         assertNoCheck(
       
   234             "JavaScriptCore/qt/tests/qscriptengine/tst_qscriptengine.cpp",
       
   235             "readability/naming")
       
   236         assertNoCheck("WebCore/ForwardingHeaders/debugger/Debugger.h",
       
   237                       "build/header_guard")
       
   238 
       
   239         # Third-party Python code: webkitpy/thirdparty
       
   240         path = "WebKitTools/Scripts/webkitpy/thirdparty/mock.py"
       
   241         assertNoCheck(path, "build/include")
       
   242         assertNoCheck(path, "pep8/E401")  # A random pep8 category.
       
   243         assertCheck(path, "pep8/W191")
       
   244         assertCheck(path, "pep8/W291")
       
   245         assertCheck(path, "whitespace/carriage_return")
       
   246 
       
   247     def test_max_reports_per_category(self):
       
   248         """Check that _MAX_REPORTS_PER_CATEGORY is valid."""
       
   249         all_categories = self._all_categories()
       
   250         for category in _MAX_REPORTS_PER_CATEGORY.iterkeys():
       
   251             self.assertTrue(category in all_categories,
       
   252                             'Key "%s" is not a category' % category)
       
   253 
       
   254 
       
   255 class CheckWebKitStyleFunctionTest(unittest.TestCase):
       
   256 
       
   257     """Tests the functions with names of the form check_webkit_style_*."""
       
   258 
       
   259     def test_check_webkit_style_configuration(self):
       
   260         # Exercise the code path to make sure the function does not error out.
       
   261         option_values = CommandOptionValues()
       
   262         configuration = check_webkit_style_configuration(option_values)
       
   263 
       
   264     def test_check_webkit_style_parser(self):
       
   265         # Exercise the code path to make sure the function does not error out.
       
   266         parser = check_webkit_style_parser()
       
   267 
       
   268 
       
   269 class CheckerDispatcherSkipTest(unittest.TestCase):
       
   270 
       
   271     """Tests the "should skip" methods of the CheckerDispatcher class."""
       
   272 
       
   273     def setUp(self):
       
   274         self._dispatcher = CheckerDispatcher()
       
   275 
       
   276     def test_should_skip_with_warning(self):
       
   277         """Test should_skip_with_warning()."""
       
   278         # Check a non-skipped file.
       
   279         self.assertFalse(self._dispatcher.should_skip_with_warning("foo.txt"))
       
   280 
       
   281         # Check skipped files.
       
   282         paths_to_skip = [
       
   283            "gtk2drawing.c",
       
   284            "gtkdrawing.h",
       
   285            "JavaScriptCore/qt/api/qscriptengine_p.h",
       
   286            "WebCore/platform/gtk/gtk2drawing.c",
       
   287            "WebCore/platform/gtk/gtkdrawing.h",
       
   288            "WebKit/gtk/tests/testatk.c",
       
   289            "WebKit/qt/Api/qwebpage.h",
       
   290            "WebKit/qt/tests/qwebsecurityorigin/tst_qwebsecurityorigin.cpp",
       
   291             ]
       
   292 
       
   293         for path in paths_to_skip:
       
   294             self.assertTrue(self._dispatcher.should_skip_with_warning(path),
       
   295                             "Checking: " + path)
       
   296 
       
   297     def _assert_should_skip_without_warning(self, path, is_checker_none,
       
   298                                             expected):
       
   299         # Check the file type before asserting the return value.
       
   300         checker = self._dispatcher.dispatch(file_path=path,
       
   301                                             handle_style_error=None,
       
   302                                             min_confidence=3)
       
   303         message = 'while checking: %s' % path
       
   304         self.assertEquals(checker is None, is_checker_none, message)
       
   305         self.assertEquals(self._dispatcher.should_skip_without_warning(path),
       
   306                           expected, message)
       
   307 
       
   308     def test_should_skip_without_warning__true(self):
       
   309         """Test should_skip_without_warning() for True return values."""
       
   310         # Check a file with NONE file type.
       
   311         path = 'foo.asdf'  # Non-sensical file extension.
       
   312         self._assert_should_skip_without_warning(path,
       
   313                                                  is_checker_none=True,
       
   314                                                  expected=True)
       
   315 
       
   316         # Check files with non-NONE file type.  These examples must be
       
   317         # drawn from the _SKIPPED_FILES_WITHOUT_WARNING configuration
       
   318         # variable.
       
   319         path = os.path.join('LayoutTests', 'foo.txt')
       
   320         self._assert_should_skip_without_warning(path,
       
   321                                                  is_checker_none=False,
       
   322                                                  expected=True)
       
   323 
       
   324     def test_should_skip_without_warning__false(self):
       
   325         """Test should_skip_without_warning() for False return values."""
       
   326         paths = ['foo.txt',
       
   327                  os.path.join('LayoutTests', 'ChangeLog'),
       
   328         ]
       
   329 
       
   330         for path in paths:
       
   331             self._assert_should_skip_without_warning(path,
       
   332                                                      is_checker_none=False,
       
   333                                                      expected=False)
       
   334 
       
   335 
       
   336 class CheckerDispatcherDispatchTest(unittest.TestCase):
       
   337 
       
   338     """Tests dispatch() method of CheckerDispatcher class."""
       
   339 
       
   340     def mock_handle_style_error(self):
       
   341         pass
       
   342 
       
   343     def dispatch(self, file_path):
       
   344         """Call dispatch() with the given file path."""
       
   345         dispatcher = CheckerDispatcher()
       
   346         checker = dispatcher.dispatch(file_path,
       
   347                                       self.mock_handle_style_error,
       
   348                                       min_confidence=3)
       
   349         return checker
       
   350 
       
   351     def assert_checker_none(self, file_path):
       
   352         """Assert that the dispatched checker is None."""
       
   353         checker = self.dispatch(file_path)
       
   354         self.assertTrue(checker is None, 'Checking: "%s"' % file_path)
       
   355 
       
   356     def assert_checker(self, file_path, expected_class):
       
   357         """Assert the type of the dispatched checker."""
       
   358         checker = self.dispatch(file_path)
       
   359         got_class = checker.__class__
       
   360         self.assertEquals(got_class, expected_class,
       
   361                           'For path "%(file_path)s" got %(got_class)s when '
       
   362                           "expecting %(expected_class)s."
       
   363                           % {"file_path": file_path,
       
   364                              "got_class": got_class,
       
   365                              "expected_class": expected_class})
       
   366 
       
   367     def assert_checker_cpp(self, file_path):
       
   368         """Assert that the dispatched checker is a CppChecker."""
       
   369         self.assert_checker(file_path, CppChecker)
       
   370 
       
   371     def assert_checker_python(self, file_path):
       
   372         """Assert that the dispatched checker is a PythonChecker."""
       
   373         self.assert_checker(file_path, PythonChecker)
       
   374 
       
   375     def assert_checker_text(self, file_path):
       
   376         """Assert that the dispatched checker is a TextChecker."""
       
   377         self.assert_checker(file_path, TextChecker)
       
   378 
       
   379     def test_cpp_paths(self):
       
   380         """Test paths that should be checked as C++."""
       
   381         paths = [
       
   382             "-",
       
   383             "foo.c",
       
   384             "foo.cpp",
       
   385             "foo.h",
       
   386             ]
       
   387 
       
   388         for path in paths:
       
   389             self.assert_checker_cpp(path)
       
   390 
       
   391         # Check checker attributes on a typical input.
       
   392         file_base = "foo"
       
   393         file_extension = "c"
       
   394         file_path = file_base + "." + file_extension
       
   395         self.assert_checker_cpp(file_path)
       
   396         checker = self.dispatch(file_path)
       
   397         self.assertEquals(checker.file_extension, file_extension)
       
   398         self.assertEquals(checker.file_path, file_path)
       
   399         self.assertEquals(checker.handle_style_error, self.mock_handle_style_error)
       
   400         self.assertEquals(checker.min_confidence, 3)
       
   401         # Check "-" for good measure.
       
   402         file_base = "-"
       
   403         file_extension = ""
       
   404         file_path = file_base
       
   405         self.assert_checker_cpp(file_path)
       
   406         checker = self.dispatch(file_path)
       
   407         self.assertEquals(checker.file_extension, file_extension)
       
   408         self.assertEquals(checker.file_path, file_path)
       
   409 
       
   410     def test_python_paths(self):
       
   411         """Test paths that should be checked as Python."""
       
   412         paths = [
       
   413            "foo.py",
       
   414            "WebKitTools/Scripts/modules/text_style.py",
       
   415         ]
       
   416 
       
   417         for path in paths:
       
   418             self.assert_checker_python(path)
       
   419 
       
   420         # Check checker attributes on a typical input.
       
   421         file_base = "foo"
       
   422         file_extension = "css"
       
   423         file_path = file_base + "." + file_extension
       
   424         self.assert_checker_text(file_path)
       
   425         checker = self.dispatch(file_path)
       
   426         self.assertEquals(checker.file_path, file_path)
       
   427         self.assertEquals(checker.handle_style_error,
       
   428                           self.mock_handle_style_error)
       
   429 
       
   430     def test_text_paths(self):
       
   431         """Test paths that should be checked as text."""
       
   432         paths = [
       
   433            "ChangeLog",
       
   434            "ChangeLog-2009-06-16",
       
   435            "foo.ac",
       
   436            "foo.cc",
       
   437            "foo.cgi",
       
   438            "foo.css",
       
   439            "foo.exp",
       
   440            "foo.flex",
       
   441            "foo.gyp",
       
   442            "foo.gypi",
       
   443            "foo.html",
       
   444            "foo.idl",
       
   445            "foo.in",
       
   446            "foo.js",
       
   447            "foo.mm",
       
   448            "foo.php",
       
   449            "foo.pl",
       
   450            "foo.pm",
       
   451            "foo.pri",
       
   452            "foo.pro",
       
   453            "foo.rb",
       
   454            "foo.sh",
       
   455            "foo.txt",
       
   456            "foo.wm",
       
   457            "foo.xhtml",
       
   458            "foo.y",
       
   459            os.path.join("WebCore", "ChangeLog"),
       
   460            os.path.join("WebCore", "inspector", "front-end", "inspector.js"),
       
   461            os.path.join("WebKitTools", "Scripts", "check-webkit-style"),
       
   462         ]
       
   463 
       
   464         for path in paths:
       
   465             self.assert_checker_text(path)
       
   466 
       
   467         # Check checker attributes on a typical input.
       
   468         file_base = "foo"
       
   469         file_extension = "css"
       
   470         file_path = file_base + "." + file_extension
       
   471         self.assert_checker_text(file_path)
       
   472         checker = self.dispatch(file_path)
       
   473         self.assertEquals(checker.file_path, file_path)
       
   474         self.assertEquals(checker.handle_style_error, self.mock_handle_style_error)
       
   475 
       
   476     def test_none_paths(self):
       
   477         """Test paths that have no file type.."""
       
   478         paths = [
       
   479            "Makefile",
       
   480            "foo.asdf",  # Non-sensical file extension.
       
   481            "foo.png",
       
   482            "foo.exe",
       
   483            "foo.vcproj",
       
   484             ]
       
   485 
       
   486         for path in paths:
       
   487             self.assert_checker_none(path)
       
   488 
       
   489 
       
   490 class StyleProcessorConfigurationTest(unittest.TestCase):
       
   491 
       
   492     """Tests the StyleProcessorConfiguration class."""
       
   493 
       
   494     def setUp(self):
       
   495         self._error_messages = []
       
   496         """The messages written to _mock_stderr_write() of this class."""
       
   497 
       
   498     def _mock_stderr_write(self, message):
       
   499         self._error_messages.append(message)
       
   500 
       
   501     def _style_checker_configuration(self, output_format="vs7"):
       
   502         """Return a StyleProcessorConfiguration instance for testing."""
       
   503         base_rules = ["-whitespace", "+whitespace/tab"]
       
   504         filter_configuration = FilterConfiguration(base_rules=base_rules)
       
   505 
       
   506         return StyleProcessorConfiguration(
       
   507                    filter_configuration=filter_configuration,
       
   508                    max_reports_per_category={"whitespace/newline": 1},
       
   509                    min_confidence=3,
       
   510                    output_format=output_format,
       
   511                    stderr_write=self._mock_stderr_write)
       
   512 
       
   513     def test_init(self):
       
   514         """Test the __init__() method."""
       
   515         configuration = self._style_checker_configuration()
       
   516 
       
   517         # Check that __init__ sets the "public" data attributes correctly.
       
   518         self.assertEquals(configuration.max_reports_per_category,
       
   519                           {"whitespace/newline": 1})
       
   520         self.assertEquals(configuration.stderr_write, self._mock_stderr_write)
       
   521         self.assertEquals(configuration.min_confidence, 3)
       
   522 
       
   523     def test_is_reportable(self):
       
   524         """Test the is_reportable() method."""
       
   525         config = self._style_checker_configuration()
       
   526 
       
   527         self.assertTrue(config.is_reportable("whitespace/tab", 3, "foo.txt"))
       
   528 
       
   529         # Test the confidence check code path by varying the confidence.
       
   530         self.assertFalse(config.is_reportable("whitespace/tab", 2, "foo.txt"))
       
   531 
       
   532         # Test the category check code path by varying the category.
       
   533         self.assertFalse(config.is_reportable("whitespace/line", 4, "foo.txt"))
       
   534 
       
   535     def _call_write_style_error(self, output_format):
       
   536         config = self._style_checker_configuration(output_format=output_format)
       
   537         config.write_style_error(category="whitespace/tab",
       
   538                                  confidence_in_error=5,
       
   539                                  file_path="foo.h",
       
   540                                  line_number=100,
       
   541                                  message="message")
       
   542 
       
   543     def test_write_style_error_emacs(self):
       
   544         """Test the write_style_error() method."""
       
   545         self._call_write_style_error("emacs")
       
   546         self.assertEquals(self._error_messages,
       
   547                           ["foo.h:100:  message  [whitespace/tab] [5]\n"])
       
   548 
       
   549     def test_write_style_error_vs7(self):
       
   550         """Test the write_style_error() method."""
       
   551         self._call_write_style_error("vs7")
       
   552         self.assertEquals(self._error_messages,
       
   553                           ["foo.h(100):  message  [whitespace/tab] [5]\n"])
       
   554 
       
   555 
       
   556 class StyleProcessor_EndToEndTest(LoggingTestCase):
       
   557 
       
   558     """Test the StyleProcessor class with an emphasis on end-to-end tests."""
       
   559 
       
   560     def setUp(self):
       
   561         LoggingTestCase.setUp(self)
       
   562         self._messages = []
       
   563 
       
   564     def _mock_stderr_write(self, message):
       
   565         """Save a message so it can later be asserted."""
       
   566         self._messages.append(message)
       
   567 
       
   568     def test_init(self):
       
   569         """Test __init__ constructor."""
       
   570         configuration = StyleProcessorConfiguration(
       
   571                             filter_configuration=FilterConfiguration(),
       
   572                             max_reports_per_category={},
       
   573                             min_confidence=3,
       
   574                             output_format="vs7",
       
   575                             stderr_write=self._mock_stderr_write)
       
   576         processor = StyleProcessor(configuration)
       
   577 
       
   578         self.assertEquals(processor.error_count, 0)
       
   579         self.assertEquals(self._messages, [])
       
   580 
       
   581     def test_process(self):
       
   582         configuration = StyleProcessorConfiguration(
       
   583                             filter_configuration=FilterConfiguration(),
       
   584                             max_reports_per_category={},
       
   585                             min_confidence=3,
       
   586                             output_format="vs7",
       
   587                             stderr_write=self._mock_stderr_write)
       
   588         processor = StyleProcessor(configuration)
       
   589 
       
   590         processor.process(lines=['line1', 'Line with tab:\t'],
       
   591                           file_path='foo.txt')
       
   592         self.assertEquals(processor.error_count, 1)
       
   593         expected_messages = ['foo.txt(2):  Line contains tab character.  '
       
   594                              '[whitespace/tab] [5]\n']
       
   595         self.assertEquals(self._messages, expected_messages)
       
   596 
       
   597 
       
   598 class StyleProcessor_CodeCoverageTest(LoggingTestCase):
       
   599 
       
   600     """Test the StyleProcessor class with an emphasis on code coverage.
       
   601 
       
   602     This class makes heavy use of mock objects.
       
   603 
       
   604     """
       
   605 
       
   606     class MockDispatchedChecker(object):
       
   607 
       
   608         """A mock checker dispatched by the MockDispatcher."""
       
   609 
       
   610         def __init__(self, file_path, min_confidence, style_error_handler):
       
   611             self.file_path = file_path
       
   612             self.min_confidence = min_confidence
       
   613             self.style_error_handler = style_error_handler
       
   614 
       
   615         def check(self, lines):
       
   616             self.lines = lines
       
   617 
       
   618     class MockDispatcher(object):
       
   619 
       
   620         """A mock CheckerDispatcher class."""
       
   621 
       
   622         def __init__(self):
       
   623             self.dispatched_checker = None
       
   624 
       
   625         def should_skip_with_warning(self, file_path):
       
   626             return file_path.endswith('skip_with_warning.txt')
       
   627 
       
   628         def should_skip_without_warning(self, file_path):
       
   629             return file_path.endswith('skip_without_warning.txt')
       
   630 
       
   631         def dispatch(self, file_path, style_error_handler, min_confidence):
       
   632             if file_path.endswith('do_not_process.txt'):
       
   633                 return None
       
   634 
       
   635             checker = StyleProcessor_CodeCoverageTest.MockDispatchedChecker(
       
   636                           file_path,
       
   637                           min_confidence,
       
   638                           style_error_handler)
       
   639 
       
   640             # Save the dispatched checker so the current test case has a
       
   641             # way to access and check it.
       
   642             self.dispatched_checker = checker
       
   643 
       
   644             return checker
       
   645 
       
   646     def setUp(self):
       
   647         LoggingTestCase.setUp(self)
       
   648         # We can pass an error-message swallower here because error message
       
   649         # output is tested instead in the end-to-end test case above.
       
   650         configuration = StyleProcessorConfiguration(
       
   651                             filter_configuration=FilterConfiguration(),
       
   652                             max_reports_per_category={"whitespace/newline": 1},
       
   653                             min_confidence=3,
       
   654                             output_format="vs7",
       
   655                             stderr_write=self._swallow_stderr_message)
       
   656 
       
   657         mock_carriage_checker_class = self._create_carriage_checker_class()
       
   658         mock_dispatcher = self.MockDispatcher()
       
   659         # We do not need to use a real incrementer here because error-count
       
   660         # incrementing is tested instead in the end-to-end test case above.
       
   661         mock_increment_error_count = self._do_nothing
       
   662 
       
   663         processor = StyleProcessor(configuration=configuration,
       
   664                         mock_carriage_checker_class=mock_carriage_checker_class,
       
   665                         mock_dispatcher=mock_dispatcher,
       
   666                         mock_increment_error_count=mock_increment_error_count)
       
   667 
       
   668         self._configuration = configuration
       
   669         self._mock_dispatcher = mock_dispatcher
       
   670         self._processor = processor
       
   671 
       
   672     def _do_nothing(self):
       
   673         # We provide this function so the caller can pass it to the
       
   674         # StyleProcessor constructor.  This lets us assert the equality of
       
   675         # the DefaultStyleErrorHandler instance generated by the process()
       
   676         # method with an expected instance.
       
   677         pass
       
   678 
       
   679     def _swallow_stderr_message(self, message):
       
   680         """Swallow a message passed to stderr.write()."""
       
   681         # This is a mock stderr.write() for passing to the constructor
       
   682         # of the StyleProcessorConfiguration class.
       
   683         pass
       
   684 
       
   685     def _create_carriage_checker_class(self):
       
   686 
       
   687         # Create a reference to self with a new name so its name does not
       
   688         # conflict with the self introduced below.
       
   689         test_case = self
       
   690 
       
   691         class MockCarriageChecker(object):
       
   692 
       
   693             """A mock carriage-return checker."""
       
   694 
       
   695             def __init__(self, style_error_handler):
       
   696                 self.style_error_handler = style_error_handler
       
   697 
       
   698                 # This gives the current test case access to the
       
   699                 # instantiated carriage checker.
       
   700                 test_case.carriage_checker = self
       
   701 
       
   702             def check(self, lines):
       
   703                 # Save the lines so the current test case has a way to access
       
   704                 # and check them.
       
   705                 self.lines = lines
       
   706 
       
   707                 return lines
       
   708 
       
   709         return MockCarriageChecker
       
   710 
       
   711     def test_should_process__skip_without_warning(self):
       
   712         """Test should_process() for a skip-without-warning file."""
       
   713         file_path = "foo/skip_without_warning.txt"
       
   714 
       
   715         self.assertFalse(self._processor.should_process(file_path))
       
   716 
       
   717     def test_should_process__skip_with_warning(self):
       
   718         """Test should_process() for a skip-with-warning file."""
       
   719         file_path = "foo/skip_with_warning.txt"
       
   720 
       
   721         self.assertFalse(self._processor.should_process(file_path))
       
   722 
       
   723         self.assertLog(['WARNING: File exempt from style guide. '
       
   724                         'Skipping: "foo/skip_with_warning.txt"\n'])
       
   725 
       
   726     def test_should_process__true_result(self):
       
   727         """Test should_process() for a file that should be processed."""
       
   728         file_path = "foo/skip_process.txt"
       
   729 
       
   730         self.assertTrue(self._processor.should_process(file_path))
       
   731 
       
   732     def test_process__checker_dispatched(self):
       
   733         """Test the process() method for a path with a dispatched checker."""
       
   734         file_path = 'foo.txt'
       
   735         lines = ['line1', 'line2']
       
   736         line_numbers = [100]
       
   737 
       
   738         expected_error_handler = DefaultStyleErrorHandler(
       
   739             configuration=self._configuration,
       
   740             file_path=file_path,
       
   741             increment_error_count=self._do_nothing,
       
   742             line_numbers=line_numbers)
       
   743 
       
   744         self._processor.process(lines=lines,
       
   745                                 file_path=file_path,
       
   746                                 line_numbers=line_numbers)
       
   747 
       
   748         # Check that the carriage-return checker was instantiated correctly
       
   749         # and was passed lines correctly.
       
   750         carriage_checker = self.carriage_checker
       
   751         self.assertEquals(carriage_checker.style_error_handler,
       
   752                           expected_error_handler)
       
   753         self.assertEquals(carriage_checker.lines, ['line1', 'line2'])
       
   754 
       
   755         # Check that the style checker was dispatched correctly and was
       
   756         # passed lines correctly.
       
   757         checker = self._mock_dispatcher.dispatched_checker
       
   758         self.assertEquals(checker.file_path, 'foo.txt')
       
   759         self.assertEquals(checker.min_confidence, 3)
       
   760         self.assertEquals(checker.style_error_handler, expected_error_handler)
       
   761 
       
   762         self.assertEquals(checker.lines, ['line1', 'line2'])
       
   763 
       
   764     def test_process__no_checker_dispatched(self):
       
   765         """Test the process() method for a path with no dispatched checker."""
       
   766         path = os.path.join('foo', 'do_not_process.txt')
       
   767         self.assertRaises(AssertionError, self._processor.process,
       
   768                           lines=['line1', 'line2'], file_path=path,
       
   769                           line_numbers=[100])