from io import StringIO import re import sys import datetime import unittest import tornado from tornado.escape import utf8 from tornado.util import ( raise_exc_info, Configurable, exec_in, ArgReplacer, timedelta_to_seconds, import_object, re_unescape, is_finalizing, ) import typing from typing import cast if typing.TYPE_CHECKING: from typing import Dict, Any # noqa: F401 class RaiseExcInfoTest(unittest.TestCase): def test_two_arg_exception(self): # This test would fail on python 3 if raise_exc_info were simply # a three-argument raise statement, because TwoArgException # doesn't have a "copy constructor" class TwoArgException(Exception): def __init__(self, a, b): super().__init__() self.a, self.b = a, b try: raise TwoArgException(1, 2) except TwoArgException: exc_info = sys.exc_info() try: raise_exc_info(exc_info) self.fail("didn't get expected exception") except TwoArgException as e: self.assertIs(e, exc_info[1]) class TestConfigurable(Configurable): @classmethod def configurable_base(cls): return TestConfigurable @classmethod def configurable_default(cls): return TestConfig1 class TestConfig1(TestConfigurable): def initialize(self, pos_arg=None, a=None): self.a = a self.pos_arg = pos_arg class TestConfig2(TestConfigurable): def initialize(self, pos_arg=None, b=None): self.b = b self.pos_arg = pos_arg class TestConfig3(TestConfigurable): # TestConfig3 is a configuration option that is itself configurable. @classmethod def configurable_base(cls): return TestConfig3 @classmethod def configurable_default(cls): return TestConfig3A class TestConfig3A(TestConfig3): def initialize(self, a=None): self.a = a class TestConfig3B(TestConfig3): def initialize(self, b=None): self.b = b class ConfigurableTest(unittest.TestCase): def setUp(self): self.saved = TestConfigurable._save_configuration() self.saved3 = TestConfig3._save_configuration() def tearDown(self): TestConfigurable._restore_configuration(self.saved) TestConfig3._restore_configuration(self.saved3) def checkSubclasses(self): # no matter how the class is configured, it should always be # possible to instantiate the subclasses directly self.assertIsInstance(TestConfig1(), TestConfig1) self.assertIsInstance(TestConfig2(), TestConfig2) obj = TestConfig1(a=1) self.assertEqual(obj.a, 1) obj2 = TestConfig2(b=2) self.assertEqual(obj2.b, 2) def test_default(self): # In these tests we combine a typing.cast to satisfy mypy with # a runtime type-assertion. Without the cast, mypy would only # let us access attributes of the base class. obj = cast(TestConfig1, TestConfigurable()) self.assertIsInstance(obj, TestConfig1) self.assertIs(obj.a, None) obj = cast(TestConfig1, TestConfigurable(a=1)) self.assertIsInstance(obj, TestConfig1) self.assertEqual(obj.a, 1) self.checkSubclasses() def test_config_class(self): TestConfigurable.configure(TestConfig2) obj = cast(TestConfig2, TestConfigurable()) self.assertIsInstance(obj, TestConfig2) self.assertIs(obj.b, None) obj = cast(TestConfig2, TestConfigurable(b=2)) self.assertIsInstance(obj, TestConfig2) self.assertEqual(obj.b, 2) self.checkSubclasses() def test_config_str(self): TestConfigurable.configure("tornado.test.util_test.TestConfig2") obj = cast(TestConfig2, TestConfigurable()) self.assertIsInstance(obj, TestConfig2) self.assertIs(obj.b, None) obj = cast(TestConfig2, TestConfigurable(b=2)) self.assertIsInstance(obj, TestConfig2) self.assertEqual(obj.b, 2) self.checkSubclasses() def test_config_args(self): TestConfigurable.configure(None, a=3) obj = cast(TestConfig1, TestConfigurable()) self.assertIsInstance(obj, TestConfig1) self.assertEqual(obj.a, 3) obj = cast(TestConfig1, TestConfigurable(42, a=4)) self.assertIsInstance(obj, TestConfig1) self.assertEqual(obj.a, 4) self.assertEqual(obj.pos_arg, 42) self.checkSubclasses() # args bound in configure don't apply when using the subclass directly obj = TestConfig1() self.assertIs(obj.a, None) def test_config_class_args(self): TestConfigurable.configure(TestConfig2, b=5) obj = cast(TestConfig2, TestConfigurable()) self.assertIsInstance(obj, TestConfig2) self.assertEqual(obj.b, 5) obj = cast(TestConfig2, TestConfigurable(42, b=6)) self.assertIsInstance(obj, TestConfig2) self.assertEqual(obj.b, 6) self.assertEqual(obj.pos_arg, 42) self.checkSubclasses() # args bound in configure don't apply when using the subclass directly obj = TestConfig2() self.assertIs(obj.b, None) def test_config_multi_level(self): TestConfigurable.configure(TestConfig3, a=1) obj = cast(TestConfig3A, TestConfigurable()) self.assertIsInstance(obj, TestConfig3A) self.assertEqual(obj.a, 1) TestConfigurable.configure(TestConfig3) TestConfig3.configure(TestConfig3B, b=2) obj2 = cast(TestConfig3B, TestConfigurable()) self.assertIsInstance(obj2, TestConfig3B) self.assertEqual(obj2.b, 2) def test_config_inner_level(self): # The inner level can be used even when the outer level # doesn't point to it. obj = TestConfig3() self.assertIsInstance(obj, TestConfig3A) TestConfig3.configure(TestConfig3B) obj = TestConfig3() self.assertIsInstance(obj, TestConfig3B) # Configuring the base doesn't configure the inner. obj2 = TestConfigurable() self.assertIsInstance(obj2, TestConfig1) TestConfigurable.configure(TestConfig2) obj3 = TestConfigurable() self.assertIsInstance(obj3, TestConfig2) obj = TestConfig3() self.assertIsInstance(obj, TestConfig3B) class UnicodeLiteralTest(unittest.TestCase): def test_unicode_escapes(self): self.assertEqual(utf8("\u00e9"), b"\xc3\xa9") class ExecInTest(unittest.TestCase): # TODO(bdarnell): make a version of this test for one of the new # future imports available in python 3. @unittest.skip("no testable future imports") def test_no_inherit_future(self): # This file has from __future__ import print_function... f = StringIO() print("hello", file=f) # ...but the template doesn't exec_in('print >> f, "world"', dict(f=f)) self.assertEqual(f.getvalue(), "hello\nworld\n") class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, "callback") def test_omitted(self): args = (1, 2) kwargs = dict() # type: Dict[str, Any] self.assertIs(self.replacer.get_old_value(args, kwargs), None) self.assertEqual( self.replacer.replace("new", args, kwargs), (None, (1, 2), dict(callback="new")), ) def test_position(self): args = (1, 2, "old", 3) kwargs = dict() # type: Dict[str, Any] self.assertEqual(self.replacer.get_old_value(args, kwargs), "old") self.assertEqual( self.replacer.replace("new", args, kwargs), ("old", [1, 2, "new", 3], dict()), ) def test_keyword(self): args = (1,) kwargs = dict(y=2, callback="old", z=3) self.assertEqual(self.replacer.get_old_value(args, kwargs), "old") self.assertEqual( self.replacer.replace("new", args, kwargs), ("old", (1,), dict(y=2, callback="new", z=3)), ) class TimedeltaToSecondsTest(unittest.TestCase): def test_timedelta_to_seconds(self): time_delta = datetime.timedelta(hours=1) self.assertEqual(timedelta_to_seconds(time_delta), 3600.0) class ImportObjectTest(unittest.TestCase): def test_import_member(self): self.assertIs(import_object("tornado.escape.utf8"), utf8) def test_import_member_unicode(self): self.assertIs(import_object("tornado.escape.utf8"), utf8) def test_import_module(self): self.assertIs(import_object("tornado.escape"), tornado.escape) def test_import_module_unicode(self): # The internal implementation of __import__ differs depending on # whether the thing being imported is a module or not. # This variant requires a byte string in python 2. self.assertIs(import_object("tornado.escape"), tornado.escape) class ReUnescapeTest(unittest.TestCase): def test_re_unescape(self): test_strings = ("/favicon.ico", "index.html", "Hello, World!", "!$@#%;") for string in test_strings: self.assertEqual(string, re_unescape(re.escape(string))) def test_re_unescape_raises_error_on_invalid_input(self): with self.assertRaises(ValueError): re_unescape("\\d") with self.assertRaises(ValueError): re_unescape("\\b") with self.assertRaises(ValueError): re_unescape("\\Z") class IsFinalizingTest(unittest.TestCase): def test_basic(self): self.assertFalse(is_finalizing())