Home | History | Annotate | Download | only in style
      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 (at] 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.changelog import ChangeLogChecker
     56 from checkers.cpp import CppChecker
     57 from checkers.python import PythonChecker
     58 from checkers.text import TextChecker
     59 from checkers.xml import XMLChecker
     60 from error_handlers import DefaultStyleErrorHandler
     61 from filter import validate_filter_rules
     62 from filter import FilterConfiguration
     63 from optparser import ArgumentParser
     64 from optparser import CommandOptionValues
     65 from webkitpy.common.system.logtesting import LoggingTestCase
     66 from webkitpy.style.filereader import TextFileReader
     67 
     68 
     69 class ConfigureLoggingTestBase(unittest.TestCase):
     70 
     71     """Base class for testing configure_logging().
     72 
     73     Sub-classes should implement:
     74 
     75       is_verbose: The is_verbose value to pass to configure_logging().
     76 
     77     """
     78 
     79     def setUp(self):
     80         is_verbose = self.is_verbose
     81 
     82         log_stream = TestLogStream(self)
     83 
     84         # Use a logger other than the root logger or one prefixed with
     85         # webkit so as not to conflict with test-webkitpy logging.
     86         logger = logging.getLogger("unittest")
     87 
     88         # Configure the test logger not to pass messages along to the
     89         # root logger.  This prevents test messages from being
     90         # propagated to loggers used by test-webkitpy logging (e.g.
     91         # the root logger).
     92         logger.propagate = False
     93 
     94         self._handlers = configure_logging(stream=log_stream, logger=logger,
     95                                            is_verbose=is_verbose)
     96         self._log = logger
     97         self._log_stream = log_stream
     98 
     99     def tearDown(self):
    100         """Reset logging to its original state.
    101 
    102         This method ensures that the logging configuration set up
    103         for a unit test does not affect logging in other unit tests.
    104 
    105         """
    106         logger = self._log
    107         for handler in self._handlers:
    108             logger.removeHandler(handler)
    109 
    110     def assert_log_messages(self, messages):
    111         """Assert that the logged messages equal the given messages."""
    112         self._log_stream.assertMessages(messages)
    113 
    114 
    115 class ConfigureLoggingTest(ConfigureLoggingTestBase):
    116 
    117     """Tests the configure_logging() function."""
    118 
    119     is_verbose = False
    120 
    121     def test_warning_message(self):
    122         self._log.warn("test message")
    123         self.assert_log_messages(["WARNING: test message\n"])
    124 
    125     def test_below_warning_message(self):
    126         # We test the boundary case of a logging level equal to 29.
    127         # In practice, we will probably only be calling log.info(),
    128         # which corresponds to a logging level of 20.
    129         level = logging.WARNING - 1  # Equals 29.
    130         self._log.log(level, "test message")
    131         self.assert_log_messages(["test message\n"])
    132 
    133     def test_debug_message(self):
    134         self._log.debug("test message")
    135         self.assert_log_messages([])
    136 
    137     def test_two_messages(self):
    138         self._log.info("message1")
    139         self._log.info("message2")
    140         self.assert_log_messages(["message1\n", "message2\n"])
    141 
    142 
    143 class ConfigureLoggingVerboseTest(ConfigureLoggingTestBase):
    144 
    145     """Tests the configure_logging() function with is_verbose True."""
    146 
    147     is_verbose = True
    148 
    149     def test_debug_message(self):
    150         self._log.debug("test message")
    151         self.assert_log_messages(["unittest: DEBUG    test message\n"])
    152 
    153 
    154 class GlobalVariablesTest(unittest.TestCase):
    155 
    156     """Tests validity of the global variables."""
    157 
    158     def _all_categories(self):
    159         return _all_categories()
    160 
    161     def defaults(self):
    162         return style._check_webkit_style_defaults()
    163 
    164     def test_webkit_base_filter_rules(self):
    165         base_filter_rules = _BASE_FILTER_RULES
    166         defaults = self.defaults()
    167         already_seen = []
    168         validate_filter_rules(base_filter_rules, self._all_categories())
    169         # Also do some additional checks.
    170         for rule in base_filter_rules:
    171             # Check no leading or trailing white space.
    172             self.assertEquals(rule, rule.strip())
    173             # All categories are on by default, so defaults should
    174             # begin with -.
    175             self.assertTrue(rule.startswith('-'))
    176             # Check no rule occurs twice.
    177             self.assertFalse(rule in already_seen)
    178             already_seen.append(rule)
    179 
    180     def test_defaults(self):
    181         """Check that default arguments are valid."""
    182         default_options = self.defaults()
    183 
    184         # FIXME: We should not need to call parse() to determine
    185         #        whether the default arguments are valid.
    186         parser = ArgumentParser(all_categories=self._all_categories(),
    187                                 base_filter_rules=[],
    188                                 default_options=default_options)
    189         # No need to test the return value here since we test parse()
    190         # on valid arguments elsewhere.
    191         #
    192         # The default options are valid: no error or SystemExit.
    193         parser.parse(args=[])
    194 
    195     def test_path_rules_specifier(self):
    196         all_categories = self._all_categories()
    197         for (sub_paths, path_rules) in PATH_RULES_SPECIFIER:
    198             validate_filter_rules(path_rules, self._all_categories())
    199 
    200         config = FilterConfiguration(path_specific=PATH_RULES_SPECIFIER)
    201 
    202         def assertCheck(path, category):
    203             """Assert that the given category should be checked."""
    204             message = ('Should check category "%s" for path "%s".'
    205                        % (category, path))
    206             self.assertTrue(config.should_check(category, path))
    207 
    208         def assertNoCheck(path, category):
    209             """Assert that the given category should not be checked."""
    210             message = ('Should not check category "%s" for path "%s".'
    211                        % (category, path))
    212             self.assertFalse(config.should_check(category, path), message)
    213 
    214         assertCheck("random_path.cpp",
    215                     "build/include")
    216         assertNoCheck("Tools/WebKitAPITest/main.cpp",
    217                       "build/include")
    218         assertCheck("random_path.cpp",
    219                     "readability/naming")
    220         assertNoCheck("Source/WebKit/gtk/webkit/webkit.h",
    221                       "readability/naming")
    222         assertNoCheck("Tools/DumpRenderTree/gtk/DumpRenderTree.cpp",
    223                       "readability/null")
    224         assertNoCheck("Source/WebKit/efl/ewk/ewk_view.h",
    225                       "readability/naming")
    226         assertNoCheck("Source/WebCore/css/CSSParser.cpp",
    227                       "readability/naming")
    228 
    229         # Test if Qt exceptions are indeed working
    230         assertCheck("Source/JavaScriptCore/qt/api/qscriptengine.cpp",
    231                     "readability/braces")
    232         assertCheck("Source/WebKit/qt/Api/qwebpage.cpp",
    233                     "readability/braces")
    234         assertCheck("Source/WebKit/qt/tests/qwebelement/tst_qwebelement.cpp",
    235                     "readability/braces")
    236         assertCheck("Source/WebKit/qt/declarative/platformplugin/WebPlugin.cpp",
    237                     "readability/braces")
    238         assertCheck("Source/WebKit/qt/examples/platformplugin/WebPlugin.cpp",
    239                     "readability/braces")
    240         assertNoCheck("Source/JavaScriptCore/qt/api/qscriptengine.cpp",
    241                       "readability/naming")
    242         assertNoCheck("Source/JavaScriptCore/qt/benchmarks"
    243                       "/qscriptengine/tst_qscriptengine.cpp",
    244                       "readability/naming")
    245         assertNoCheck("Source/WebKit/qt/Api/qwebpage.cpp",
    246                       "readability/naming")
    247         assertNoCheck("Source/WebKit/qt/tests/qwebelement/tst_qwebelement.cpp",
    248                       "readability/naming")
    249         assertNoCheck("Source/WebKit/qt/declarative/platformplugin/WebPlugin.cpp",
    250                       "readability/naming")
    251         assertNoCheck("Source/WebKit/qt/examples/platformplugin/WebPlugin.cpp",
    252                       "readability/naming")
    253 
    254         assertNoCheck("Tools/MiniBrowser/qt/UrlLoader.cpp",
    255                     "build/include")
    256 
    257         assertNoCheck("Source/WebCore/ForwardingHeaders/debugger/Debugger.h",
    258                       "build/header_guard")
    259 
    260         # Third-party Python code: webkitpy/thirdparty
    261         path = "Tools/Scripts/webkitpy/thirdparty/mock.py"
    262         assertNoCheck(path, "build/include")
    263         assertNoCheck(path, "pep8/E401")  # A random pep8 category.
    264         assertCheck(path, "pep8/W191")
    265         assertCheck(path, "pep8/W291")
    266         assertCheck(path, "whitespace/carriage_return")
    267 
    268     def test_max_reports_per_category(self):
    269         """Check that _MAX_REPORTS_PER_CATEGORY is valid."""
    270         all_categories = self._all_categories()
    271         for category in _MAX_REPORTS_PER_CATEGORY.iterkeys():
    272             self.assertTrue(category in all_categories,
    273                             'Key "%s" is not a category' % category)
    274 
    275 
    276 class CheckWebKitStyleFunctionTest(unittest.TestCase):
    277 
    278     """Tests the functions with names of the form check_webkit_style_*."""
    279 
    280     def test_check_webkit_style_configuration(self):
    281         # Exercise the code path to make sure the function does not error out.
    282         option_values = CommandOptionValues()
    283         configuration = check_webkit_style_configuration(option_values)
    284 
    285     def test_check_webkit_style_parser(self):
    286         # Exercise the code path to make sure the function does not error out.
    287         parser = check_webkit_style_parser()
    288 
    289 
    290 class CheckerDispatcherSkipTest(unittest.TestCase):
    291 
    292     """Tests the "should skip" methods of the CheckerDispatcher class."""
    293 
    294     def setUp(self):
    295         self._dispatcher = CheckerDispatcher()
    296 
    297     def test_should_skip_with_warning(self):
    298         """Test should_skip_with_warning()."""
    299         # Check a non-skipped file.
    300         self.assertFalse(self._dispatcher.should_skip_with_warning("foo.txt"))
    301 
    302         # Check skipped files.
    303         paths_to_skip = [
    304            "gtk2drawing.c",
    305            "gtkdrawing.h",
    306            "Source/WebCore/platform/gtk/gtk2drawing.c",
    307            "Source/WebCore/platform/gtk/gtkdrawing.h",
    308            "Source/WebKit/gtk/tests/testatk.c",
    309             ]
    310 
    311         for path in paths_to_skip:
    312             self.assertTrue(self._dispatcher.should_skip_with_warning(path),
    313                             "Checking: " + path)
    314 
    315     def _assert_should_skip_without_warning(self, path, is_checker_none,
    316                                             expected):
    317         # Check the file type before asserting the return value.
    318         checker = self._dispatcher.dispatch(file_path=path,
    319                                             handle_style_error=None,
    320                                             min_confidence=3)
    321         message = 'while checking: %s' % path
    322         self.assertEquals(checker is None, is_checker_none, message)
    323         self.assertEquals(self._dispatcher.should_skip_without_warning(path),
    324                           expected, message)
    325 
    326     def test_should_skip_without_warning__true(self):
    327         """Test should_skip_without_warning() for True return values."""
    328         # Check a file with NONE file type.
    329         path = 'foo.asdf'  # Non-sensical file extension.
    330         self._assert_should_skip_without_warning(path,
    331                                                  is_checker_none=True,
    332                                                  expected=True)
    333 
    334         # Check files with non-NONE file type.  These examples must be
    335         # drawn from the _SKIPPED_FILES_WITHOUT_WARNING configuration
    336         # variable.
    337         path = os.path.join('LayoutTests', 'foo.txt')
    338         self._assert_should_skip_without_warning(path,
    339                                                  is_checker_none=False,
    340                                                  expected=True)
    341 
    342     def test_should_skip_without_warning__false(self):
    343         """Test should_skip_without_warning() for False return values."""
    344         paths = ['foo.txt',
    345                  os.path.join('LayoutTests', 'ChangeLog'),
    346         ]
    347 
    348         for path in paths:
    349             self._assert_should_skip_without_warning(path,
    350                                                      is_checker_none=False,
    351                                                      expected=False)
    352 
    353 
    354 class CheckerDispatcherCarriageReturnTest(unittest.TestCase):
    355     def test_should_check_and_strip_carriage_returns(self):
    356         files = {
    357             'foo.txt': True,
    358             'foo.cpp': True,
    359             'foo.vcproj': False,
    360             'foo.vsprops': False,
    361         }
    362 
    363         dispatcher = CheckerDispatcher()
    364         for file_path, expected_result in files.items():
    365             self.assertEquals(dispatcher.should_check_and_strip_carriage_returns(file_path), expected_result, 'Checking: %s' % file_path)
    366 
    367 
    368 class CheckerDispatcherDispatchTest(unittest.TestCase):
    369 
    370     """Tests dispatch() method of CheckerDispatcher class."""
    371 
    372     def dispatch(self, file_path):
    373         """Call dispatch() with the given file path."""
    374         dispatcher = CheckerDispatcher()
    375         self.mock_handle_style_error = DefaultStyleErrorHandler('', None, None, [])
    376         checker = dispatcher.dispatch(file_path,
    377                                       self.mock_handle_style_error,
    378                                       min_confidence=3)
    379         return checker
    380 
    381     def assert_checker_none(self, file_path):
    382         """Assert that the dispatched checker is None."""
    383         checker = self.dispatch(file_path)
    384         self.assertTrue(checker is None, 'Checking: "%s"' % file_path)
    385 
    386     def assert_checker(self, file_path, expected_class):
    387         """Assert the type of the dispatched checker."""
    388         checker = self.dispatch(file_path)
    389         got_class = checker.__class__
    390         self.assertEquals(got_class, expected_class,
    391                           'For path "%(file_path)s" got %(got_class)s when '
    392                           "expecting %(expected_class)s."
    393                           % {"file_path": file_path,
    394                              "got_class": got_class,
    395                              "expected_class": expected_class})
    396 
    397     def assert_checker_changelog(self, file_path):
    398         """Assert that the dispatched checker is a ChangeLogChecker."""
    399         self.assert_checker(file_path, ChangeLogChecker)
    400 
    401     def assert_checker_cpp(self, file_path):
    402         """Assert that the dispatched checker is a CppChecker."""
    403         self.assert_checker(file_path, CppChecker)
    404 
    405     def assert_checker_python(self, file_path):
    406         """Assert that the dispatched checker is a PythonChecker."""
    407         self.assert_checker(file_path, PythonChecker)
    408 
    409     def assert_checker_text(self, file_path):
    410         """Assert that the dispatched checker is a TextChecker."""
    411         self.assert_checker(file_path, TextChecker)
    412 
    413     def assert_checker_xml(self, file_path):
    414         """Assert that the dispatched checker is a XMLChecker."""
    415         self.assert_checker(file_path, XMLChecker)
    416 
    417     def test_changelog_paths(self):
    418         """Test paths that should be checked as ChangeLog."""
    419         paths = [
    420                  "ChangeLog",
    421                  "ChangeLog-2009-06-16",
    422                  os.path.join("Source", "WebCore", "ChangeLog"),
    423                  ]
    424 
    425         for path in paths:
    426             self.assert_checker_changelog(path)
    427 
    428         # Check checker attributes on a typical input.
    429         file_path = "ChangeLog"
    430         self.assert_checker_changelog(file_path)
    431         checker = self.dispatch(file_path)
    432         self.assertEquals(checker.file_path, file_path)
    433         self.assertEquals(checker.handle_style_error,
    434                           self.mock_handle_style_error)
    435 
    436     def test_cpp_paths(self):
    437         """Test paths that should be checked as C++."""
    438         paths = [
    439             "-",
    440             "foo.c",
    441             "foo.cpp",
    442             "foo.h",
    443             ]
    444 
    445         for path in paths:
    446             self.assert_checker_cpp(path)
    447 
    448         # Check checker attributes on a typical input.
    449         file_base = "foo"
    450         file_extension = "c"
    451         file_path = file_base + "." + file_extension
    452         self.assert_checker_cpp(file_path)
    453         checker = self.dispatch(file_path)
    454         self.assertEquals(checker.file_extension, file_extension)
    455         self.assertEquals(checker.file_path, file_path)
    456         self.assertEquals(checker.handle_style_error, self.mock_handle_style_error)
    457         self.assertEquals(checker.min_confidence, 3)
    458         # Check "-" for good measure.
    459         file_base = "-"
    460         file_extension = ""
    461         file_path = file_base
    462         self.assert_checker_cpp(file_path)
    463         checker = self.dispatch(file_path)
    464         self.assertEquals(checker.file_extension, file_extension)
    465         self.assertEquals(checker.file_path, file_path)
    466 
    467     def test_python_paths(self):
    468         """Test paths that should be checked as Python."""
    469         paths = [
    470            "foo.py",
    471            "Tools/Scripts/modules/text_style.py",
    472         ]
    473 
    474         for path in paths:
    475             self.assert_checker_python(path)
    476 
    477         # Check checker attributes on a typical input.
    478         file_base = "foo"
    479         file_extension = "css"
    480         file_path = file_base + "." + file_extension
    481         self.assert_checker_text(file_path)
    482         checker = self.dispatch(file_path)
    483         self.assertEquals(checker.file_path, file_path)
    484         self.assertEquals(checker.handle_style_error,
    485                           self.mock_handle_style_error)
    486 
    487     def test_text_paths(self):
    488         """Test paths that should be checked as text."""
    489         paths = [
    490            "foo.ac",
    491            "foo.cc",
    492            "foo.cgi",
    493            "foo.css",
    494            "foo.exp",
    495            "foo.flex",
    496            "foo.gyp",
    497            "foo.gypi",
    498            "foo.html",
    499            "foo.idl",
    500            "foo.in",
    501            "foo.js",
    502            "foo.mm",
    503            "foo.php",
    504            "foo.pl",
    505            "foo.pm",
    506            "foo.pri",
    507            "foo.pro",
    508            "foo.rb",
    509            "foo.sh",
    510            "foo.txt",
    511            "foo.wm",
    512            "foo.xhtml",
    513            "foo.y",
    514            os.path.join("Source", "WebCore", "inspector", "front-end", "inspector.js"),
    515            os.path.join("Tools", "Scripts", "check-webkit-style"),
    516         ]
    517 
    518         for path in paths:
    519             self.assert_checker_text(path)
    520 
    521         # Check checker attributes on a typical input.
    522         file_base = "foo"
    523         file_extension = "css"
    524         file_path = file_base + "." + file_extension
    525         self.assert_checker_text(file_path)
    526         checker = self.dispatch(file_path)
    527         self.assertEquals(checker.file_path, file_path)
    528         self.assertEquals(checker.handle_style_error, self.mock_handle_style_error)
    529 
    530     def test_xml_paths(self):
    531         """Test paths that should be checked as XML."""
    532         paths = [
    533            "Source/WebCore/WebCore.vcproj/WebCore.vcproj",
    534            "WebKitLibraries/win/tools/vsprops/common.vsprops",
    535         ]
    536 
    537         for path in paths:
    538             self.assert_checker_xml(path)
    539 
    540         # Check checker attributes on a typical input.
    541         file_base = "foo"
    542         file_extension = "vcproj"
    543         file_path = file_base + "." + file_extension
    544         self.assert_checker_xml(file_path)
    545         checker = self.dispatch(file_path)
    546         self.assertEquals(checker.file_path, file_path)
    547         self.assertEquals(checker.handle_style_error,
    548                           self.mock_handle_style_error)
    549 
    550     def test_none_paths(self):
    551         """Test paths that have no file type.."""
    552         paths = [
    553            "Makefile",
    554            "foo.asdf",  # Non-sensical file extension.
    555            "foo.png",
    556            "foo.exe",
    557             ]
    558 
    559         for path in paths:
    560             self.assert_checker_none(path)
    561 
    562 
    563 class StyleProcessorConfigurationTest(unittest.TestCase):
    564 
    565     """Tests the StyleProcessorConfiguration class."""
    566 
    567     def setUp(self):
    568         self._error_messages = []
    569         """The messages written to _mock_stderr_write() of this class."""
    570 
    571     def _mock_stderr_write(self, message):
    572         self._error_messages.append(message)
    573 
    574     def _style_checker_configuration(self, output_format="vs7"):
    575         """Return a StyleProcessorConfiguration instance for testing."""
    576         base_rules = ["-whitespace", "+whitespace/tab"]
    577         filter_configuration = FilterConfiguration(base_rules=base_rules)
    578 
    579         return StyleProcessorConfiguration(
    580                    filter_configuration=filter_configuration,
    581                    max_reports_per_category={"whitespace/newline": 1},
    582                    min_confidence=3,
    583                    output_format=output_format,
    584                    stderr_write=self._mock_stderr_write)
    585 
    586     def test_init(self):
    587         """Test the __init__() method."""
    588         configuration = self._style_checker_configuration()
    589 
    590         # Check that __init__ sets the "public" data attributes correctly.
    591         self.assertEquals(configuration.max_reports_per_category,
    592                           {"whitespace/newline": 1})
    593         self.assertEquals(configuration.stderr_write, self._mock_stderr_write)
    594         self.assertEquals(configuration.min_confidence, 3)
    595 
    596     def test_is_reportable(self):
    597         """Test the is_reportable() method."""
    598         config = self._style_checker_configuration()
    599 
    600         self.assertTrue(config.is_reportable("whitespace/tab", 3, "foo.txt"))
    601 
    602         # Test the confidence check code path by varying the confidence.
    603         self.assertFalse(config.is_reportable("whitespace/tab", 2, "foo.txt"))
    604 
    605         # Test the category check code path by varying the category.
    606         self.assertFalse(config.is_reportable("whitespace/line", 4, "foo.txt"))
    607 
    608     def _call_write_style_error(self, output_format):
    609         config = self._style_checker_configuration(output_format=output_format)
    610         config.write_style_error(category="whitespace/tab",
    611                                  confidence_in_error=5,
    612                                  file_path="foo.h",
    613                                  line_number=100,
    614                                  message="message")
    615 
    616     def test_write_style_error_emacs(self):
    617         """Test the write_style_error() method."""
    618         self._call_write_style_error("emacs")
    619         self.assertEquals(self._error_messages,
    620                           ["foo.h:100:  message  [whitespace/tab] [5]\n"])
    621 
    622     def test_write_style_error_vs7(self):
    623         """Test the write_style_error() method."""
    624         self._call_write_style_error("vs7")
    625         self.assertEquals(self._error_messages,
    626                           ["foo.h(100):  message  [whitespace/tab] [5]\n"])
    627 
    628 
    629 class StyleProcessor_EndToEndTest(LoggingTestCase):
    630 
    631     """Test the StyleProcessor class with an emphasis on end-to-end tests."""
    632 
    633     def setUp(self):
    634         LoggingTestCase.setUp(self)
    635         self._messages = []
    636 
    637     def _mock_stderr_write(self, message):
    638         """Save a message so it can later be asserted."""
    639         self._messages.append(message)
    640 
    641     def test_init(self):
    642         """Test __init__ constructor."""
    643         configuration = StyleProcessorConfiguration(
    644                             filter_configuration=FilterConfiguration(),
    645                             max_reports_per_category={},
    646                             min_confidence=3,
    647                             output_format="vs7",
    648                             stderr_write=self._mock_stderr_write)
    649         processor = StyleProcessor(configuration)
    650 
    651         self.assertEquals(processor.error_count, 0)
    652         self.assertEquals(self._messages, [])
    653 
    654     def test_process(self):
    655         configuration = StyleProcessorConfiguration(
    656                             filter_configuration=FilterConfiguration(),
    657                             max_reports_per_category={},
    658                             min_confidence=3,
    659                             output_format="vs7",
    660                             stderr_write=self._mock_stderr_write)
    661         processor = StyleProcessor(configuration)
    662 
    663         processor.process(lines=['line1', 'Line with tab:\t'],
    664                           file_path='foo.txt')
    665         self.assertEquals(processor.error_count, 1)
    666         expected_messages = ['foo.txt(2):  Line contains tab character.  '
    667                              '[whitespace/tab] [5]\n']
    668         self.assertEquals(self._messages, expected_messages)
    669 
    670 
    671 class StyleProcessor_CodeCoverageTest(LoggingTestCase):
    672 
    673     """Test the StyleProcessor class with an emphasis on code coverage.
    674 
    675     This class makes heavy use of mock objects.
    676 
    677     """
    678 
    679     class MockDispatchedChecker(object):
    680 
    681         """A mock checker dispatched by the MockDispatcher."""
    682 
    683         def __init__(self, file_path, min_confidence, style_error_handler):
    684             self.file_path = file_path
    685             self.min_confidence = min_confidence
    686             self.style_error_handler = style_error_handler
    687 
    688         def check(self, lines):
    689             self.lines = lines
    690 
    691     class MockDispatcher(object):
    692 
    693         """A mock CheckerDispatcher class."""
    694 
    695         def __init__(self):
    696             self.dispatched_checker = None
    697 
    698         def should_skip_with_warning(self, file_path):
    699             return file_path.endswith('skip_with_warning.txt')
    700 
    701         def should_skip_without_warning(self, file_path):
    702             return file_path.endswith('skip_without_warning.txt')
    703 
    704         def should_check_and_strip_carriage_returns(self, file_path):
    705             return not file_path.endswith('carriage_returns_allowed.txt')
    706 
    707         def dispatch(self, file_path, style_error_handler, min_confidence):
    708             if file_path.endswith('do_not_process.txt'):
    709                 return None
    710 
    711             checker = StyleProcessor_CodeCoverageTest.MockDispatchedChecker(
    712                           file_path,
    713                           min_confidence,
    714                           style_error_handler)
    715 
    716             # Save the dispatched checker so the current test case has a
    717             # way to access and check it.
    718             self.dispatched_checker = checker
    719 
    720             return checker
    721 
    722     def setUp(self):
    723         LoggingTestCase.setUp(self)
    724         # We can pass an error-message swallower here because error message
    725         # output is tested instead in the end-to-end test case above.
    726         configuration = StyleProcessorConfiguration(
    727                             filter_configuration=FilterConfiguration(),
    728                             max_reports_per_category={"whitespace/newline": 1},
    729                             min_confidence=3,
    730                             output_format="vs7",
    731                             stderr_write=self._swallow_stderr_message)
    732 
    733         mock_carriage_checker_class = self._create_carriage_checker_class()
    734         mock_dispatcher = self.MockDispatcher()
    735         # We do not need to use a real incrementer here because error-count
    736         # incrementing is tested instead in the end-to-end test case above.
    737         mock_increment_error_count = self._do_nothing
    738 
    739         processor = StyleProcessor(configuration=configuration,
    740                         mock_carriage_checker_class=mock_carriage_checker_class,
    741                         mock_dispatcher=mock_dispatcher,
    742                         mock_increment_error_count=mock_increment_error_count)
    743 
    744         self._configuration = configuration
    745         self._mock_dispatcher = mock_dispatcher
    746         self._processor = processor
    747 
    748     def _do_nothing(self):
    749         # We provide this function so the caller can pass it to the
    750         # StyleProcessor constructor.  This lets us assert the equality of
    751         # the DefaultStyleErrorHandler instance generated by the process()
    752         # method with an expected instance.
    753         pass
    754 
    755     def _swallow_stderr_message(self, message):
    756         """Swallow a message passed to stderr.write()."""
    757         # This is a mock stderr.write() for passing to the constructor
    758         # of the StyleProcessorConfiguration class.
    759         pass
    760 
    761     def _create_carriage_checker_class(self):
    762 
    763         # Create a reference to self with a new name so its name does not
    764         # conflict with the self introduced below.
    765         test_case = self
    766 
    767         class MockCarriageChecker(object):
    768 
    769             """A mock carriage-return checker."""
    770 
    771             def __init__(self, style_error_handler):
    772                 self.style_error_handler = style_error_handler
    773 
    774                 # This gives the current test case access to the
    775                 # instantiated carriage checker.
    776                 test_case.carriage_checker = self
    777 
    778             def check(self, lines):
    779                 # Save the lines so the current test case has a way to access
    780                 # and check them.
    781                 self.lines = lines
    782 
    783                 return lines
    784 
    785         return MockCarriageChecker
    786 
    787     def test_should_process__skip_without_warning(self):
    788         """Test should_process() for a skip-without-warning file."""
    789         file_path = "foo/skip_without_warning.txt"
    790 
    791         self.assertFalse(self._processor.should_process(file_path))
    792 
    793     def test_should_process__skip_with_warning(self):
    794         """Test should_process() for a skip-with-warning file."""
    795         file_path = "foo/skip_with_warning.txt"
    796 
    797         self.assertFalse(self._processor.should_process(file_path))
    798 
    799         self.assertLog(['WARNING: File exempt from style guide. '
    800                         'Skipping: "foo/skip_with_warning.txt"\n'])
    801 
    802     def test_should_process__true_result(self):
    803         """Test should_process() for a file that should be processed."""
    804         file_path = "foo/skip_process.txt"
    805 
    806         self.assertTrue(self._processor.should_process(file_path))
    807 
    808     def test_process__checker_dispatched(self):
    809         """Test the process() method for a path with a dispatched checker."""
    810         file_path = 'foo.txt'
    811         lines = ['line1', 'line2']
    812         line_numbers = [100]
    813 
    814         expected_error_handler = DefaultStyleErrorHandler(
    815             configuration=self._configuration,
    816             file_path=file_path,
    817             increment_error_count=self._do_nothing,
    818             line_numbers=line_numbers)
    819 
    820         self._processor.process(lines=lines,
    821                                 file_path=file_path,
    822                                 line_numbers=line_numbers)
    823 
    824         # Check that the carriage-return checker was instantiated correctly
    825         # and was passed lines correctly.
    826         carriage_checker = self.carriage_checker
    827         self.assertEquals(carriage_checker.style_error_handler,
    828                           expected_error_handler)
    829         self.assertEquals(carriage_checker.lines, ['line1', 'line2'])
    830 
    831         # Check that the style checker was dispatched correctly and was
    832         # passed lines correctly.
    833         checker = self._mock_dispatcher.dispatched_checker
    834         self.assertEquals(checker.file_path, 'foo.txt')
    835         self.assertEquals(checker.min_confidence, 3)
    836         self.assertEquals(checker.style_error_handler, expected_error_handler)
    837 
    838         self.assertEquals(checker.lines, ['line1', 'line2'])
    839 
    840     def test_process__no_checker_dispatched(self):
    841         """Test the process() method for a path with no dispatched checker."""
    842         path = os.path.join('foo', 'do_not_process.txt')
    843         self.assertRaises(AssertionError, self._processor.process,
    844                           lines=['line1', 'line2'], file_path=path,
    845                           line_numbers=[100])
    846 
    847     def test_process__carriage_returns_not_stripped(self):
    848         """Test that carriage returns aren't stripped from files that are allowed to contain them."""
    849         file_path = 'carriage_returns_allowed.txt'
    850         lines = ['line1\r', 'line2\r']
    851         line_numbers = [100]
    852         self._processor.process(lines=lines,
    853                                 file_path=file_path,
    854                                 line_numbers=line_numbers)
    855         # The carriage return checker should never have been invoked, and so
    856         # should not have saved off any lines.
    857         self.assertFalse(hasattr(self.carriage_checker, 'lines'))
    858