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