| OLD | NEW |
| (Empty) |
| 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 | |
| 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('tests', '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('tests', '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_erro
r) | |
| 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_erro
r) | |
| 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 allowe
d 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')) | |
| OLD | NEW |