#!/usr/bin/python
# -*- coding: utf-8; -*-
#
# Copyright (C) 2009 Google Inc. All rights reserved.
# Copyright (C) 2009 Torch Mobile Inc.
# Copyright (C) 2009 Apple Inc. All rights reserved.
# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com)
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Unit tests for style.py."""
import unittest
import checker as style
from checker import _PATH_RULES_SPECIFIER as PATH_RULES_SPECIFIER
from checker import style_categories
from checker import ProcessorDispatcher
from checker import ProcessorOptions
from checker import StyleChecker
from filter import validate_filter_rules
from filter import FilterConfiguration
from processors.cpp import CppProcessor
from processors.text import TextProcessor
class ProcessorOptionsTest(unittest.TestCase):
"""Tests ProcessorOptions class."""
def test_init(self):
"""Test __init__ constructor."""
# Check default parameters.
options = ProcessorOptions()
self.assertEquals(options.extra_flag_values, {})
self.assertEquals(options.filter_configuration, FilterConfiguration())
self.assertEquals(options.git_commit, None)
self.assertEquals(options.max_reports_per_category, {})
self.assertEquals(options.output_format, "emacs")
self.assertEquals(options.verbosity, 1)
# Check argument validation.
self.assertRaises(ValueError, ProcessorOptions, output_format="bad")
ProcessorOptions(output_format="emacs") # No ValueError: works
ProcessorOptions(output_format="vs7") # works
self.assertRaises(ValueError, ProcessorOptions, verbosity=0)
self.assertRaises(ValueError, ProcessorOptions, verbosity=6)
ProcessorOptions(verbosity=1) # works
ProcessorOptions(verbosity=5) # works
# Check attributes.
filter_configuration = FilterConfiguration(base_rules=["+"])
options = ProcessorOptions(extra_flag_values={"extra_value" : 2},
filter_configuration=filter_configuration,
git_commit="commit",
max_reports_per_category={"category": 3},
output_format="vs7",
verbosity=3)
self.assertEquals(options.extra_flag_values, {"extra_value" : 2})
self.assertEquals(options.filter_configuration, filter_configuration)
self.assertEquals(options.git_commit, "commit")
self.assertEquals(options.max_reports_per_category, {"category": 3})
self.assertEquals(options.output_format, "vs7")
self.assertEquals(options.verbosity, 3)
def test_eq(self):
"""Test __eq__ equality function."""
# == calls __eq__.
self.assertTrue(ProcessorOptions() == ProcessorOptions())
# Verify that a difference in any argument causes equality to fail.
filter_configuration = FilterConfiguration(base_rules=["+"])
options = ProcessorOptions(extra_flag_values={"extra_value" : 1},
filter_configuration=filter_configuration,
git_commit="commit",
max_reports_per_category={"category": 3},
output_format="vs7",
verbosity=1)
self.assertFalse(options == ProcessorOptions(extra_flag_values={"extra_value" : 2}))
new_config = FilterConfiguration(base_rules=["-"])
self.assertFalse(options ==
ProcessorOptions(filter_configuration=new_config))
self.assertFalse(options == ProcessorOptions(git_commit="commit2"))
self.assertFalse(options == ProcessorOptions(max_reports_per_category=
{"category": 2}))
self.assertFalse(options == ProcessorOptions(output_format="emacs"))
self.assertFalse(options == ProcessorOptions(verbosity=2))
def test_ne(self):
"""Test __ne__ inequality function."""
# != calls __ne__.
# By default, __ne__ always returns true on different objects.
# Thus, just check the distinguishing case to verify that the
# code defines __ne__.
self.assertFalse(ProcessorOptions() != ProcessorOptions())
def test_is_reportable(self):
"""Test is_reportable()."""
filter_configuration = FilterConfiguration(base_rules=["-xyz"])
options = ProcessorOptions(filter_configuration=filter_configuration,
verbosity=3)
# Test verbosity
self.assertTrue(options.is_reportable("abc", 3, "foo.h"))
self.assertFalse(options.is_reportable("abc", 2, "foo.h"))
# Test filter
self.assertTrue(options.is_reportable("xy", 3, "foo.h"))
self.assertFalse(options.is_reportable("xyz", 3, "foo.h"))
class GlobalVariablesTest(unittest.TestCase):
"""Tests validity of the global variables."""
def _all_categories(self):
return style.style_categories()
def defaults(self):
return style.webkit_argument_defaults()
def test_filter_rules(self):
defaults = self.defaults()
already_seen = []
validate_filter_rules(defaults.base_filter_rules,
self._all_categories())
# Also do some additional checks.
for rule in defaults.base_filter_rules:
# Check no leading or trailing white space.
self.assertEquals(rule, rule.strip())
# All categories are on by default, so defaults should
# begin with -.
self.assertTrue(rule.startswith('-'))
# Check no rule occurs twice.
self.assertFalse(rule in already_seen)
already_seen.append(rule)
def test_defaults(self):
"""Check that default arguments are valid."""
defaults = self.defaults()
# FIXME: We should not need to call parse() to determine
# whether the default arguments are valid.
parser = style.ArgumentParser(defaults)
# No need to test the return value here since we test parse()
# on valid arguments elsewhere.
parser.parse([]) # arguments valid: no error or SystemExit
def test_path_rules_specifier(self):
all_categories = style_categories()
for (sub_paths, path_rules) in PATH_RULES_SPECIFIER:
self.assertTrue(isinstance(path_rules, tuple),
"Checking: " + str(path_rules))
validate_filter_rules(path_rules, self._all_categories())
# Try using the path specifier (as an "end-to-end" check).
config = FilterConfiguration(path_specific=PATH_RULES_SPECIFIER)
self.assertTrue(config.should_check("xxx_any_category",
"xxx_non_matching_path"))
self.assertTrue(config.should_check("xxx_any_category",
"WebKitTools/WebKitAPITest/"))
self.assertFalse(config.should_check("build/include",
"WebKitTools/WebKitAPITest/"))
self.assertFalse(config.should_check("readability/naming",
"WebKit/qt/tests/qwebelement/tst_qwebelement.cpp"))
def test_max_reports_per_category(self):
"""Check that MAX_REPORTS_PER_CATEGORY is valid."""
all_categories = self._all_categories()
for category in style.MAX_REPORTS_PER_CATEGORY.iterkeys():
self.assertTrue(category in all_categories,
'Key "%s" is not a category' % category)
class ArgumentPrinterTest(unittest.TestCase):
"""Tests the ArgumentPrinter class."""
_printer = style.ArgumentPrinter()
def _create_options(self,
output_format='emacs',
verbosity=3,
user_rules=[],
git_commit=None,
extra_flag_values={}):
filter_configuration = FilterConfiguration(user_rules=user_rules)
return style.ProcessorOptions(extra_flag_values=extra_flag_values,
filter_configuration=filter_configuration,
git_commit=git_commit,
output_format=output_format,
verbosity=verbosity)
def test_to_flag_string(self):
options = self._create_options('vs7', 5, ['+foo', '-bar'], 'git',
{'a': 0, 'z': 1})
self.assertEquals('--a=0 --filter=+foo,-bar --git-commit=git '
'--output=vs7 --verbose=5 --z=1',
self._printer.to_flag_string(options))
# This is to check that --filter and --git-commit do not
# show up when not user-specified.
options = self._create_options()
self.assertEquals('--output=emacs --verbose=3',
self._printer.to_flag_string(options))
class ArgumentParserTest(unittest.TestCase):
"""Test the ArgumentParser class."""
def _parse(self):
"""Return a default parse() function for testing."""
return self._create_parser().parse
def _create_defaults(self, default_output_format='vs7',
default_verbosity=3,
default_filter_rules=['-', '+whitespace']):
"""Return a default ArgumentDefaults instance for testing."""
return style.ArgumentDefaults(default_output_format,
default_verbosity,
default_filter_rules)
def _create_parser(self, defaults=None):
"""Return an ArgumentParser instance for testing."""
def create_usage(_defaults):
"""Return a usage string for testing."""
return "usage"
def doc_print(message):
# We do not want the usage string or style categories
# to print during unit tests, so print nothing.
return
if defaults is None:
defaults = self._create_defaults()
return style.ArgumentParser(defaults, create_usage, doc_print)
def test_parse_documentation(self):
parse = self._parse()
# FIXME: Test both the printing of the usage string and the
# filter categories help.
# Request the usage string.
self.assertRaises(SystemExit, parse, ['--help'])
# Request default filter rules and available style categories.
self.assertRaises(SystemExit, parse, ['--filter='])
def test_parse_bad_values(self):
parse = self._parse()
# Pass an unsupported argument.
self.assertRaises(SystemExit, parse, ['--bad'])
self.assertRaises(ValueError, parse, ['--verbose=bad'])
self.assertRaises(ValueError, parse, ['--verbose=0'])
self.assertRaises(ValueError, parse, ['--verbose=6'])
parse(['--verbose=1']) # works
parse(['--verbose=5']) # works
self.assertRaises(ValueError, parse, ['--output=bad'])
parse(['--output=vs7']) # works
# Pass a filter rule not beginning with + or -.
self.assertRaises(ValueError, parse, ['--filter=build'])
parse(['--filter=+build']) # works
# Pass files and git-commit at the same time.
self.assertRaises(SystemExit, parse, ['--git-commit=', 'file.txt'])
# Pass an extra flag already supported.
self.assertRaises(ValueError, parse, [], ['filter='])
parse([], ['extra=']) # works
# Pass an extra flag with typo.
self.assertRaises(SystemExit, parse, ['--extratypo='], ['extra='])
parse(['--extra='], ['extra=']) # works
self.assertRaises(ValueError, parse, [], ['extra=', 'extra='])
def test_parse_default_arguments(self):
parse = self._parse()
(files, options) = parse([])
self.assertEquals(files, [])
self.assertEquals(options.output_format, 'vs7')
self.assertEquals(options.verbosity, 3)
self.assertEquals(options.filter_configuration,
FilterConfiguration(base_rules=["-", "+whitespace"],
path_specific=PATH_RULES_SPECIFIER))
self.assertEquals(options.git_commit, None)
def test_parse_explicit_arguments(self):
parse = self._parse()
# Pass non-default explicit values.
(files, options) = parse(['--output=emacs'])
self.assertEquals(options.output_format, 'emacs')
(files, options) = parse(['--verbose=4'])
self.assertEquals(options.verbosity, 4)
(files, options) = parse(['--git-commit=commit'])
self.assertEquals(options.git_commit, 'commit')
# Pass user_rules.
(files, options) = parse(['--filter=+build,-whitespace'])
config = options.filter_configuration
self.assertEquals(options.filter_configuration,
FilterConfiguration(base_rules=["-", "+whitespace"],
path_specific=PATH_RULES_SPECIFIER,
user_rules=["+build", "-whitespace"]))
# Pass spurious white space in user rules.
(files, options) = parse(['--filter=+build, -whitespace'])
self.assertEquals(options.filter_configuration,
FilterConfiguration(base_rules=["-", "+whitespace"],
path_specific=PATH_RULES_SPECIFIER,
user_rules=["+build", "-whitespace"]))
# Pass extra flag values.
(files, options) = parse(['--extra'], ['extra'])
self.assertEquals(options.extra_flag_values, {'--extra': ''})
(files, options) = parse(['--extra='], ['extra='])
self.assertEquals(options.extra_flag_values, {'--extra': ''})
(files, options) = parse(['--extra=x'], ['extra='])
self.assertEquals(options.extra_flag_values, {'--extra': 'x'})
def test_parse_files(self):
parse = self._parse()
(files, options) = parse(['foo.cpp'])
self.assertEquals(files, ['foo.cpp'])
# Pass multiple files.
(files, options) = parse(['--output=emacs', 'foo.cpp', 'bar.cpp'])
self.assertEquals(files, ['foo.cpp', 'bar.cpp'])
class ProcessorDispatcherSkipTest(unittest.TestCase):
"""Tests the "should skip" methods of the ProcessorDispatcher class."""
def test_should_skip_with_warning(self):
"""Test should_skip_with_warning()."""
dispatcher = ProcessorDispatcher()
# Check a non-skipped file.
self.assertFalse(dispatcher.should_skip_with_warning("foo.txt"))
# Check skipped files.
paths_to_skip = [
"gtk2drawing.c",
"gtk2drawing.h",
"JavaScriptCore/qt/api/qscriptengine_p.h",
"WebCore/platform/gtk/gtk2drawing.c",
"WebCore/platform/gtk/gtk2drawing.h",
"WebKit/gtk/tests/testatk.c",
"WebKit/qt/Api/qwebpage.h",
"WebKit/qt/tests/qwebsecurityorigin/tst_qwebsecurityorigin.cpp",
]
for path in paths_to_skip:
self.assertTrue(dispatcher.should_skip_with_warning(path),
"Checking: " + path)
def test_should_skip_without_warning(self):
"""Test should_skip_without_warning()."""
dispatcher = ProcessorDispatcher()
# Check a non-skipped file.
self.assertFalse(dispatcher.should_skip_without_warning("foo.txt"))
# Check skipped files.
paths_to_skip = [
# LayoutTests folder
"LayoutTests/foo.txt",
]
for path in paths_to_skip:
self.assertTrue(dispatcher.should_skip_without_warning(path),
"Checking: " + path)
class ProcessorDispatcherDispatchTest(unittest.TestCase):
"""Tests dispatch_processor() method of ProcessorDispatcher class."""
def mock_handle_style_error(self):
pass
def dispatch_processor(self, file_path):
"""Call dispatch_processor() with the given file path."""
dispatcher = ProcessorDispatcher()
processor = dispatcher.dispatch_processor(file_path,
self.mock_handle_style_error,
verbosity=3)
return processor
def assert_processor_none(self, file_path):
"""Assert that the dispatched processor is None."""
processor = self.dispatch_processor(file_path)
self.assertTrue(processor is None, 'Checking: "%s"' % file_path)
def assert_processor(self, file_path, expected_class):
"""Assert the type of the dispatched processor."""
processor = self.dispatch_processor(file_path)
got_class = processor.__class__
self.assertEquals(got_class, expected_class,
'For path "%(file_path)s" got %(got_class)s when '
"expecting %(expected_class)s."
% {"file_path": file_path,
"got_class": got_class,
"expected_class": expected_class})
def assert_processor_cpp(self, file_path):
"""Assert that the dispatched processor is a CppProcessor."""
self.assert_processor(file_path, CppProcessor)
def assert_processor_text(self, file_path):
"""Assert that the dispatched processor is a TextProcessor."""
self.assert_processor(file_path, TextProcessor)
def test_cpp_paths(self):
"""Test paths that should be checked as C++."""
paths = [
"-",
"foo.c",
"foo.cpp",
"foo.h",
]
for path in paths:
self.assert_processor_cpp(path)
# Check processor attributes on a typical input.
file_base = "foo"
file_extension = "c"
file_path = file_base + "." + file_extension
self.assert_processor_cpp(file_path)
processor = self.dispatch_processor(file_path)
self.assertEquals(processor.file_extension, file_extension)
self.assertEquals(processor.file_path, file_path)
self.assertEquals(processor.handle_style_error, self.mock_handle_style_error)
self.assertEquals(processor.verbosity, 3)
# Check "-" for good measure.
file_base = "-"
file_extension = ""
file_path = file_base
self.assert_processor_cpp(file_path)
processor = self.dispatch_processor(file_path)
self.assertEquals(processor.file_extension, file_extension)
self.assertEquals(processor.file_path, file_path)
def test_text_paths(self):
"""Test paths that should be checked as text."""
paths = [
"ChangeLog",
"foo.css",
"foo.html",
"foo.idl",
"foo.js",
"foo.mm",
"foo.php",
"foo.pm",
"foo.py",
"foo.txt",
"FooChangeLog.bak",
"WebCore/ChangeLog",
"WebCore/inspector/front-end/inspector.js",
"WebKitTools/Scripts/check-webkit=style",
"WebKitTools/Scripts/modules/text_style.py",
]
for path in paths:
self.assert_processor_text(path)
# Check processor attributes on a typical input.
file_base = "foo"
file_extension = "css"
file_path = file_base + "." + file_extension
self.assert_processor_text(file_path)
processor = self.dispatch_processor(file_path)
self.assertEquals(processor.file_path, file_path)
self.assertEquals(processor.handle_style_error, self.mock_handle_style_error)
def test_none_paths(self):
"""Test paths that have no file type.."""
paths = [
"Makefile",
"foo.png",
"foo.exe",
]
for path in paths:
self.assert_processor_none(path)
class StyleCheckerTest(unittest.TestCase):
"""Test the StyleChecker class.
Attributes:
error_messages: A string containing all of the warning messages
written to the mock_stderr_write method of
this class.
"""
def _mock_stderr_write(self, message):
pass
def _style_checker(self, options):
return StyleChecker(options, self._mock_stderr_write)
def test_init(self):
"""Test __init__ constructor."""
options = ProcessorOptions()
style_checker = self._style_checker(options)
self.assertEquals(style_checker.error_count, 0)
self.assertEquals(style_checker.options, options)
class StyleCheckerCheckFileTest(unittest.TestCase):
"""Test the check_file() method of the StyleChecker class.
The check_file() method calls its process_file parameter when
given a file that should not be skipped.
The "got_*" attributes of this class are the parameters passed
to process_file by calls to check_file() made by this test
class. These attributes allow us to check the parameter values
passed internally to the process_file function.
Attributes:
got_file_path: The file_path parameter passed by check_file()
to its process_file parameter.
got_handle_style_error: The handle_style_error parameter passed
by check_file() to its process_file
parameter.
got_processor: The processor parameter passed by check_file() to
its process_file parameter.
warning_messages: A string containing all of the warning messages
written to the mock_stderr_write method of
this class.
"""
def setUp(self):
self.got_file_path = None
self.got_handle_style_error = None
self.got_processor = None
self.warning_messages = ""
def mock_stderr_write(self, warning_message):
self.warning_messages += warning_message
def mock_handle_style_error(self):
pass
def mock_process_file(self, processor, file_path, handle_style_error):
"""A mock _process_file().
See the documentation for this class for more information
on this function.
"""
self.got_file_path = file_path
self.got_handle_style_error = handle_style_error
self.got_processor = processor
def assert_attributes(self,
expected_file_path,
expected_handle_style_error,
expected_processor,
expected_warning_messages):
"""Assert that the attributes of this class equal the given values."""
self.assertEquals(self.got_file_path, expected_file_path)
self.assertEquals(self.got_handle_style_error, expected_handle_style_error)
self.assertEquals(self.got_processor, expected_processor)
self.assertEquals(self.warning_messages, expected_warning_messages)
def call_check_file(self, file_path):
"""Call the check_file() method of a test StyleChecker instance."""
# Confirm that the attributes are reset.
self.assert_attributes(None, None, None, "")
# Create a test StyleChecker instance.
#
# The verbosity attribute is the only ProcessorOptions
# attribute that needs to be checked in this test.
# This is because it is the only option is directly
# passed to the constructor of a style processor.
options = ProcessorOptions(verbosity=3)
style_checker = StyleChecker(options, self.mock_stderr_write)
style_checker.check_file(file_path,
self.mock_handle_style_error,
self.mock_process_file)
def test_check_file_on_skip_without_warning(self):
"""Test check_file() for a skipped-without-warning file."""
file_path = "LayoutTests/foo.txt"
dispatcher = ProcessorDispatcher()
# Confirm that the input file is truly a skipped-without-warning file.
self.assertTrue(dispatcher.should_skip_without_warning(file_path))
# Check the outcome.
self.call_check_file(file_path)
self.assert_attributes(None, None, None, "")
def test_check_file_on_skip_with_warning(self):
"""Test check_file() for a skipped-with-warning file."""
file_path = "gtk2drawing.c"
dispatcher = ProcessorDispatcher()
# Check that the input file is truly a skipped-with-warning file.
self.assertTrue(dispatcher.should_skip_with_warning(file_path))
# Check the outcome.
self.call_check_file(file_path)
self.assert_attributes(None, None, None,
'Ignoring "gtk2drawing.c": this file is exempt from the style guide.\n')
def test_check_file_on_non_skipped(self):
# We use a C++ file since by using a CppProcessor, we can check
# that all of the possible information is getting passed to
# process_file (in particular, the verbosity).
file_base = "foo"
file_extension = "cpp"
file_path = file_base + "." + file_extension
dispatcher = ProcessorDispatcher()
# Check that the input file is truly a C++ file.
self.assertEquals(dispatcher._file_type(file_path), style.FileType.CPP)
# Check the outcome.
self.call_check_file(file_path)
expected_processor = CppProcessor(file_path, file_extension, self.mock_handle_style_error, 3)
self.assert_attributes(file_path,
self.mock_handle_style_error,
expected_processor,
"")
if __name__ == '__main__':
import sys
unittest.main()