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