1# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions
5# are met:
6# 1.  Redistributions of source code must retain the above copyright
7#     notice, this list of conditions and the following disclaimer.
8# 2.  Redistributions in binary form must reproduce the above copyright
9#     notice, this list of conditions and the following disclaimer in the
10#     documentation and/or other materials provided with the distribution.
11#
12# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
13# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
16# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23"""Unit tests for parser.py."""
24
25import unittest
26
27from webkitpy.common.system.logtesting import LoggingTestCase
28from webkitpy.style.optparser import ArgumentParser
29from webkitpy.style.optparser import ArgumentPrinter
30from webkitpy.style.optparser import CommandOptionValues as ProcessorOptions
31from webkitpy.style.optparser import DefaultCommandOptionValues
32
33
34class ArgumentPrinterTest(unittest.TestCase):
35
36    """Tests the ArgumentPrinter class."""
37
38    _printer = ArgumentPrinter()
39
40    def _create_options(self,
41                        output_format='emacs',
42                        min_confidence=3,
43                        filter_rules=[],
44                        git_commit=None):
45        return ProcessorOptions(filter_rules=filter_rules,
46                                git_commit=git_commit,
47                                min_confidence=min_confidence,
48                                output_format=output_format)
49
50    def test_to_flag_string(self):
51        options = self._create_options('vs7', 5, ['+foo', '-bar'], 'git')
52        self.assertEquals('--filter=+foo,-bar --git-commit=git '
53                          '--min-confidence=5 --output=vs7',
54                          self._printer.to_flag_string(options))
55
56        # This is to check that --filter and --git-commit do not
57        # show up when not user-specified.
58        options = self._create_options()
59        self.assertEquals('--min-confidence=3 --output=emacs',
60                          self._printer.to_flag_string(options))
61
62
63class ArgumentParserTest(LoggingTestCase):
64
65    """Test the ArgumentParser class."""
66
67    class _MockStdErr(object):
68
69        def write(self, message):
70            # We do not want the usage string or style categories
71            # to print during unit tests, so print nothing.
72            return
73
74    def _parse(self, args):
75        """Call a test parser.parse()."""
76        parser = self._create_parser()
77        return parser.parse(args)
78
79    def _create_defaults(self):
80        """Return a DefaultCommandOptionValues instance for testing."""
81        base_filter_rules = ["-", "+whitespace"]
82        return DefaultCommandOptionValues(min_confidence=3,
83                                          output_format="vs7")
84
85    def _create_parser(self):
86        """Return an ArgumentParser instance for testing."""
87        default_options = self._create_defaults()
88
89        all_categories = ["build" ,"whitespace"]
90
91        mock_stderr = self._MockStdErr()
92
93        return ArgumentParser(all_categories=all_categories,
94                              base_filter_rules=[],
95                              default_options=default_options,
96                              mock_stderr=mock_stderr,
97                              usage="test usage")
98
99    def test_parse_documentation(self):
100        parse = self._parse
101
102        # FIXME: Test both the printing of the usage string and the
103        #        filter categories help.
104
105        # Request the usage string.
106        self.assertRaises(SystemExit, parse, ['--help'])
107        # Request default filter rules and available style categories.
108        self.assertRaises(SystemExit, parse, ['--filter='])
109
110    def test_parse_bad_values(self):
111        parse = self._parse
112
113        # Pass an unsupported argument.
114        self.assertRaises(SystemExit, parse, ['--bad'])
115        self.assertLog(['ERROR: no such option: --bad\n'])
116
117        self.assertRaises(SystemExit, parse, ['--min-confidence=bad'])
118        self.assertLog(['ERROR: option --min-confidence: '
119                        "invalid integer value: 'bad'\n"])
120        self.assertRaises(SystemExit, parse, ['--min-confidence=0'])
121        self.assertLog(['ERROR: option --min-confidence: invalid integer: 0: '
122                        'value must be between 1 and 5\n'])
123        self.assertRaises(SystemExit, parse, ['--min-confidence=6'])
124        self.assertLog(['ERROR: option --min-confidence: invalid integer: 6: '
125                        'value must be between 1 and 5\n'])
126        parse(['--min-confidence=1']) # works
127        parse(['--min-confidence=5']) # works
128
129        self.assertRaises(SystemExit, parse, ['--output=bad'])
130        self.assertLog(['ERROR: option --output-format: invalid choice: '
131                        "'bad' (choose from 'emacs', 'vs7')\n"])
132        parse(['--output=vs7']) # works
133
134        # Pass a filter rule not beginning with + or -.
135        self.assertRaises(SystemExit, parse, ['--filter=build'])
136        self.assertLog(['ERROR: Invalid filter rule "build": '
137                        'every rule must start with + or -.\n'])
138        parse(['--filter=+build']) # works
139
140    def test_parse_default_arguments(self):
141        parse = self._parse
142
143        (files, options) = parse([])
144
145        self.assertEquals(files, [])
146
147        self.assertEquals(options.filter_rules, [])
148        self.assertEquals(options.git_commit, None)
149        self.assertEquals(options.diff_files, False)
150        self.assertEquals(options.is_verbose, False)
151        self.assertEquals(options.min_confidence, 3)
152        self.assertEquals(options.output_format, 'vs7')
153
154    def test_parse_explicit_arguments(self):
155        parse = self._parse
156
157        # Pass non-default explicit values.
158        (files, options) = parse(['--min-confidence=4'])
159        self.assertEquals(options.min_confidence, 4)
160        (files, options) = parse(['--output=emacs'])
161        self.assertEquals(options.output_format, 'emacs')
162        (files, options) = parse(['-g', 'commit'])
163        self.assertEquals(options.git_commit, 'commit')
164        (files, options) = parse(['--git-commit=commit'])
165        self.assertEquals(options.git_commit, 'commit')
166        (files, options) = parse(['--git-diff=commit'])
167        self.assertEquals(options.git_commit, 'commit')
168        (files, options) = parse(['--verbose'])
169        self.assertEquals(options.is_verbose, True)
170        (files, options) = parse(['--diff-files', 'file.txt'])
171        self.assertEquals(options.diff_files, True)
172
173        # Pass user_rules.
174        (files, options) = parse(['--filter=+build,-whitespace'])
175        self.assertEquals(options.filter_rules,
176                          ["+build", "-whitespace"])
177
178        # Pass spurious white space in user rules.
179        (files, options) = parse(['--filter=+build, -whitespace'])
180        self.assertEquals(options.filter_rules,
181                          ["+build", "-whitespace"])
182
183    def test_parse_files(self):
184        parse = self._parse
185
186        (files, options) = parse(['foo.cpp'])
187        self.assertEquals(files, ['foo.cpp'])
188
189        # Pass multiple files.
190        (files, options) = parse(['--output=emacs', 'foo.cpp', 'bar.cpp'])
191        self.assertEquals(files, ['foo.cpp', 'bar.cpp'])
192
193
194class CommandOptionValuesTest(unittest.TestCase):
195
196    """Tests CommandOptionValues class."""
197
198    def test_init(self):
199        """Test __init__ constructor."""
200        # Check default parameters.
201        options = ProcessorOptions()
202        self.assertEquals(options.filter_rules, [])
203        self.assertEquals(options.git_commit, None)
204        self.assertEquals(options.is_verbose, False)
205        self.assertEquals(options.min_confidence, 1)
206        self.assertEquals(options.output_format, "emacs")
207
208        # Check argument validation.
209        self.assertRaises(ValueError, ProcessorOptions, output_format="bad")
210        ProcessorOptions(output_format="emacs") # No ValueError: works
211        ProcessorOptions(output_format="vs7") # works
212        self.assertRaises(ValueError, ProcessorOptions, min_confidence=0)
213        self.assertRaises(ValueError, ProcessorOptions, min_confidence=6)
214        ProcessorOptions(min_confidence=1) # works
215        ProcessorOptions(min_confidence=5) # works
216
217        # Check attributes.
218        options = ProcessorOptions(filter_rules=["+"],
219                                   git_commit="commit",
220                                   is_verbose=True,
221                                   min_confidence=3,
222                                   output_format="vs7")
223        self.assertEquals(options.filter_rules, ["+"])
224        self.assertEquals(options.git_commit, "commit")
225        self.assertEquals(options.is_verbose, True)
226        self.assertEquals(options.min_confidence, 3)
227        self.assertEquals(options.output_format, "vs7")
228
229    def test_eq(self):
230        """Test __eq__ equality function."""
231        self.assertTrue(ProcessorOptions().__eq__(ProcessorOptions()))
232
233        # Also verify that a difference in any argument causes equality to fail.
234
235        # Explicitly create a ProcessorOptions instance with all default
236        # values.  We do this to be sure we are assuming the right default
237        # values in our self.assertFalse() calls below.
238        options = ProcessorOptions(filter_rules=[],
239                                   git_commit=None,
240                                   is_verbose=False,
241                                   min_confidence=1,
242                                   output_format="emacs")
243        # Verify that we created options correctly.
244        self.assertTrue(options.__eq__(ProcessorOptions()))
245
246        self.assertFalse(options.__eq__(ProcessorOptions(filter_rules=["+"])))
247        self.assertFalse(options.__eq__(ProcessorOptions(git_commit="commit")))
248        self.assertFalse(options.__eq__(ProcessorOptions(is_verbose=True)))
249        self.assertFalse(options.__eq__(ProcessorOptions(min_confidence=2)))
250        self.assertFalse(options.__eq__(ProcessorOptions(output_format="vs7")))
251
252    def test_ne(self):
253        """Test __ne__ inequality function."""
254        # By default, __ne__ always returns true on different objects.
255        # Thus, just check the distinguishing case to verify that the
256        # code defines __ne__.
257        self.assertFalse(ProcessorOptions().__ne__(ProcessorOptions()))
258
259