|
1 #!/usr/bin/python |
|
2 # -*- coding: utf-8; -*- |
|
3 # |
|
4 # Copyright (C) 2009 Google Inc. All rights reserved. |
|
5 # Copyright (C) 2009 Torch Mobile Inc. |
|
6 # Copyright (C) 2009 Apple Inc. All rights reserved. |
|
7 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) |
|
8 # |
|
9 # Redistribution and use in source and binary forms, with or without |
|
10 # modification, are permitted provided that the following conditions are |
|
11 # met: |
|
12 # |
|
13 # * Redistributions of source code must retain the above copyright |
|
14 # notice, this list of conditions and the following disclaimer. |
|
15 # * Redistributions in binary form must reproduce the above |
|
16 # copyright notice, this list of conditions and the following disclaimer |
|
17 # in the documentation and/or other materials provided with the |
|
18 # distribution. |
|
19 # * Neither the name of Google Inc. nor the names of its |
|
20 # contributors may be used to endorse or promote products derived from |
|
21 # this software without specific prior written permission. |
|
22 # |
|
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
26 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
27 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
28 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
29 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
30 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
31 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
32 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
33 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
34 |
|
35 """Unit tests for style.py.""" |
|
36 |
|
37 import logging |
|
38 import os |
|
39 import unittest |
|
40 |
|
41 import checker as style |
|
42 from webkitpy.style_references import 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.cpp import CppChecker |
|
56 from checkers.python import PythonChecker |
|
57 from checkers.text import TextChecker |
|
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.assertEquals(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.assertFalse(rule in 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 assertNoCheck("WebKitTools/WebKitAPITest/main.cpp", |
|
215 "build/include") |
|
216 assertNoCheck("WebKit/qt/QGVLauncher/main.cpp", |
|
217 "build/include") |
|
218 assertNoCheck("WebKit/qt/QGVLauncher/main.cpp", |
|
219 "readability/streams") |
|
220 |
|
221 assertCheck("random_path.cpp", |
|
222 "readability/naming") |
|
223 assertNoCheck("WebKit/gtk/webkit/webkit.h", |
|
224 "readability/naming") |
|
225 assertNoCheck("WebKitTools/DumpRenderTree/gtk/DumpRenderTree.cpp", |
|
226 "readability/null") |
|
227 assertNoCheck("WebKit/efl/ewk/ewk_view.h", |
|
228 "readability/naming") |
|
229 assertNoCheck("WebCore/css/CSSParser.cpp", |
|
230 "readability/naming") |
|
231 assertNoCheck("WebKit/qt/tests/qwebelement/tst_qwebelement.cpp", |
|
232 "readability/naming") |
|
233 assertNoCheck( |
|
234 "JavaScriptCore/qt/tests/qscriptengine/tst_qscriptengine.cpp", |
|
235 "readability/naming") |
|
236 assertNoCheck("WebCore/ForwardingHeaders/debugger/Debugger.h", |
|
237 "build/header_guard") |
|
238 |
|
239 # Third-party Python code: webkitpy/thirdparty |
|
240 path = "WebKitTools/Scripts/webkitpy/thirdparty/mock.py" |
|
241 assertNoCheck(path, "build/include") |
|
242 assertNoCheck(path, "pep8/E401") # A random pep8 category. |
|
243 assertCheck(path, "pep8/W191") |
|
244 assertCheck(path, "pep8/W291") |
|
245 assertCheck(path, "whitespace/carriage_return") |
|
246 |
|
247 def test_max_reports_per_category(self): |
|
248 """Check that _MAX_REPORTS_PER_CATEGORY is valid.""" |
|
249 all_categories = self._all_categories() |
|
250 for category in _MAX_REPORTS_PER_CATEGORY.iterkeys(): |
|
251 self.assertTrue(category in all_categories, |
|
252 'Key "%s" is not a category' % category) |
|
253 |
|
254 |
|
255 class CheckWebKitStyleFunctionTest(unittest.TestCase): |
|
256 |
|
257 """Tests the functions with names of the form check_webkit_style_*.""" |
|
258 |
|
259 def test_check_webkit_style_configuration(self): |
|
260 # Exercise the code path to make sure the function does not error out. |
|
261 option_values = CommandOptionValues() |
|
262 configuration = check_webkit_style_configuration(option_values) |
|
263 |
|
264 def test_check_webkit_style_parser(self): |
|
265 # Exercise the code path to make sure the function does not error out. |
|
266 parser = check_webkit_style_parser() |
|
267 |
|
268 |
|
269 class CheckerDispatcherSkipTest(unittest.TestCase): |
|
270 |
|
271 """Tests the "should skip" methods of the CheckerDispatcher class.""" |
|
272 |
|
273 def setUp(self): |
|
274 self._dispatcher = CheckerDispatcher() |
|
275 |
|
276 def test_should_skip_with_warning(self): |
|
277 """Test should_skip_with_warning().""" |
|
278 # Check a non-skipped file. |
|
279 self.assertFalse(self._dispatcher.should_skip_with_warning("foo.txt")) |
|
280 |
|
281 # Check skipped files. |
|
282 paths_to_skip = [ |
|
283 "gtk2drawing.c", |
|
284 "gtkdrawing.h", |
|
285 "JavaScriptCore/qt/api/qscriptengine_p.h", |
|
286 "WebCore/platform/gtk/gtk2drawing.c", |
|
287 "WebCore/platform/gtk/gtkdrawing.h", |
|
288 "WebKit/gtk/tests/testatk.c", |
|
289 "WebKit/qt/Api/qwebpage.h", |
|
290 "WebKit/qt/tests/qwebsecurityorigin/tst_qwebsecurityorigin.cpp", |
|
291 ] |
|
292 |
|
293 for path in paths_to_skip: |
|
294 self.assertTrue(self._dispatcher.should_skip_with_warning(path), |
|
295 "Checking: " + path) |
|
296 |
|
297 def _assert_should_skip_without_warning(self, path, is_checker_none, |
|
298 expected): |
|
299 # Check the file type before asserting the return value. |
|
300 checker = self._dispatcher.dispatch(file_path=path, |
|
301 handle_style_error=None, |
|
302 min_confidence=3) |
|
303 message = 'while checking: %s' % path |
|
304 self.assertEquals(checker is None, is_checker_none, message) |
|
305 self.assertEquals(self._dispatcher.should_skip_without_warning(path), |
|
306 expected, message) |
|
307 |
|
308 def test_should_skip_without_warning__true(self): |
|
309 """Test should_skip_without_warning() for True return values.""" |
|
310 # Check a file with NONE file type. |
|
311 path = 'foo.asdf' # Non-sensical file extension. |
|
312 self._assert_should_skip_without_warning(path, |
|
313 is_checker_none=True, |
|
314 expected=True) |
|
315 |
|
316 # Check files with non-NONE file type. These examples must be |
|
317 # drawn from the _SKIPPED_FILES_WITHOUT_WARNING configuration |
|
318 # variable. |
|
319 path = os.path.join('LayoutTests', 'foo.txt') |
|
320 self._assert_should_skip_without_warning(path, |
|
321 is_checker_none=False, |
|
322 expected=True) |
|
323 |
|
324 def test_should_skip_without_warning__false(self): |
|
325 """Test should_skip_without_warning() for False return values.""" |
|
326 paths = ['foo.txt', |
|
327 os.path.join('LayoutTests', 'ChangeLog'), |
|
328 ] |
|
329 |
|
330 for path in paths: |
|
331 self._assert_should_skip_without_warning(path, |
|
332 is_checker_none=False, |
|
333 expected=False) |
|
334 |
|
335 |
|
336 class CheckerDispatcherDispatchTest(unittest.TestCase): |
|
337 |
|
338 """Tests dispatch() method of CheckerDispatcher class.""" |
|
339 |
|
340 def mock_handle_style_error(self): |
|
341 pass |
|
342 |
|
343 def dispatch(self, file_path): |
|
344 """Call dispatch() with the given file path.""" |
|
345 dispatcher = CheckerDispatcher() |
|
346 checker = dispatcher.dispatch(file_path, |
|
347 self.mock_handle_style_error, |
|
348 min_confidence=3) |
|
349 return checker |
|
350 |
|
351 def assert_checker_none(self, file_path): |
|
352 """Assert that the dispatched checker is None.""" |
|
353 checker = self.dispatch(file_path) |
|
354 self.assertTrue(checker is None, 'Checking: "%s"' % file_path) |
|
355 |
|
356 def assert_checker(self, file_path, expected_class): |
|
357 """Assert the type of the dispatched checker.""" |
|
358 checker = self.dispatch(file_path) |
|
359 got_class = checker.__class__ |
|
360 self.assertEquals(got_class, expected_class, |
|
361 'For path "%(file_path)s" got %(got_class)s when ' |
|
362 "expecting %(expected_class)s." |
|
363 % {"file_path": file_path, |
|
364 "got_class": got_class, |
|
365 "expected_class": expected_class}) |
|
366 |
|
367 def assert_checker_cpp(self, file_path): |
|
368 """Assert that the dispatched checker is a CppChecker.""" |
|
369 self.assert_checker(file_path, CppChecker) |
|
370 |
|
371 def assert_checker_python(self, file_path): |
|
372 """Assert that the dispatched checker is a PythonChecker.""" |
|
373 self.assert_checker(file_path, PythonChecker) |
|
374 |
|
375 def assert_checker_text(self, file_path): |
|
376 """Assert that the dispatched checker is a TextChecker.""" |
|
377 self.assert_checker(file_path, TextChecker) |
|
378 |
|
379 def test_cpp_paths(self): |
|
380 """Test paths that should be checked as C++.""" |
|
381 paths = [ |
|
382 "-", |
|
383 "foo.c", |
|
384 "foo.cpp", |
|
385 "foo.h", |
|
386 ] |
|
387 |
|
388 for path in paths: |
|
389 self.assert_checker_cpp(path) |
|
390 |
|
391 # Check checker attributes on a typical input. |
|
392 file_base = "foo" |
|
393 file_extension = "c" |
|
394 file_path = file_base + "." + file_extension |
|
395 self.assert_checker_cpp(file_path) |
|
396 checker = self.dispatch(file_path) |
|
397 self.assertEquals(checker.file_extension, file_extension) |
|
398 self.assertEquals(checker.file_path, file_path) |
|
399 self.assertEquals(checker.handle_style_error, self.mock_handle_style_error) |
|
400 self.assertEquals(checker.min_confidence, 3) |
|
401 # Check "-" for good measure. |
|
402 file_base = "-" |
|
403 file_extension = "" |
|
404 file_path = file_base |
|
405 self.assert_checker_cpp(file_path) |
|
406 checker = self.dispatch(file_path) |
|
407 self.assertEquals(checker.file_extension, file_extension) |
|
408 self.assertEquals(checker.file_path, file_path) |
|
409 |
|
410 def test_python_paths(self): |
|
411 """Test paths that should be checked as Python.""" |
|
412 paths = [ |
|
413 "foo.py", |
|
414 "WebKitTools/Scripts/modules/text_style.py", |
|
415 ] |
|
416 |
|
417 for path in paths: |
|
418 self.assert_checker_python(path) |
|
419 |
|
420 # Check checker attributes on a typical input. |
|
421 file_base = "foo" |
|
422 file_extension = "css" |
|
423 file_path = file_base + "." + file_extension |
|
424 self.assert_checker_text(file_path) |
|
425 checker = self.dispatch(file_path) |
|
426 self.assertEquals(checker.file_path, file_path) |
|
427 self.assertEquals(checker.handle_style_error, |
|
428 self.mock_handle_style_error) |
|
429 |
|
430 def test_text_paths(self): |
|
431 """Test paths that should be checked as text.""" |
|
432 paths = [ |
|
433 "ChangeLog", |
|
434 "ChangeLog-2009-06-16", |
|
435 "foo.ac", |
|
436 "foo.cc", |
|
437 "foo.cgi", |
|
438 "foo.css", |
|
439 "foo.exp", |
|
440 "foo.flex", |
|
441 "foo.gyp", |
|
442 "foo.gypi", |
|
443 "foo.html", |
|
444 "foo.idl", |
|
445 "foo.in", |
|
446 "foo.js", |
|
447 "foo.mm", |
|
448 "foo.php", |
|
449 "foo.pl", |
|
450 "foo.pm", |
|
451 "foo.pri", |
|
452 "foo.pro", |
|
453 "foo.rb", |
|
454 "foo.sh", |
|
455 "foo.txt", |
|
456 "foo.wm", |
|
457 "foo.xhtml", |
|
458 "foo.y", |
|
459 os.path.join("WebCore", "ChangeLog"), |
|
460 os.path.join("WebCore", "inspector", "front-end", "inspector.js"), |
|
461 os.path.join("WebKitTools", "Scripts", "check-webkit-style"), |
|
462 ] |
|
463 |
|
464 for path in paths: |
|
465 self.assert_checker_text(path) |
|
466 |
|
467 # Check checker attributes on a typical input. |
|
468 file_base = "foo" |
|
469 file_extension = "css" |
|
470 file_path = file_base + "." + file_extension |
|
471 self.assert_checker_text(file_path) |
|
472 checker = self.dispatch(file_path) |
|
473 self.assertEquals(checker.file_path, file_path) |
|
474 self.assertEquals(checker.handle_style_error, self.mock_handle_style_error) |
|
475 |
|
476 def test_none_paths(self): |
|
477 """Test paths that have no file type..""" |
|
478 paths = [ |
|
479 "Makefile", |
|
480 "foo.asdf", # Non-sensical file extension. |
|
481 "foo.png", |
|
482 "foo.exe", |
|
483 "foo.vcproj", |
|
484 ] |
|
485 |
|
486 for path in paths: |
|
487 self.assert_checker_none(path) |
|
488 |
|
489 |
|
490 class StyleProcessorConfigurationTest(unittest.TestCase): |
|
491 |
|
492 """Tests the StyleProcessorConfiguration class.""" |
|
493 |
|
494 def setUp(self): |
|
495 self._error_messages = [] |
|
496 """The messages written to _mock_stderr_write() of this class.""" |
|
497 |
|
498 def _mock_stderr_write(self, message): |
|
499 self._error_messages.append(message) |
|
500 |
|
501 def _style_checker_configuration(self, output_format="vs7"): |
|
502 """Return a StyleProcessorConfiguration instance for testing.""" |
|
503 base_rules = ["-whitespace", "+whitespace/tab"] |
|
504 filter_configuration = FilterConfiguration(base_rules=base_rules) |
|
505 |
|
506 return StyleProcessorConfiguration( |
|
507 filter_configuration=filter_configuration, |
|
508 max_reports_per_category={"whitespace/newline": 1}, |
|
509 min_confidence=3, |
|
510 output_format=output_format, |
|
511 stderr_write=self._mock_stderr_write) |
|
512 |
|
513 def test_init(self): |
|
514 """Test the __init__() method.""" |
|
515 configuration = self._style_checker_configuration() |
|
516 |
|
517 # Check that __init__ sets the "public" data attributes correctly. |
|
518 self.assertEquals(configuration.max_reports_per_category, |
|
519 {"whitespace/newline": 1}) |
|
520 self.assertEquals(configuration.stderr_write, self._mock_stderr_write) |
|
521 self.assertEquals(configuration.min_confidence, 3) |
|
522 |
|
523 def test_is_reportable(self): |
|
524 """Test the is_reportable() method.""" |
|
525 config = self._style_checker_configuration() |
|
526 |
|
527 self.assertTrue(config.is_reportable("whitespace/tab", 3, "foo.txt")) |
|
528 |
|
529 # Test the confidence check code path by varying the confidence. |
|
530 self.assertFalse(config.is_reportable("whitespace/tab", 2, "foo.txt")) |
|
531 |
|
532 # Test the category check code path by varying the category. |
|
533 self.assertFalse(config.is_reportable("whitespace/line", 4, "foo.txt")) |
|
534 |
|
535 def _call_write_style_error(self, output_format): |
|
536 config = self._style_checker_configuration(output_format=output_format) |
|
537 config.write_style_error(category="whitespace/tab", |
|
538 confidence_in_error=5, |
|
539 file_path="foo.h", |
|
540 line_number=100, |
|
541 message="message") |
|
542 |
|
543 def test_write_style_error_emacs(self): |
|
544 """Test the write_style_error() method.""" |
|
545 self._call_write_style_error("emacs") |
|
546 self.assertEquals(self._error_messages, |
|
547 ["foo.h:100: message [whitespace/tab] [5]\n"]) |
|
548 |
|
549 def test_write_style_error_vs7(self): |
|
550 """Test the write_style_error() method.""" |
|
551 self._call_write_style_error("vs7") |
|
552 self.assertEquals(self._error_messages, |
|
553 ["foo.h(100): message [whitespace/tab] [5]\n"]) |
|
554 |
|
555 |
|
556 class StyleProcessor_EndToEndTest(LoggingTestCase): |
|
557 |
|
558 """Test the StyleProcessor class with an emphasis on end-to-end tests.""" |
|
559 |
|
560 def setUp(self): |
|
561 LoggingTestCase.setUp(self) |
|
562 self._messages = [] |
|
563 |
|
564 def _mock_stderr_write(self, message): |
|
565 """Save a message so it can later be asserted.""" |
|
566 self._messages.append(message) |
|
567 |
|
568 def test_init(self): |
|
569 """Test __init__ constructor.""" |
|
570 configuration = StyleProcessorConfiguration( |
|
571 filter_configuration=FilterConfiguration(), |
|
572 max_reports_per_category={}, |
|
573 min_confidence=3, |
|
574 output_format="vs7", |
|
575 stderr_write=self._mock_stderr_write) |
|
576 processor = StyleProcessor(configuration) |
|
577 |
|
578 self.assertEquals(processor.error_count, 0) |
|
579 self.assertEquals(self._messages, []) |
|
580 |
|
581 def test_process(self): |
|
582 configuration = StyleProcessorConfiguration( |
|
583 filter_configuration=FilterConfiguration(), |
|
584 max_reports_per_category={}, |
|
585 min_confidence=3, |
|
586 output_format="vs7", |
|
587 stderr_write=self._mock_stderr_write) |
|
588 processor = StyleProcessor(configuration) |
|
589 |
|
590 processor.process(lines=['line1', 'Line with tab:\t'], |
|
591 file_path='foo.txt') |
|
592 self.assertEquals(processor.error_count, 1) |
|
593 expected_messages = ['foo.txt(2): Line contains tab character. ' |
|
594 '[whitespace/tab] [5]\n'] |
|
595 self.assertEquals(self._messages, expected_messages) |
|
596 |
|
597 |
|
598 class StyleProcessor_CodeCoverageTest(LoggingTestCase): |
|
599 |
|
600 """Test the StyleProcessor class with an emphasis on code coverage. |
|
601 |
|
602 This class makes heavy use of mock objects. |
|
603 |
|
604 """ |
|
605 |
|
606 class MockDispatchedChecker(object): |
|
607 |
|
608 """A mock checker dispatched by the MockDispatcher.""" |
|
609 |
|
610 def __init__(self, file_path, min_confidence, style_error_handler): |
|
611 self.file_path = file_path |
|
612 self.min_confidence = min_confidence |
|
613 self.style_error_handler = style_error_handler |
|
614 |
|
615 def check(self, lines): |
|
616 self.lines = lines |
|
617 |
|
618 class MockDispatcher(object): |
|
619 |
|
620 """A mock CheckerDispatcher class.""" |
|
621 |
|
622 def __init__(self): |
|
623 self.dispatched_checker = None |
|
624 |
|
625 def should_skip_with_warning(self, file_path): |
|
626 return file_path.endswith('skip_with_warning.txt') |
|
627 |
|
628 def should_skip_without_warning(self, file_path): |
|
629 return file_path.endswith('skip_without_warning.txt') |
|
630 |
|
631 def dispatch(self, file_path, style_error_handler, min_confidence): |
|
632 if file_path.endswith('do_not_process.txt'): |
|
633 return None |
|
634 |
|
635 checker = StyleProcessor_CodeCoverageTest.MockDispatchedChecker( |
|
636 file_path, |
|
637 min_confidence, |
|
638 style_error_handler) |
|
639 |
|
640 # Save the dispatched checker so the current test case has a |
|
641 # way to access and check it. |
|
642 self.dispatched_checker = checker |
|
643 |
|
644 return checker |
|
645 |
|
646 def setUp(self): |
|
647 LoggingTestCase.setUp(self) |
|
648 # We can pass an error-message swallower here because error message |
|
649 # output is tested instead in the end-to-end test case above. |
|
650 configuration = StyleProcessorConfiguration( |
|
651 filter_configuration=FilterConfiguration(), |
|
652 max_reports_per_category={"whitespace/newline": 1}, |
|
653 min_confidence=3, |
|
654 output_format="vs7", |
|
655 stderr_write=self._swallow_stderr_message) |
|
656 |
|
657 mock_carriage_checker_class = self._create_carriage_checker_class() |
|
658 mock_dispatcher = self.MockDispatcher() |
|
659 # We do not need to use a real incrementer here because error-count |
|
660 # incrementing is tested instead in the end-to-end test case above. |
|
661 mock_increment_error_count = self._do_nothing |
|
662 |
|
663 processor = StyleProcessor(configuration=configuration, |
|
664 mock_carriage_checker_class=mock_carriage_checker_class, |
|
665 mock_dispatcher=mock_dispatcher, |
|
666 mock_increment_error_count=mock_increment_error_count) |
|
667 |
|
668 self._configuration = configuration |
|
669 self._mock_dispatcher = mock_dispatcher |
|
670 self._processor = processor |
|
671 |
|
672 def _do_nothing(self): |
|
673 # We provide this function so the caller can pass it to the |
|
674 # StyleProcessor constructor. This lets us assert the equality of |
|
675 # the DefaultStyleErrorHandler instance generated by the process() |
|
676 # method with an expected instance. |
|
677 pass |
|
678 |
|
679 def _swallow_stderr_message(self, message): |
|
680 """Swallow a message passed to stderr.write().""" |
|
681 # This is a mock stderr.write() for passing to the constructor |
|
682 # of the StyleProcessorConfiguration class. |
|
683 pass |
|
684 |
|
685 def _create_carriage_checker_class(self): |
|
686 |
|
687 # Create a reference to self with a new name so its name does not |
|
688 # conflict with the self introduced below. |
|
689 test_case = self |
|
690 |
|
691 class MockCarriageChecker(object): |
|
692 |
|
693 """A mock carriage-return checker.""" |
|
694 |
|
695 def __init__(self, style_error_handler): |
|
696 self.style_error_handler = style_error_handler |
|
697 |
|
698 # This gives the current test case access to the |
|
699 # instantiated carriage checker. |
|
700 test_case.carriage_checker = self |
|
701 |
|
702 def check(self, lines): |
|
703 # Save the lines so the current test case has a way to access |
|
704 # and check them. |
|
705 self.lines = lines |
|
706 |
|
707 return lines |
|
708 |
|
709 return MockCarriageChecker |
|
710 |
|
711 def test_should_process__skip_without_warning(self): |
|
712 """Test should_process() for a skip-without-warning file.""" |
|
713 file_path = "foo/skip_without_warning.txt" |
|
714 |
|
715 self.assertFalse(self._processor.should_process(file_path)) |
|
716 |
|
717 def test_should_process__skip_with_warning(self): |
|
718 """Test should_process() for a skip-with-warning file.""" |
|
719 file_path = "foo/skip_with_warning.txt" |
|
720 |
|
721 self.assertFalse(self._processor.should_process(file_path)) |
|
722 |
|
723 self.assertLog(['WARNING: File exempt from style guide. ' |
|
724 'Skipping: "foo/skip_with_warning.txt"\n']) |
|
725 |
|
726 def test_should_process__true_result(self): |
|
727 """Test should_process() for a file that should be processed.""" |
|
728 file_path = "foo/skip_process.txt" |
|
729 |
|
730 self.assertTrue(self._processor.should_process(file_path)) |
|
731 |
|
732 def test_process__checker_dispatched(self): |
|
733 """Test the process() method for a path with a dispatched checker.""" |
|
734 file_path = 'foo.txt' |
|
735 lines = ['line1', 'line2'] |
|
736 line_numbers = [100] |
|
737 |
|
738 expected_error_handler = DefaultStyleErrorHandler( |
|
739 configuration=self._configuration, |
|
740 file_path=file_path, |
|
741 increment_error_count=self._do_nothing, |
|
742 line_numbers=line_numbers) |
|
743 |
|
744 self._processor.process(lines=lines, |
|
745 file_path=file_path, |
|
746 line_numbers=line_numbers) |
|
747 |
|
748 # Check that the carriage-return checker was instantiated correctly |
|
749 # and was passed lines correctly. |
|
750 carriage_checker = self.carriage_checker |
|
751 self.assertEquals(carriage_checker.style_error_handler, |
|
752 expected_error_handler) |
|
753 self.assertEquals(carriage_checker.lines, ['line1', 'line2']) |
|
754 |
|
755 # Check that the style checker was dispatched correctly and was |
|
756 # passed lines correctly. |
|
757 checker = self._mock_dispatcher.dispatched_checker |
|
758 self.assertEquals(checker.file_path, 'foo.txt') |
|
759 self.assertEquals(checker.min_confidence, 3) |
|
760 self.assertEquals(checker.style_error_handler, expected_error_handler) |
|
761 |
|
762 self.assertEquals(checker.lines, ['line1', 'line2']) |
|
763 |
|
764 def test_process__no_checker_dispatched(self): |
|
765 """Test the process() method for a path with no dispatched checker.""" |
|
766 path = os.path.join('foo', 'do_not_process.txt') |
|
767 self.assertRaises(AssertionError, self._processor.process, |
|
768 lines=['line1', 'line2'], file_path=path, |
|
769 line_numbers=[100]) |