Home | History | Annotate | Download | only in style
      1 # -*- coding: utf-8; -*-
      2 #
      3 # Copyright (C) 2009 Google Inc. All rights reserved.
      4 # Copyright (C) 2009 Torch Mobile Inc.
      5 # Copyright (C) 2009 Apple Inc. All rights reserved.
      6 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek (at] gmail.com)
      7 #
      8 # Redistribution and use in source and binary forms, with or without
      9 # modification, are permitted provided that the following conditions are
     10 # met:
     11 #
     12 #    * Redistributions of source code must retain the above copyright
     13 # notice, this list of conditions and the following disclaimer.
     14 #    * Redistributions in binary form must reproduce the above
     15 # copyright notice, this list of conditions and the following disclaimer
     16 # in the documentation and/or other materials provided with the
     17 # distribution.
     18 #    * Neither the name of Google Inc. nor the names of its
     19 # contributors may be used to endorse or promote products derived from
     20 # this software without specific prior written permission.
     21 #
     22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     25 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     26 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     27 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     28 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     29 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     30 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     31 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     32 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     33 
     34 """Unit tests for style.py."""
     35 
     36 import logging
     37 import os
     38 import unittest
     39 
     40 import checker as style
     41 from webkitpy.common.system.logtesting import LogTesting, TestLogStream
     42 from checker import _BASE_FILTER_RULES
     43 from checker import _MAX_REPORTS_PER_CATEGORY
     44 from checker import _PATH_RULES_SPECIFIER as PATH_RULES_SPECIFIER
     45 from checker import _all_categories
     46 from checker import check_webkit_style_configuration
     47 from checker import check_webkit_style_parser
     48 from checker import configure_logging
     49 from checker import CheckerDispatcher
     50 from checker import ProcessorBase
     51 from checker import StyleProcessor
     52 from checker import StyleProcessorConfiguration
     53 from checkers.cpp import CppChecker
     54 from checkers.jsonchecker import JSONChecker
     55 from checkers.python import PythonChecker
     56 from checkers.text import TextChecker
     57 from checkers.xml import XMLChecker
     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.assertEqual(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.assertNotIn(rule, 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         assertCheck("random_path.cpp",
    215                     "readability/naming")
    216         assertNoCheck("Source/core/css/CSSParser-in.cpp",
    217                       "readability/naming")
    218 
    219         # Third-party Python code: webkitpy/thirdparty
    220         path = "Tools/Scripts/webkitpy/thirdparty/mock.py"
    221         assertNoCheck(path, "build/include")
    222         assertNoCheck(path, "pep8/E401")  # A random pep8 category.
    223         assertCheck(path, "pep8/W191")
    224         assertCheck(path, "pep8/W291")
    225         assertCheck(path, "whitespace/carriage_return")
    226 
    227     def test_max_reports_per_category(self):
    228         """Check that _MAX_REPORTS_PER_CATEGORY is valid."""
    229         all_categories = self._all_categories()
    230         for category in _MAX_REPORTS_PER_CATEGORY.iterkeys():
    231             self.assertIn(category, all_categories,
    232                           'Key "%s" is not a category' % category)
    233 
    234 
    235 class CheckWebKitStyleFunctionTest(unittest.TestCase):
    236 
    237     """Tests the functions with names of the form check_webkit_style_*."""
    238 
    239     def test_check_webkit_style_configuration(self):
    240         # Exercise the code path to make sure the function does not error out.
    241         option_values = CommandOptionValues()
    242         configuration = check_webkit_style_configuration(option_values)
    243 
    244     def test_check_webkit_style_parser(self):
    245         # Exercise the code path to make sure the function does not error out.
    246         parser = check_webkit_style_parser()
    247 
    248 
    249 class CheckerDispatcherSkipTest(unittest.TestCase):
    250 
    251     """Tests the "should skip" methods of the CheckerDispatcher class."""
    252 
    253     def setUp(self):
    254         self._dispatcher = CheckerDispatcher()
    255 
    256     def test_should_skip_with_warning(self):
    257         """Test should_skip_with_warning()."""
    258         # Check skipped files.
    259         paths_to_skip = [
    260            "Source/WebKit/gtk/tests/testatk.c",
    261            "Source/WebKit2/UIProcess/API/gtk/webkit2.h",
    262            "Source/WebKit2/UIProcess/API/gtk/WebKitWebView.h",
    263            "Source/WebKit2/UIProcess/API/gtk/WebKitLoader.h",
    264             ]
    265 
    266         for path in paths_to_skip:
    267             self.assertTrue(self._dispatcher.should_skip_with_warning(path),
    268                             "Checking: " + path)
    269 
    270         # Verify that some files are not skipped.
    271         paths_not_to_skip = [
    272            "foo.txt",
    273            "Source/WebKit2/UIProcess/API/gtk/HelperClass.cpp",
    274            "Source/WebKit2/UIProcess/API/gtk/HelperClass.h",
    275            "Source/WebKit2/UIProcess/API/gtk/WebKitWebView.cpp",
    276            "Source/WebKit2/UIProcess/API/gtk/WebKitWebViewPrivate.h",
    277            "Source/WebKit2/UIProcess/API/gtk/tests/WebViewTest.cpp",
    278            "Source/WebKit2/UIProcess/API/gtk/tests/WebViewTest.h",
    279             ]
    280 
    281         for path in paths_not_to_skip:
    282             self.assertFalse(self._dispatcher.should_skip_with_warning(path))
    283 
    284     def _assert_should_skip_without_warning(self, path, is_checker_none,
    285                                             expected):
    286         # Check the file type before asserting the return value.
    287         checker = self._dispatcher.dispatch(file_path=path,
    288                                             handle_style_error=None,
    289                                             min_confidence=3)
    290         message = 'while checking: %s' % path
    291         self.assertEqual(checker is None, is_checker_none, message)
    292         self.assertEqual(self._dispatcher.should_skip_without_warning(path),
    293                           expected, message)
    294 
    295     def test_should_skip_without_warning__true(self):
    296         """Test should_skip_without_warning() for True return values."""
    297         # Check a file with NONE file type.
    298         path = 'foo.asdf'  # Non-sensical file extension.
    299         self._assert_should_skip_without_warning(path,
    300                                                  is_checker_none=True,
    301                                                  expected=True)
    302 
    303         # Check files with non-NONE file type.  These examples must be
    304         # drawn from the _SKIPPED_FILES_WITHOUT_WARNING configuration
    305         # variable.
    306         path = os.path.join('LayoutTests', 'foo.txt')
    307         self._assert_should_skip_without_warning(path,
    308                                                  is_checker_none=False,
    309                                                  expected=True)
    310 
    311     def test_should_skip_without_warning__false(self):
    312         """Test should_skip_without_warning() for False return values."""
    313         paths = ['foo.txt',
    314                  os.path.join('LayoutTests', 'TestExpectations'),
    315         ]
    316 
    317         for path in paths:
    318             self._assert_should_skip_without_warning(path,
    319                                                      is_checker_none=False,
    320                                                      expected=False)
    321 
    322 
    323 class CheckerDispatcherCarriageReturnTest(unittest.TestCase):
    324     def test_should_check_and_strip_carriage_returns(self):
    325         files = {
    326             'foo.txt': True,
    327             'foo.cpp': True,
    328             'foo.vcproj': False,
    329             'foo.vsprops': False,
    330         }
    331 
    332         dispatcher = CheckerDispatcher()
    333         for file_path, expected_result in files.items():
    334             self.assertEqual(dispatcher.should_check_and_strip_carriage_returns(file_path), expected_result, 'Checking: %s' % file_path)
    335 
    336 
    337 class CheckerDispatcherDispatchTest(unittest.TestCase):
    338 
    339     """Tests dispatch() method of CheckerDispatcher class."""
    340 
    341     def dispatch(self, file_path):
    342         """Call dispatch() with the given file path."""
    343         dispatcher = CheckerDispatcher()
    344         self.mock_handle_style_error = DefaultStyleErrorHandler('', None, None, [])
    345         checker = dispatcher.dispatch(file_path,
    346                                       self.mock_handle_style_error,
    347                                       min_confidence=3)
    348         return checker
    349 
    350     def assert_checker_none(self, file_path):
    351         """Assert that the dispatched checker is None."""
    352         checker = self.dispatch(file_path)
    353         self.assertIsNone(checker, 'Checking: "%s"' % file_path)
    354 
    355     def assert_checker(self, file_path, expected_class):
    356         """Assert the type of the dispatched checker."""
    357         checker = self.dispatch(file_path)
    358         got_class = checker.__class__
    359         self.assertEqual(got_class, expected_class,
    360                           'For path "%(file_path)s" got %(got_class)s when '
    361                           "expecting %(expected_class)s."
    362                           % {"file_path": file_path,
    363                              "got_class": got_class,
    364                              "expected_class": expected_class})
    365 
    366     def assert_checker_cpp(self, file_path):
    367         """Assert that the dispatched checker is a CppChecker."""
    368         self.assert_checker(file_path, CppChecker)
    369 
    370     def assert_checker_json(self, file_path):
    371         """Assert that the dispatched checker is a JSONChecker."""
    372         self.assert_checker(file_path, JSONChecker)
    373 
    374     def assert_checker_python(self, file_path):
    375         """Assert that the dispatched checker is a PythonChecker."""
    376         self.assert_checker(file_path, PythonChecker)
    377 
    378     def assert_checker_text(self, file_path):
    379         """Assert that the dispatched checker is a TextChecker."""
    380         self.assert_checker(file_path, TextChecker)
    381 
    382     def assert_checker_xml(self, file_path):
    383         """Assert that the dispatched checker is a XMLChecker."""
    384         self.assert_checker(file_path, XMLChecker)
    385 
    386     def test_cpp_paths(self):
    387         """Test paths that should be checked as C++."""
    388         paths = [
    389             "-",
    390             "foo.c",
    391             "foo.cpp",
    392             "foo.h",
    393             ]
    394 
    395         for path in paths:
    396             self.assert_checker_cpp(path)
    397 
    398         # Check checker attributes on a typical input.
    399         file_base = "foo"
    400         file_extension = "c"
    401         file_path = file_base + "." + file_extension
    402         self.assert_checker_cpp(file_path)
    403         checker = self.dispatch(file_path)
    404         self.assertEqual(checker.file_extension, file_extension)
    405         self.assertEqual(checker.file_path, file_path)
    406         self.assertEqual(checker.handle_style_error, self.mock_handle_style_error)
    407         self.assertEqual(checker.min_confidence, 3)
    408         # Check "-" for good measure.
    409         file_base = "-"
    410         file_extension = ""
    411         file_path = file_base
    412         self.assert_checker_cpp(file_path)
    413         checker = self.dispatch(file_path)
    414         self.assertEqual(checker.file_extension, file_extension)
    415         self.assertEqual(checker.file_path, file_path)
    416 
    417     def test_json_paths(self):
    418         """Test paths that should be checked as JSON."""
    419         paths = [
    420            "Source/WebCore/inspector/Inspector.json",
    421            "Tools/BuildSlaveSupport/build.webkit.org-config/config.json",
    422         ]
    423 
    424         for path in paths:
    425             self.assert_checker_json(path)
    426 
    427         # Check checker attributes on a typical input.
    428         file_base = "foo"
    429         file_extension = "json"
    430         file_path = file_base + "." + file_extension
    431         self.assert_checker_json(file_path)
    432         checker = self.dispatch(file_path)
    433         self.assertEqual(checker._handle_style_error,
    434                           self.mock_handle_style_error)
    435 
    436     def test_python_paths(self):
    437         """Test paths that should be checked as Python."""
    438         paths = [
    439            "foo.py",
    440            "Tools/Scripts/modules/text_style.py",
    441         ]
    442 
    443         for path in paths:
    444             self.assert_checker_python(path)
    445 
    446         # Check checker attributes on a typical input.
    447         file_base = "foo"
    448         file_extension = "css"
    449         file_path = file_base + "." + file_extension
    450         self.assert_checker_text(file_path)
    451         checker = self.dispatch(file_path)
    452         self.assertEqual(checker.file_path, file_path)
    453         self.assertEqual(checker.handle_style_error,
    454                           self.mock_handle_style_error)
    455 
    456     def test_text_paths(self):
    457         """Test paths that should be checked as text."""
    458         paths = [
    459            "foo.cc",
    460            "foo.cgi",
    461            "foo.css",
    462            "foo.gyp",
    463            "foo.gypi",
    464            "foo.html",
    465            "foo.idl",
    466            "foo.in",
    467            "foo.js",
    468            "foo.mm",
    469            "foo.php",
    470            "foo.pl",
    471            "foo.pm",
    472            "foo.rb",
    473            "foo.sh",
    474            "foo.txt",
    475            "foo.xhtml",
    476            "foo.y",
    477            os.path.join("Source", "WebCore", "inspector", "front-end", "Main.js"),
    478            os.path.join("Tools", "Scripts", "check-webkit-style"),
    479         ]
    480 
    481         for path in paths:
    482             self.assert_checker_text(path)
    483 
    484         # Check checker attributes on a typical input.
    485         file_base = "foo"
    486         file_extension = "css"
    487         file_path = file_base + "." + file_extension
    488         self.assert_checker_text(file_path)
    489         checker = self.dispatch(file_path)
    490         self.assertEqual(checker.file_path, file_path)
    491         self.assertEqual(checker.handle_style_error, self.mock_handle_style_error)
    492 
    493     def test_xml_paths(self):
    494         """Test paths that should be checked as XML."""
    495         paths = [
    496            "Source/WebCore/WebCore.vcproj/WebCore.vcproj",
    497            "WebKitLibraries/win/tools/vsprops/common.vsprops",
    498         ]
    499 
    500         for path in paths:
    501             self.assert_checker_xml(path)
    502 
    503         # Check checker attributes on a typical input.
    504         file_base = "foo"
    505         file_extension = "vcproj"
    506         file_path = file_base + "." + file_extension
    507         self.assert_checker_xml(file_path)
    508         checker = self.dispatch(file_path)
    509         self.assertEqual(checker._handle_style_error,
    510                           self.mock_handle_style_error)
    511 
    512     def test_none_paths(self):
    513         """Test paths that have no file type.."""
    514         paths = [
    515            "Makefile",
    516            "foo.asdf",  # Non-sensical file extension.
    517            "foo.exe",
    518             ]
    519 
    520         for path in paths:
    521             self.assert_checker_none(path)
    522 
    523 
    524 class StyleProcessorConfigurationTest(unittest.TestCase):
    525 
    526     """Tests the StyleProcessorConfiguration class."""
    527 
    528     def setUp(self):
    529         self._error_messages = []
    530         """The messages written to _mock_stderr_write() of this class."""
    531 
    532     def _mock_stderr_write(self, message):
    533         self._error_messages.append(message)
    534 
    535     def _style_checker_configuration(self, output_format="vs7"):
    536         """Return a StyleProcessorConfiguration instance for testing."""
    537         base_rules = ["-whitespace", "+whitespace/tab"]
    538         filter_configuration = FilterConfiguration(base_rules=base_rules)
    539 
    540         return StyleProcessorConfiguration(
    541                    filter_configuration=filter_configuration,
    542                    max_reports_per_category={"whitespace/newline": 1},
    543                    min_confidence=3,
    544                    output_format=output_format,
    545                    stderr_write=self._mock_stderr_write)
    546 
    547     def test_init(self):
    548         """Test the __init__() method."""
    549         configuration = self._style_checker_configuration()
    550 
    551         # Check that __init__ sets the "public" data attributes correctly.
    552         self.assertEqual(configuration.max_reports_per_category,
    553                           {"whitespace/newline": 1})
    554         self.assertEqual(configuration.stderr_write, self._mock_stderr_write)
    555         self.assertEqual(configuration.min_confidence, 3)
    556 
    557     def test_is_reportable(self):
    558         """Test the is_reportable() method."""
    559         config = self._style_checker_configuration()
    560 
    561         self.assertTrue(config.is_reportable("whitespace/tab", 3, "foo.txt"))
    562 
    563         # Test the confidence check code path by varying the confidence.
    564         self.assertFalse(config.is_reportable("whitespace/tab", 2, "foo.txt"))
    565 
    566         # Test the category check code path by varying the category.
    567         self.assertFalse(config.is_reportable("whitespace/line", 4, "foo.txt"))
    568 
    569     def _call_write_style_error(self, output_format):
    570         config = self._style_checker_configuration(output_format=output_format)
    571         config.write_style_error(category="whitespace/tab",
    572                                  confidence_in_error=5,
    573                                  file_path="foo.h",
    574                                  line_number=100,
    575                                  message="message")
    576 
    577     def test_write_style_error_emacs(self):
    578         """Test the write_style_error() method."""
    579         self._call_write_style_error("emacs")
    580         self.assertEqual(self._error_messages,
    581                           ["foo.h:100:  message  [whitespace/tab] [5]\n"])
    582 
    583     def test_write_style_error_vs7(self):
    584         """Test the write_style_error() method."""
    585         self._call_write_style_error("vs7")
    586         self.assertEqual(self._error_messages,
    587                           ["foo.h(100):  message  [whitespace/tab] [5]\n"])
    588 
    589 
    590 class StyleProcessor_EndToEndTest(LoggingTestCase):
    591 
    592     """Test the StyleProcessor class with an emphasis on end-to-end tests."""
    593 
    594     def setUp(self):
    595         LoggingTestCase.setUp(self)
    596         self._messages = []
    597 
    598     def _mock_stderr_write(self, message):
    599         """Save a message so it can later be asserted."""
    600         self._messages.append(message)
    601 
    602     def test_init(self):
    603         """Test __init__ constructor."""
    604         configuration = StyleProcessorConfiguration(
    605                             filter_configuration=FilterConfiguration(),
    606                             max_reports_per_category={},
    607                             min_confidence=3,
    608                             output_format="vs7",
    609                             stderr_write=self._mock_stderr_write)
    610         processor = StyleProcessor(configuration)
    611 
    612         self.assertEqual(processor.error_count, 0)
    613         self.assertEqual(self._messages, [])
    614 
    615     def test_process(self):
    616         configuration = StyleProcessorConfiguration(
    617                             filter_configuration=FilterConfiguration(),
    618                             max_reports_per_category={},
    619                             min_confidence=3,
    620                             output_format="vs7",
    621                             stderr_write=self._mock_stderr_write)
    622         processor = StyleProcessor(configuration)
    623 
    624         processor.process(lines=['line1', 'Line with tab:\t'],
    625                           file_path='foo.txt')
    626         self.assertEqual(processor.error_count, 1)
    627         expected_messages = ['foo.txt(2):  Line contains tab character.  '
    628                              '[whitespace/tab] [5]\n']
    629         self.assertEqual(self._messages, expected_messages)
    630 
    631 
    632 class StyleProcessor_CodeCoverageTest(LoggingTestCase):
    633 
    634     """Test the StyleProcessor class with an emphasis on code coverage.
    635 
    636     This class makes heavy use of mock objects.
    637 
    638     """
    639 
    640     class MockDispatchedChecker(object):
    641 
    642         """A mock checker dispatched by the MockDispatcher."""
    643 
    644         def __init__(self, file_path, min_confidence, style_error_handler):
    645             self.file_path = file_path
    646             self.min_confidence = min_confidence
    647             self.style_error_handler = style_error_handler
    648 
    649         def check(self, lines):
    650             self.lines = lines
    651 
    652     class MockDispatcher(object):
    653 
    654         """A mock CheckerDispatcher class."""
    655 
    656         def __init__(self):
    657             self.dispatched_checker = None
    658 
    659         def should_skip_with_warning(self, file_path):
    660             return file_path.endswith('skip_with_warning.txt')
    661 
    662         def should_skip_without_warning(self, file_path):
    663             return file_path.endswith('skip_without_warning.txt')
    664 
    665         def should_check_and_strip_carriage_returns(self, file_path):
    666             return not file_path.endswith('carriage_returns_allowed.txt')
    667 
    668         def dispatch(self, file_path, style_error_handler, min_confidence):
    669             if file_path.endswith('do_not_process.txt'):
    670                 return None
    671 
    672             checker = StyleProcessor_CodeCoverageTest.MockDispatchedChecker(
    673                           file_path,
    674                           min_confidence,
    675                           style_error_handler)
    676 
    677             # Save the dispatched checker so the current test case has a
    678             # way to access and check it.
    679             self.dispatched_checker = checker
    680 
    681             return checker
    682 
    683     def setUp(self):
    684         LoggingTestCase.setUp(self)
    685         # We can pass an error-message swallower here because error message
    686         # output is tested instead in the end-to-end test case above.
    687         configuration = StyleProcessorConfiguration(
    688                             filter_configuration=FilterConfiguration(),
    689                             max_reports_per_category={"whitespace/newline": 1},
    690                             min_confidence=3,
    691                             output_format="vs7",
    692                             stderr_write=self._swallow_stderr_message)
    693 
    694         mock_carriage_checker_class = self._create_carriage_checker_class()
    695         mock_dispatcher = self.MockDispatcher()
    696         # We do not need to use a real incrementer here because error-count
    697         # incrementing is tested instead in the end-to-end test case above.
    698         mock_increment_error_count = self._do_nothing
    699 
    700         processor = StyleProcessor(configuration=configuration,
    701                         mock_carriage_checker_class=mock_carriage_checker_class,
    702                         mock_dispatcher=mock_dispatcher,
    703                         mock_increment_error_count=mock_increment_error_count)
    704 
    705         self._configuration = configuration
    706         self._mock_dispatcher = mock_dispatcher
    707         self._processor = processor
    708 
    709     def _do_nothing(self):
    710         # We provide this function so the caller can pass it to the
    711         # StyleProcessor constructor.  This lets us assert the equality of
    712         # the DefaultStyleErrorHandler instance generated by the process()
    713         # method with an expected instance.
    714         pass
    715 
    716     def _swallow_stderr_message(self, message):
    717         """Swallow a message passed to stderr.write()."""
    718         # This is a mock stderr.write() for passing to the constructor
    719         # of the StyleProcessorConfiguration class.
    720         pass
    721 
    722     def _create_carriage_checker_class(self):
    723 
    724         # Create a reference to self with a new name so its name does not
    725         # conflict with the self introduced below.
    726         test_case = self
    727 
    728         class MockCarriageChecker(object):
    729 
    730             """A mock carriage-return checker."""
    731 
    732             def __init__(self, style_error_handler):
    733                 self.style_error_handler = style_error_handler
    734 
    735                 # This gives the current test case access to the
    736                 # instantiated carriage checker.
    737                 test_case.carriage_checker = self
    738 
    739             def check(self, lines):
    740                 # Save the lines so the current test case has a way to access
    741                 # and check them.
    742                 self.lines = lines
    743 
    744                 return lines
    745 
    746         return MockCarriageChecker
    747 
    748     def test_should_process__skip_without_warning(self):
    749         """Test should_process() for a skip-without-warning file."""
    750         file_path = "foo/skip_without_warning.txt"
    751 
    752         self.assertFalse(self._processor.should_process(file_path))
    753 
    754     def test_should_process__skip_with_warning(self):
    755         """Test should_process() for a skip-with-warning file."""
    756         file_path = "foo/skip_with_warning.txt"
    757 
    758         self.assertFalse(self._processor.should_process(file_path))
    759 
    760         self.assertLog(['WARNING: File exempt from style guide. '
    761                         'Skipping: "foo/skip_with_warning.txt"\n'])
    762 
    763     def test_should_process__true_result(self):
    764         """Test should_process() for a file that should be processed."""
    765         file_path = "foo/skip_process.txt"
    766 
    767         self.assertTrue(self._processor.should_process(file_path))
    768 
    769     def test_process__checker_dispatched(self):
    770         """Test the process() method for a path with a dispatched checker."""
    771         file_path = 'foo.txt'
    772         lines = ['line1', 'line2']
    773         line_numbers = [100]
    774 
    775         expected_error_handler = DefaultStyleErrorHandler(
    776             configuration=self._configuration,
    777             file_path=file_path,
    778             increment_error_count=self._do_nothing,
    779             line_numbers=line_numbers)
    780 
    781         self._processor.process(lines=lines,
    782                                 file_path=file_path,
    783                                 line_numbers=line_numbers)
    784 
    785         # Check that the carriage-return checker was instantiated correctly
    786         # and was passed lines correctly.
    787         carriage_checker = self.carriage_checker
    788         self.assertEqual(carriage_checker.style_error_handler,
    789                           expected_error_handler)
    790         self.assertEqual(carriage_checker.lines, ['line1', 'line2'])
    791 
    792         # Check that the style checker was dispatched correctly and was
    793         # passed lines correctly.
    794         checker = self._mock_dispatcher.dispatched_checker
    795         self.assertEqual(checker.file_path, 'foo.txt')
    796         self.assertEqual(checker.min_confidence, 3)
    797         self.assertEqual(checker.style_error_handler, expected_error_handler)
    798 
    799         self.assertEqual(checker.lines, ['line1', 'line2'])
    800 
    801     def test_process__no_checker_dispatched(self):
    802         """Test the process() method for a path with no dispatched checker."""
    803         path = os.path.join('foo', 'do_not_process.txt')
    804         self.assertRaises(AssertionError, self._processor.process,
    805                           lines=['line1', 'line2'], file_path=path,
    806                           line_numbers=[100])
    807 
    808     def test_process__carriage_returns_not_stripped(self):
    809         """Test that carriage returns aren't stripped from files that are allowed to contain them."""
    810         file_path = 'carriage_returns_allowed.txt'
    811         lines = ['line1\r', 'line2\r']
    812         line_numbers = [100]
    813         self._processor.process(lines=lines,
    814                                 file_path=file_path,
    815                                 line_numbers=line_numbers)
    816         # The carriage return checker should never have been invoked, and so
    817         # should not have saved off any lines.
    818         self.assertFalse(hasattr(self.carriage_checker, 'lines'))
    819