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