• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@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
36import logging
37import os
38import webkitpy.thirdparty.unittest2 as unittest
39
40import checker as style
41from webkitpy.common.system.logtesting import LogTesting, TestLogStream
42from checker import _BASE_FILTER_RULES
43from checker import _MAX_REPORTS_PER_CATEGORY
44from checker import _PATH_RULES_SPECIFIER as PATH_RULES_SPECIFIER
45from checker import _all_categories
46from checker import check_webkit_style_configuration
47from checker import check_webkit_style_parser
48from checker import configure_logging
49from checker import CheckerDispatcher
50from checker import ProcessorBase
51from checker import StyleProcessor
52from checker import StyleProcessorConfiguration
53from checkers.cpp import CppChecker
54from checkers.jsonchecker import JSONChecker
55from checkers.python import PythonChecker
56from checkers.text import TextChecker
57from checkers.xml import XMLChecker
58from error_handlers import DefaultStyleErrorHandler
59from filter import validate_filter_rules
60from filter import FilterConfiguration
61from optparser import ArgumentParser
62from optparser import CommandOptionValues
63from webkitpy.common.system.logtesting import LoggingTestCase
64from webkitpy.style.filereader import TextFileReader
65
66
67class 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
113class 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
141class 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
152class 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
235class 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
249class 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
323class 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
337class 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", "inspector.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
524class 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
590class 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
632class 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