d632f0599486ff82ddee75e2e74b01bcd9fca002
[WebKit.git] / WebKitTools / 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.style_references import parse_patch
43 from webkitpy.style_references import LogTesting
44 from webkitpy.style_references import TestLogStream
45 from checker import _BASE_FILTER_RULES
46 from checker import _MAX_REPORTS_PER_CATEGORY
47 from checker import _PATH_RULES_SPECIFIER as PATH_RULES_SPECIFIER
48 from checker import _all_categories
49 from checker import check_webkit_style_configuration
50 from checker import check_webkit_style_parser
51 from checker import configure_logging
52 from checker import ProcessorDispatcher
53 from checker import StyleChecker
54 from checker import StyleCheckerConfiguration
55 from filter import validate_filter_rules
56 from filter import FilterConfiguration
57 from optparser import ArgumentParser
58 from optparser import CommandOptionValues
59 from processors.cpp import CppProcessor
60 from processors.python import PythonProcessor
61 from processors.text import TextProcessor
62 from webkitpy.common.system.logtesting import LoggingTestCase
63
64
65 class ConfigureLoggingTestBase(unittest.TestCase):
66
67     """Base class for testing configure_logging().
68
69     Sub-classes should implement:
70
71       is_verbose: The is_verbose value to pass to configure_logging().
72
73     """
74
75     def setUp(self):
76         is_verbose = self.is_verbose
77
78         log_stream = TestLogStream(self)
79
80         # Use a logger other than the root logger or one prefixed with
81         # webkit so as not to conflict with test-webkitpy logging.
82         logger = logging.getLogger("unittest")
83
84         # Configure the test logger not to pass messages along to the
85         # root logger.  This prevents test messages from being
86         # propagated to loggers used by test-webkitpy logging (e.g.
87         # the root logger).
88         logger.propagate = False
89
90         self._handlers = configure_logging(stream=log_stream, logger=logger,
91                                            is_verbose=is_verbose)
92         self._log = logger
93         self._log_stream = log_stream
94
95     def tearDown(self):
96         """Reset logging to its original state.
97
98         This method ensures that the logging configuration set up
99         for a unit test does not affect logging in other unit tests.
100
101         """
102         logger = self._log
103         for handler in self._handlers:
104             logger.removeHandler(handler)
105
106     def assert_log_messages(self, messages):
107         """Assert that the logged messages equal the given messages."""
108         self._log_stream.assertMessages(messages)
109
110
111 class ConfigureLoggingTest(ConfigureLoggingTestBase):
112
113     """Tests the configure_logging() function."""
114
115     is_verbose = False
116
117     def test_warning_message(self):
118         self._log.warn("test message")
119         self.assert_log_messages(["WARNING: test message\n"])
120
121     def test_below_warning_message(self):
122         # We test the boundary case of a logging level equal to 29.
123         # In practice, we will probably only be calling log.info(),
124         # which corresponds to a logging level of 20.
125         level = logging.WARNING - 1  # Equals 29.
126         self._log.log(level, "test message")
127         self.assert_log_messages(["test message\n"])
128
129     def test_debug_message(self):
130         self._log.debug("test message")
131         self.assert_log_messages([])
132
133     def test_two_messages(self):
134         self._log.info("message1")
135         self._log.info("message2")
136         self.assert_log_messages(["message1\n", "message2\n"])
137
138
139 class ConfigureLoggingVerboseTest(ConfigureLoggingTestBase):
140
141     """Tests the configure_logging() function with is_verbose True."""
142
143     is_verbose = True
144
145     def test_debug_message(self):
146         self._log.debug("test message")
147         self.assert_log_messages(["unittest: DEBUG    test message\n"])
148
149
150 class GlobalVariablesTest(unittest.TestCase):
151
152     """Tests validity of the global variables."""
153
154     def _all_categories(self):
155         return _all_categories()
156
157     def defaults(self):
158         return style._check_webkit_style_defaults()
159
160     def test_webkit_base_filter_rules(self):
161         base_filter_rules = _BASE_FILTER_RULES
162         defaults = self.defaults()
163         already_seen = []
164         validate_filter_rules(base_filter_rules, self._all_categories())
165         # Also do some additional checks.
166         for rule in base_filter_rules:
167             # Check no leading or trailing white space.
168             self.assertEquals(rule, rule.strip())
169             # All categories are on by default, so defaults should
170             # begin with -.
171             self.assertTrue(rule.startswith('-'))
172             # Check no rule occurs twice.
173             self.assertFalse(rule in already_seen)
174             already_seen.append(rule)
175
176     def test_defaults(self):
177         """Check that default arguments are valid."""
178         default_options = self.defaults()
179
180         # FIXME: We should not need to call parse() to determine
181         #        whether the default arguments are valid.
182         parser = ArgumentParser(all_categories=self._all_categories(),
183                                 base_filter_rules=[],
184                                 default_options=default_options)
185         # No need to test the return value here since we test parse()
186         # on valid arguments elsewhere.
187         #
188         # The default options are valid: no error or SystemExit.
189         parser.parse(args=[])
190
191     def test_path_rules_specifier(self):
192         all_categories = self._all_categories()
193         for (sub_paths, path_rules) in PATH_RULES_SPECIFIER:
194             validate_filter_rules(path_rules, self._all_categories())
195
196         config = FilterConfiguration(path_specific=PATH_RULES_SPECIFIER)
197
198         def assertCheck(path, category):
199             """Assert that the given category should be checked."""
200             message = ('Should check category "%s" for path "%s".'
201                        % (category, path))
202             self.assertTrue(config.should_check(category, path))
203
204         def assertNoCheck(path, category):
205             """Assert that the given category should not be checked."""
206             message = ('Should not check category "%s" for path "%s".'
207                        % (category, path))
208             self.assertFalse(config.should_check(category, path), message)
209
210         assertCheck("random_path.cpp",
211                     "build/include")
212         assertNoCheck("WebKitTools/WebKitAPITest/main.cpp",
213                       "build/include")
214         assertNoCheck("WebKit/qt/QGVLauncher/main.cpp",
215                       "build/include")
216         assertNoCheck("WebKit/qt/QGVLauncher/main.cpp",
217                       "readability/streams")
218
219         assertCheck("random_path.cpp",
220                     "readability/naming")
221         assertNoCheck("WebKit/gtk/webkit/webkit.h",
222                       "readability/naming")
223         assertNoCheck("WebKitTools/DumpRenderTree/gtk/DumpRenderTree.cpp",
224                       "readability/null")
225         assertNoCheck("WebKit/efl/ewk/ewk_view.h",
226                       "readability/naming")
227         assertNoCheck("WebCore/css/CSSParser.cpp",
228                       "readability/naming")
229         assertNoCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp",
230                       "readability/naming")
231         assertNoCheck(
232             "JavaScriptCore/qt/tests/qscriptengine/tst_qscriptengine.cpp",
233             "readability/naming")
234         assertNoCheck("WebCore/ForwardingHeaders/debugger/Debugger.h",
235                       "build/header_guard")
236
237         # Third-party Python code: webkitpy/thirdparty
238         path = "WebKitTools/Scripts/webkitpy/thirdparty/mock.py"
239         assertNoCheck(path, "build/include")
240         assertNoCheck(path, "pep8/E401")  # A random pep8 category.
241         assertCheck(path, "pep8/W191")
242         assertCheck(path, "pep8/W291")
243         assertCheck(path, "whitespace/carriage_return")
244
245     def test_max_reports_per_category(self):
246         """Check that _MAX_REPORTS_PER_CATEGORY is valid."""
247         all_categories = self._all_categories()
248         for category in _MAX_REPORTS_PER_CATEGORY.iterkeys():
249             self.assertTrue(category in all_categories,
250                             'Key "%s" is not a category' % category)
251
252
253 class CheckWebKitStyleFunctionTest(unittest.TestCase):
254
255     """Tests the functions with names of the form check_webkit_style_*."""
256
257     def test_check_webkit_style_configuration(self):
258         # Exercise the code path to make sure the function does not error out.
259         option_values = CommandOptionValues()
260         configuration = check_webkit_style_configuration(option_values)
261
262     def test_check_webkit_style_parser(self):
263         # Exercise the code path to make sure the function does not error out.
264         parser = check_webkit_style_parser()
265
266
267 class ProcessorDispatcherSkipTest(unittest.TestCase):
268
269     """Tests the "should skip" methods of the ProcessorDispatcher class."""
270
271     def test_should_skip_with_warning(self):
272         """Test should_skip_with_warning()."""
273         dispatcher = ProcessorDispatcher()
274
275         # Check a non-skipped file.
276         self.assertFalse(dispatcher.should_skip_with_warning("foo.txt"))
277
278         # Check skipped files.
279         paths_to_skip = [
280            "gtk2drawing.c",
281            "gtk2drawing.h",
282            "JavaScriptCore/qt/api/qscriptengine_p.h",
283            "WebCore/platform/gtk/gtk2drawing.c",
284            "WebCore/platform/gtk/gtk2drawing.h",
285            "WebKit/gtk/tests/testatk.c",
286            "WebKit/qt/Api/qwebpage.h",
287            "WebKit/qt/tests/qwebsecurityorigin/tst_qwebsecurityorigin.cpp",
288             ]
289
290         for path in paths_to_skip:
291             self.assertTrue(dispatcher.should_skip_with_warning(path),
292                             "Checking: " + path)
293
294     def test_should_skip_without_warning(self):
295         """Test should_skip_without_warning()."""
296         dispatcher = ProcessorDispatcher()
297
298         # Check a non-skipped file.
299         self.assertFalse(dispatcher.should_skip_without_warning("foo.txt"))
300
301         # Check skipped files.
302         paths_to_skip = [
303            # LayoutTests folder
304            "LayoutTests/foo.txt",
305             ]
306
307         for path in paths_to_skip:
308             self.assertTrue(dispatcher.should_skip_without_warning(path),
309                             "Checking: " + path)
310
311
312 class ProcessorDispatcherDispatchTest(unittest.TestCase):
313
314     """Tests dispatch_processor() method of ProcessorDispatcher class."""
315
316     def mock_handle_style_error(self):
317         pass
318
319     def dispatch_processor(self, file_path):
320         """Call dispatch_processor() with the given file path."""
321         dispatcher = ProcessorDispatcher()
322         processor = dispatcher.dispatch_processor(file_path,
323                                                   self.mock_handle_style_error,
324                                                   min_confidence=3)
325         return processor
326
327     def assert_processor_none(self, file_path):
328         """Assert that the dispatched processor is None."""
329         processor = self.dispatch_processor(file_path)
330         self.assertTrue(processor is None, 'Checking: "%s"' % file_path)
331
332     def assert_processor(self, file_path, expected_class):
333         """Assert the type of the dispatched processor."""
334         processor = self.dispatch_processor(file_path)
335         got_class = processor.__class__
336         self.assertEquals(got_class, expected_class,
337                           'For path "%(file_path)s" got %(got_class)s when '
338                           "expecting %(expected_class)s."
339                           % {"file_path": file_path,
340                              "got_class": got_class,
341                              "expected_class": expected_class})
342
343     def assert_processor_cpp(self, file_path):
344         """Assert that the dispatched processor is a CppProcessor."""
345         self.assert_processor(file_path, CppProcessor)
346
347     def assert_processor_python(self, file_path):
348         """Assert that the dispatched processor is a PythonProcessor."""
349         self.assert_processor(file_path, PythonProcessor)
350
351     def assert_processor_text(self, file_path):
352         """Assert that the dispatched processor is a TextProcessor."""
353         self.assert_processor(file_path, TextProcessor)
354
355     def test_cpp_paths(self):
356         """Test paths that should be checked as C++."""
357         paths = [
358             "-",
359             "foo.c",
360             "foo.cpp",
361             "foo.h",
362             ]
363
364         for path in paths:
365             self.assert_processor_cpp(path)
366
367         # Check processor attributes on a typical input.
368         file_base = "foo"
369         file_extension = "c"
370         file_path = file_base + "." + file_extension
371         self.assert_processor_cpp(file_path)
372         processor = self.dispatch_processor(file_path)
373         self.assertEquals(processor.file_extension, file_extension)
374         self.assertEquals(processor.file_path, file_path)
375         self.assertEquals(processor.handle_style_error, self.mock_handle_style_error)
376         self.assertEquals(processor.min_confidence, 3)
377         # Check "-" for good measure.
378         file_base = "-"
379         file_extension = ""
380         file_path = file_base
381         self.assert_processor_cpp(file_path)
382         processor = self.dispatch_processor(file_path)
383         self.assertEquals(processor.file_extension, file_extension)
384         self.assertEquals(processor.file_path, file_path)
385
386     def test_python_paths(self):
387         """Test paths that should be checked as Python."""
388         paths = [
389            "foo.py",
390            "WebKitTools/Scripts/modules/text_style.py",
391         ]
392
393         for path in paths:
394             self.assert_processor_python(path)
395
396         # Check processor attributes on a typical input.
397         file_base = "foo"
398         file_extension = "css"
399         file_path = file_base + "." + file_extension
400         self.assert_processor_text(file_path)
401         processor = self.dispatch_processor(file_path)
402         self.assertEquals(processor.file_path, file_path)
403         self.assertEquals(processor.handle_style_error,
404                           self.mock_handle_style_error)
405
406     def test_text_paths(self):
407         """Test paths that should be checked as text."""
408         paths = [
409            "ChangeLog",
410            "foo.css",
411            "foo.html",
412            "foo.idl",
413            "foo.js",
414            "foo.mm",
415            "foo.php",
416            "foo.pm",
417            "foo.txt",
418            "FooChangeLog.bak",
419            "WebCore/ChangeLog",
420            "WebCore/inspector/front-end/inspector.js",
421            "WebKitTools/Scripts/check-webkit-style",
422         ]
423
424         for path in paths:
425             self.assert_processor_text(path)
426
427         # Check processor attributes on a typical input.
428         file_base = "foo"
429         file_extension = "css"
430         file_path = file_base + "." + file_extension
431         self.assert_processor_text(file_path)
432         processor = self.dispatch_processor(file_path)
433         self.assertEquals(processor.file_path, file_path)
434         self.assertEquals(processor.handle_style_error, self.mock_handle_style_error)
435
436     def test_none_paths(self):
437         """Test paths that have no file type.."""
438         paths = [
439            "Makefile",
440            "foo.png",
441            "foo.exe",
442             ]
443
444         for path in paths:
445             self.assert_processor_none(path)
446
447
448 class StyleCheckerConfigurationTest(unittest.TestCase):
449
450     """Tests the StyleCheckerConfiguration class."""
451
452     def setUp(self):
453         self._error_messages = []
454         """The messages written to _mock_stderr_write() of this class."""
455
456     def _mock_stderr_write(self, message):
457         self._error_messages.append(message)
458
459     def _style_checker_configuration(self, output_format="vs7"):
460         """Return a StyleCheckerConfiguration instance for testing."""
461         base_rules = ["-whitespace", "+whitespace/tab"]
462         filter_configuration = FilterConfiguration(base_rules=base_rules)
463
464         return StyleCheckerConfiguration(
465                    filter_configuration=filter_configuration,
466                    max_reports_per_category={"whitespace/newline": 1},
467                    min_confidence=3,
468                    output_format=output_format,
469                    stderr_write=self._mock_stderr_write)
470
471     def test_init(self):
472         """Test the __init__() method."""
473         configuration = self._style_checker_configuration()
474
475         # Check that __init__ sets the "public" data attributes correctly.
476         self.assertEquals(configuration.max_reports_per_category,
477                           {"whitespace/newline": 1})
478         self.assertEquals(configuration.stderr_write, self._mock_stderr_write)
479         self.assertEquals(configuration.min_confidence, 3)
480
481     def test_is_reportable(self):
482         """Test the is_reportable() method."""
483         config = self._style_checker_configuration()
484
485         self.assertTrue(config.is_reportable("whitespace/tab", 3, "foo.txt"))
486
487         # Test the confidence check code path by varying the confidence.
488         self.assertFalse(config.is_reportable("whitespace/tab", 2, "foo.txt"))
489
490         # Test the category check code path by varying the category.
491         self.assertFalse(config.is_reportable("whitespace/line", 4, "foo.txt"))
492
493     def _call_write_style_error(self, output_format):
494         config = self._style_checker_configuration(output_format=output_format)
495         config.write_style_error(category="whitespace/tab",
496                                  confidence_in_error=5,
497                                  file_path="foo.h",
498                                  line_number=100,
499                                  message="message")
500
501     def test_write_style_error_emacs(self):
502         """Test the write_style_error() method."""
503         self._call_write_style_error("emacs")
504         self.assertEquals(self._error_messages,
505                           ["foo.h:100:  message  [whitespace/tab] [5]\n"])
506
507     def test_write_style_error_vs7(self):
508         """Test the write_style_error() method."""
509         self._call_write_style_error("vs7")
510         self.assertEquals(self._error_messages,
511                           ["foo.h(100):  message  [whitespace/tab] [5]\n"])
512
513
514 class StyleCheckerTest(unittest.TestCase):
515
516     """Test the StyleChecker class."""
517
518     def _mock_stderr_write(self, message):
519         pass
520
521     def _style_checker(self, configuration):
522         return StyleChecker(configuration)
523
524     def test_init(self):
525         """Test __init__ constructor."""
526         configuration = StyleCheckerConfiguration(
527                             filter_configuration=FilterConfiguration(),
528                             max_reports_per_category={},
529                             min_confidence=3,
530                             output_format="vs7",
531                             stderr_write=self._mock_stderr_write)
532
533         style_checker = self._style_checker(configuration)
534
535         self.assertEquals(style_checker._configuration, configuration)
536         self.assertEquals(style_checker.error_count, 0)
537         self.assertEquals(style_checker.file_count, 0)
538
539
540 class StyleCheckerCheckFileBase(LoggingTestCase):
541
542     def setUp(self):
543         LoggingTestCase.setUp(self)
544         self.warning_messages = ""
545
546     def mock_stderr_write(self, warning_message):
547         self.warning_messages += warning_message
548
549     def _style_checker_configuration(self):
550         return StyleCheckerConfiguration(
551             filter_configuration=FilterConfiguration(),
552             max_reports_per_category={"whitespace/newline": 1},
553             min_confidence=3,
554             output_format="vs7",
555             stderr_write=self.mock_stderr_write)
556
557
558 class StyleCheckerCheckFileTest(StyleCheckerCheckFileBase):
559
560     """Test the check_file() method of the StyleChecker class.
561
562     The check_file() method calls its process_file parameter when
563     given a file that should not be skipped.
564
565     The "got_*" attributes of this class are the parameters passed
566     to process_file by calls to check_file() made by this test
567     class. These attributes allow us to check the parameter values
568     passed internally to the process_file function.
569
570     Attributes:
571       got_file_path: The file_path parameter passed by check_file()
572                      to its process_file parameter.
573       got_handle_style_error: The handle_style_error parameter passed
574                               by check_file() to its process_file
575                               parameter.
576       got_processor: The processor parameter passed by check_file() to
577                      its process_file parameter.
578       warning_messages: A string containing all of the warning messages
579                         written to the mock_stderr_write method of
580                         this class.
581
582     """
583     def setUp(self):
584         StyleCheckerCheckFileBase.setUp(self)
585         self.got_file_path = None
586         self.got_handle_style_error = None
587         self.got_processor = None
588
589     def mock_handle_style_error(self):
590         pass
591
592     def mock_os_path_exists(self, path):
593         # We deliberately make it so that this method returns False unless
594         # the caller has made an effort to put "does_exist" in the path.
595         return path.find("does_exist") > -1
596
597     def mock_process_file(self, processor, file_path, handle_style_error):
598         """A mock _process_file().
599
600         See the documentation for this class for more information
601         on this function.
602
603         """
604         self.got_file_path = file_path
605         self.got_handle_style_error = handle_style_error
606         self.got_processor = processor
607
608     def assert_attributes(self,
609                           expected_file_path,
610                           expected_handle_style_error,
611                           expected_processor,
612                           expected_warning_messages):
613         """Assert that the attributes of this class equal the given values."""
614         self.assertEquals(self.got_file_path, expected_file_path)
615         self.assertEquals(self.got_handle_style_error, expected_handle_style_error)
616         self.assertEquals(self.got_processor, expected_processor)
617         self.assertEquals(self.warning_messages, expected_warning_messages)
618
619     def call_check_file(self, file_path):
620         """Call the check_file() method of a test StyleChecker instance."""
621         # Confirm that the attributes are reset.
622         self.assert_attributes(None, None, None, "")
623
624         configuration = self._style_checker_configuration()
625
626         style_checker = StyleChecker(configuration)
627
628         style_checker.check_file(file_path=file_path,
629             mock_handle_style_error=self.mock_handle_style_error,
630             mock_os_path_exists=self.mock_os_path_exists,
631             mock_process_file=self.mock_process_file)
632
633         file_count = 1 if self.mock_os_path_exists(file_path) else 0
634
635         self.assertEquals(file_count, style_checker.file_count)
636
637     def test_check_file_does_not_exist(self):
638         file_path = "file_does_not_exist.txt"
639
640         # Confirm that the file does not exist.
641         self.assertFalse(self.mock_os_path_exists(file_path))
642
643         # Check the outcome.
644         self.assertRaises(SystemExit, self.call_check_file, file_path)
645         self.assertLog(["ERROR: File does not exist: "
646                         "file_does_not_exist.txt\n"])
647
648     def test_check_file_on_skip_without_warning(self):
649         """Test check_file() for a skipped-without-warning file."""
650
651         file_path = "LayoutTests/does_exist/foo.txt"
652
653         dispatcher = ProcessorDispatcher()
654         # Confirm that the input file is truly a skipped-without-warning file.
655         self.assertTrue(dispatcher.should_skip_without_warning(file_path))
656
657         # Check the outcome.
658         self.call_check_file(file_path)
659         self.assert_attributes(None, None, None, "")
660
661     def test_check_file_on_skip_with_warning(self):
662         """Test check_file() for a skipped-with-warning file."""
663
664         file_path = "does_exist/gtk2drawing.c"
665
666         dispatcher = ProcessorDispatcher()
667         # Check that the input file is truly a skipped-with-warning file.
668         self.assertTrue(dispatcher.should_skip_with_warning(file_path))
669
670         # Check the outcome.
671         self.call_check_file(file_path)
672         self.assert_attributes(None, None, None, "")
673         self.assertLog(["WARNING: File exempt from style guide. "
674                         'Skipping: "does_exist/gtk2drawing.c"\n'])
675
676     def test_check_file_on_non_skipped(self):
677
678         # We use a C++ file since by using a CppProcessor, we can check
679         # that all of the possible information is getting passed to
680         # process_file (in particular, the min_confidence parameter).
681         file_base = "foo_does_exist"
682         file_extension = "cpp"
683         file_path = file_base + "." + file_extension
684
685         dispatcher = ProcessorDispatcher()
686         # Check that the input file is truly a C++ file.
687         self.assertEquals(dispatcher._file_type(file_path), style.FileType.CPP)
688
689         # Check the outcome.
690         self.call_check_file(file_path)
691
692         expected_processor = CppProcessor(file_path, file_extension, self.mock_handle_style_error, 3)
693
694         self.assert_attributes(file_path,
695                                self.mock_handle_style_error,
696                                expected_processor,
697                                "")
698
699
700 class StyleCheckerCheckPatchTest(StyleCheckerCheckFileBase):
701
702     """Test the check_patch() method of the StyleChecker class.
703
704     Internally, the check_patch() method calls StyleChecker.check_file() for
705     each file that appears in the patch string.  This class passes a mock
706     check_file() method to check_patch() to facilitate unit-testing.  The
707     "got_*" attributes of this class are the parameters that check_patch()
708     passed to check_file().  (We test only a single call.)  These attributes
709     let us check that check_patch() is calling check_file() correctly.
710
711     Attributes:
712       got_file_path: The value that check_patch() passed as the file_path
713                      parameter to the mock_check_file() function.
714       got_line_numbers: The value that check_patch() passed as the line_numbers
715                         parameter to the mock_check_file() function.
716
717     """
718
719     _file_path = "__init__.py"
720
721     # The modified line_numbers array for this patch is: [2].
722     _patch_string = """diff --git a/__init__.py b/__init__.py
723 index ef65bee..e3db70e 100644
724 --- a/__init__.py
725 +++ b/__init__.py
726 @@ -1,1 +1,2 @@
727  # Required for Python to search this directory for module files
728 +# New line
729 """
730
731     def setUp(self):
732         StyleCheckerCheckFileBase.setUp(self)
733         self._got_file_path = None
734         self._got_line_numbers = None
735
736     def _mock_check_file(self, file_path, line_numbers):
737         self._got_file_path = file_path
738         self._got_line_numbers = line_numbers
739
740     def test_check_patch(self):
741         patch_files = parse_patch(self._patch_string)
742         diff = patch_files[self._file_path]
743
744         configuration = self._style_checker_configuration()
745
746         style_checker = StyleChecker(configuration)
747
748         style_checker.check_patch(patch_string=self._patch_string,
749                                   mock_check_file=self._mock_check_file)
750
751         self.assertEquals(self._got_file_path, "__init__.py")
752         self.assertEquals(self._got_line_numbers, set([2]))
753
754
755 class StyleCheckerCheckPathsTest(unittest.TestCase):
756
757     """Test the check_paths() method of the StyleChecker class."""
758
759     class MockOs(object):
760
761         class MockPath(object):
762
763             """A mock os.path."""
764
765             def isdir(self, path):
766                 return path == "directory"
767
768         def __init__(self):
769             self.path = self.MockPath()
770
771         def walk(self, directory):
772             """A mock of os.walk."""
773             if directory == "directory":
774                 dirs = [("dir_path1", [], ["file1", "file2"]),
775                         ("dir_path2", [], ["file3"])]
776                 return dirs
777             return None
778
779     def setUp(self):
780         self._checked_files = []
781
782     def _mock_check_file(self, file):
783         self._checked_files.append(file)
784
785     def test_check_paths(self):
786         """Test StyleChecker.check_paths()."""
787         checker = StyleChecker(configuration=None)
788         mock_check_file = self._mock_check_file
789         mock_os = self.MockOs()
790
791         # Confirm that checked files is empty at the outset.
792         self.assertEquals(self._checked_files, [])
793         checker.check_paths(["path1", "directory"],
794                             mock_check_file=mock_check_file,
795                             mock_os=mock_os)
796         self.assertEquals(self._checked_files,
797                           ["path1",
798                            os.path.join("dir_path1", "file1"),
799                            os.path.join("dir_path1", "file2"),
800                            os.path.join("dir_path2", "file3")])