123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- # -*- coding: utf-8 -*-
- from __future__ import absolute_import, division, print_function
- import datetime
- import os
- import sys
- from tornado.options import OptionParser, Error
- from tornado.util import basestring_type, PY3
- from tornado.test.util import unittest, subTest
- if PY3:
- from io import StringIO
- else:
- from cStringIO import StringIO
- try:
- # py33+
- from unittest import mock # type: ignore
- except ImportError:
- try:
- import mock # type: ignore
- except ImportError:
- mock = None
- class Email(object):
- def __init__(self, value):
- if isinstance(value, str) and '@' in value:
- self._value = value
- else:
- raise ValueError()
- @property
- def value(self):
- return self._value
- class OptionsTest(unittest.TestCase):
- def test_parse_command_line(self):
- options = OptionParser()
- options.define("port", default=80)
- options.parse_command_line(["main.py", "--port=443"])
- self.assertEqual(options.port, 443)
- def test_parse_config_file(self):
- options = OptionParser()
- options.define("port", default=80)
- options.define("username", default='foo')
- options.define("my_path")
- config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
- "options_test.cfg")
- options.parse_config_file(config_path)
- self.assertEqual(options.port, 443)
- self.assertEqual(options.username, "李康")
- self.assertEqual(options.my_path, config_path)
- def test_parse_callbacks(self):
- options = OptionParser()
- self.called = False
- def callback():
- self.called = True
- options.add_parse_callback(callback)
- # non-final parse doesn't run callbacks
- options.parse_command_line(["main.py"], final=False)
- self.assertFalse(self.called)
- # final parse does
- options.parse_command_line(["main.py"])
- self.assertTrue(self.called)
- # callbacks can be run more than once on the same options
- # object if there are multiple final parses
- self.called = False
- options.parse_command_line(["main.py"])
- self.assertTrue(self.called)
- def test_help(self):
- options = OptionParser()
- try:
- orig_stderr = sys.stderr
- sys.stderr = StringIO()
- with self.assertRaises(SystemExit):
- options.parse_command_line(["main.py", "--help"])
- usage = sys.stderr.getvalue()
- finally:
- sys.stderr = orig_stderr
- self.assertIn("Usage:", usage)
- def test_subcommand(self):
- base_options = OptionParser()
- base_options.define("verbose", default=False)
- sub_options = OptionParser()
- sub_options.define("foo", type=str)
- rest = base_options.parse_command_line(
- ["main.py", "--verbose", "subcommand", "--foo=bar"])
- self.assertEqual(rest, ["subcommand", "--foo=bar"])
- self.assertTrue(base_options.verbose)
- rest2 = sub_options.parse_command_line(rest)
- self.assertEqual(rest2, [])
- self.assertEqual(sub_options.foo, "bar")
- # the two option sets are distinct
- try:
- orig_stderr = sys.stderr
- sys.stderr = StringIO()
- with self.assertRaises(Error):
- sub_options.parse_command_line(["subcommand", "--verbose"])
- finally:
- sys.stderr = orig_stderr
- def test_setattr(self):
- options = OptionParser()
- options.define('foo', default=1, type=int)
- options.foo = 2
- self.assertEqual(options.foo, 2)
- def test_setattr_type_check(self):
- # setattr requires that options be the right type and doesn't
- # parse from string formats.
- options = OptionParser()
- options.define('foo', default=1, type=int)
- with self.assertRaises(Error):
- options.foo = '2'
- def test_setattr_with_callback(self):
- values = []
- options = OptionParser()
- options.define('foo', default=1, type=int, callback=values.append)
- options.foo = 2
- self.assertEqual(values, [2])
- def _sample_options(self):
- options = OptionParser()
- options.define('a', default=1)
- options.define('b', default=2)
- return options
- def test_iter(self):
- options = self._sample_options()
- # OptionParsers always define 'help'.
- self.assertEqual(set(['a', 'b', 'help']), set(iter(options)))
- def test_getitem(self):
- options = self._sample_options()
- self.assertEqual(1, options['a'])
- def test_setitem(self):
- options = OptionParser()
- options.define('foo', default=1, type=int)
- options['foo'] = 2
- self.assertEqual(options['foo'], 2)
- def test_items(self):
- options = self._sample_options()
- # OptionParsers always define 'help'.
- expected = [('a', 1), ('b', 2), ('help', options.help)]
- actual = sorted(options.items())
- self.assertEqual(expected, actual)
- def test_as_dict(self):
- options = self._sample_options()
- expected = {'a': 1, 'b': 2, 'help': options.help}
- self.assertEqual(expected, options.as_dict())
- def test_group_dict(self):
- options = OptionParser()
- options.define('a', default=1)
- options.define('b', group='b_group', default=2)
- frame = sys._getframe(0)
- this_file = frame.f_code.co_filename
- self.assertEqual(set(['b_group', '', this_file]), options.groups())
- b_group_dict = options.group_dict('b_group')
- self.assertEqual({'b': 2}, b_group_dict)
- self.assertEqual({}, options.group_dict('nonexistent'))
- @unittest.skipIf(mock is None, 'mock package not present')
- def test_mock_patch(self):
- # ensure that our setattr hooks don't interfere with mock.patch
- options = OptionParser()
- options.define('foo', default=1)
- options.parse_command_line(['main.py', '--foo=2'])
- self.assertEqual(options.foo, 2)
- with mock.patch.object(options.mockable(), 'foo', 3):
- self.assertEqual(options.foo, 3)
- self.assertEqual(options.foo, 2)
- # Try nested patches mixed with explicit sets
- with mock.patch.object(options.mockable(), 'foo', 4):
- self.assertEqual(options.foo, 4)
- options.foo = 5
- self.assertEqual(options.foo, 5)
- with mock.patch.object(options.mockable(), 'foo', 6):
- self.assertEqual(options.foo, 6)
- self.assertEqual(options.foo, 5)
- self.assertEqual(options.foo, 2)
- def _define_options(self):
- options = OptionParser()
- options.define('str', type=str)
- options.define('basestring', type=basestring_type)
- options.define('int', type=int)
- options.define('float', type=float)
- options.define('datetime', type=datetime.datetime)
- options.define('timedelta', type=datetime.timedelta)
- options.define('email', type=Email)
- options.define('list-of-int', type=int, multiple=True)
- return options
- def _check_options_values(self, options):
- self.assertEqual(options.str, 'asdf')
- self.assertEqual(options.basestring, 'qwer')
- self.assertEqual(options.int, 42)
- self.assertEqual(options.float, 1.5)
- self.assertEqual(options.datetime,
- datetime.datetime(2013, 4, 28, 5, 16))
- self.assertEqual(options.timedelta, datetime.timedelta(seconds=45))
- self.assertEqual(options.email.value, 'tornado@web.com')
- self.assertTrue(isinstance(options.email, Email))
- self.assertEqual(options.list_of_int, [1, 2, 3])
- def test_types(self):
- options = self._define_options()
- options.parse_command_line(['main.py',
- '--str=asdf',
- '--basestring=qwer',
- '--int=42',
- '--float=1.5',
- '--datetime=2013-04-28 05:16',
- '--timedelta=45s',
- '--email=tornado@web.com',
- '--list-of-int=1,2,3'])
- self._check_options_values(options)
- def test_types_with_conf_file(self):
- for config_file_name in ("options_test_types.cfg",
- "options_test_types_str.cfg"):
- options = self._define_options()
- options.parse_config_file(os.path.join(os.path.dirname(__file__),
- config_file_name))
- self._check_options_values(options)
- def test_multiple_string(self):
- options = OptionParser()
- options.define('foo', type=str, multiple=True)
- options.parse_command_line(['main.py', '--foo=a,b,c'])
- self.assertEqual(options.foo, ['a', 'b', 'c'])
- def test_multiple_int(self):
- options = OptionParser()
- options.define('foo', type=int, multiple=True)
- options.parse_command_line(['main.py', '--foo=1,3,5:7'])
- self.assertEqual(options.foo, [1, 3, 5, 6, 7])
- def test_error_redefine(self):
- options = OptionParser()
- options.define('foo')
- with self.assertRaises(Error) as cm:
- options.define('foo')
- self.assertRegexpMatches(str(cm.exception),
- 'Option.*foo.*already defined')
- def test_error_redefine_underscore(self):
- # Ensure that the dash/underscore normalization doesn't
- # interfere with the redefinition error.
- tests = [
- ('foo-bar', 'foo-bar'),
- ('foo_bar', 'foo_bar'),
- ('foo-bar', 'foo_bar'),
- ('foo_bar', 'foo-bar'),
- ]
- for a, b in tests:
- with subTest(self, a=a, b=b):
- options = OptionParser()
- options.define(a)
- with self.assertRaises(Error) as cm:
- options.define(b)
- self.assertRegexpMatches(str(cm.exception),
- 'Option.*foo.bar.*already defined')
- def test_dash_underscore_cli(self):
- # Dashes and underscores should be interchangeable.
- for defined_name in ['foo-bar', 'foo_bar']:
- for flag in ['--foo-bar=a', '--foo_bar=a']:
- options = OptionParser()
- options.define(defined_name)
- options.parse_command_line(['main.py', flag])
- # Attr-style access always uses underscores.
- self.assertEqual(options.foo_bar, 'a')
- # Dict-style access allows both.
- self.assertEqual(options['foo-bar'], 'a')
- self.assertEqual(options['foo_bar'], 'a')
- def test_dash_underscore_file(self):
- # No matter how an option was defined, it can be set with underscores
- # in a config file.
- for defined_name in ['foo-bar', 'foo_bar']:
- options = OptionParser()
- options.define(defined_name)
- options.parse_config_file(os.path.join(os.path.dirname(__file__),
- "options_test.cfg"))
- self.assertEqual(options.foo_bar, 'a')
- def test_dash_underscore_introspection(self):
- # Original names are preserved in introspection APIs.
- options = OptionParser()
- options.define('with-dash', group='g')
- options.define('with_underscore', group='g')
- all_options = ['help', 'with-dash', 'with_underscore']
- self.assertEqual(sorted(options), all_options)
- self.assertEqual(sorted(k for (k, v) in options.items()), all_options)
- self.assertEqual(sorted(options.as_dict().keys()), all_options)
- self.assertEqual(sorted(options.group_dict('g')),
- ['with-dash', 'with_underscore'])
- # --help shows CLI-style names with dashes.
- buf = StringIO()
- options.print_help(buf)
- self.assertIn('--with-dash', buf.getvalue())
- self.assertIn('--with-underscore', buf.getvalue())
|