Home | History | Annotate | Download | only in test
      1 import unittest
      2 import __builtin__
      3 import exceptions
      4 import warnings
      5 from test.test_support import run_unittest, check_warnings
      6 import os
      7 import sys
      8 from platform import system as platform_system
      9 
     10 DEPRECATION_WARNINGS = ["BaseException.message has been deprecated"]
     11 
     12 if sys.py3kwarning:
     13     DEPRECATION_WARNINGS.extend(
     14         ["exceptions must derive from BaseException",
     15          "catching classes that don't inherit from BaseException is not allowed",
     16          "__get(item|slice)__ not supported for exception classes"])
     17 
     18 _deprecations = [(msg, DeprecationWarning) for msg in DEPRECATION_WARNINGS]
     19 
     20 # Silence Py3k and other deprecation warnings
     21 def ignore_deprecation_warnings(func):
     22     """Ignore the known DeprecationWarnings."""
     23     def wrapper(*args, **kw):
     24         with check_warnings(*_deprecations, quiet=True):
     25             return func(*args, **kw)
     26     return wrapper
     27 
     28 class ExceptionClassTests(unittest.TestCase):
     29 
     30     """Tests for anything relating to exception objects themselves (e.g.,
     31     inheritance hierarchy)"""
     32 
     33     def test_builtins_new_style(self):
     34         self.assertTrue(issubclass(Exception, object))
     35 
     36     @ignore_deprecation_warnings
     37     def verify_instance_interface(self, ins):
     38         for attr in ("args", "message", "__str__", "__repr__", "__getitem__"):
     39             self.assertTrue(hasattr(ins, attr),
     40                             "%s missing %s attribute" %
     41                             (ins.__class__.__name__, attr))
     42 
     43     def test_inheritance(self):
     44         # Make sure the inheritance hierarchy matches the documentation
     45         exc_set = set(x for x in dir(exceptions) if not x.startswith('_'))
     46         inheritance_tree = open(os.path.join(os.path.split(__file__)[0],
     47                                                 'exception_hierarchy.txt'))
     48         try:
     49             superclass_name = inheritance_tree.readline().rstrip()
     50             try:
     51                 last_exc = getattr(__builtin__, superclass_name)
     52             except AttributeError:
     53                 self.fail("base class %s not a built-in" % superclass_name)
     54             self.assertIn(superclass_name, exc_set)
     55             exc_set.discard(superclass_name)
     56             superclasses = []  # Loop will insert base exception
     57             last_depth = 0
     58             for exc_line in inheritance_tree:
     59                 exc_line = exc_line.rstrip()
     60                 depth = exc_line.rindex('-')
     61                 exc_name = exc_line[depth+2:]  # Slice past space
     62                 if '(' in exc_name:
     63                     paren_index = exc_name.index('(')
     64                     platform_name = exc_name[paren_index+1:-1]
     65                     exc_name = exc_name[:paren_index-1]  # Slice off space
     66                     if platform_system() != platform_name:
     67                         exc_set.discard(exc_name)
     68                         continue
     69                 if '[' in exc_name:
     70                     left_bracket = exc_name.index('[')
     71                     exc_name = exc_name[:left_bracket-1]  # cover space
     72                 try:
     73                     exc = getattr(__builtin__, exc_name)
     74                 except AttributeError:
     75                     self.fail("%s not a built-in exception" % exc_name)
     76                 if last_depth < depth:
     77                     superclasses.append((last_depth, last_exc))
     78                 elif last_depth > depth:
     79                     while superclasses[-1][0] >= depth:
     80                         superclasses.pop()
     81                 self.assertTrue(issubclass(exc, superclasses[-1][1]),
     82                 "%s is not a subclass of %s" % (exc.__name__,
     83                     superclasses[-1][1].__name__))
     84                 try:  # Some exceptions require arguments; just skip them
     85                     self.verify_instance_interface(exc())
     86                 except TypeError:
     87                     pass
     88                 self.assertIn(exc_name, exc_set)
     89                 exc_set.discard(exc_name)
     90                 last_exc = exc
     91                 last_depth = depth
     92         finally:
     93             inheritance_tree.close()
     94         self.assertEqual(len(exc_set), 0, "%s not accounted for" % exc_set)
     95 
     96     interface_tests = ("length", "args", "message", "str", "unicode", "repr",
     97             "indexing")
     98 
     99     def interface_test_driver(self, results):
    100         for test_name, (given, expected) in zip(self.interface_tests, results):
    101             self.assertEqual(given, expected, "%s: %s != %s" % (test_name,
    102                 given, expected))
    103 
    104     @ignore_deprecation_warnings
    105     def test_interface_single_arg(self):
    106         # Make sure interface works properly when given a single argument
    107         arg = "spam"
    108         exc = Exception(arg)
    109         results = ([len(exc.args), 1], [exc.args[0], arg], [exc.message, arg],
    110                    [str(exc), str(arg)], [unicode(exc), unicode(arg)],
    111                    [repr(exc), exc.__class__.__name__ + repr(exc.args)],
    112                    [exc[0], arg])
    113         self.interface_test_driver(results)
    114 
    115     @ignore_deprecation_warnings
    116     def test_interface_multi_arg(self):
    117         # Make sure interface correct when multiple arguments given
    118         arg_count = 3
    119         args = tuple(range(arg_count))
    120         exc = Exception(*args)
    121         results = ([len(exc.args), arg_count], [exc.args, args],
    122                    [exc.message, ''], [str(exc), str(args)],
    123                    [unicode(exc), unicode(args)],
    124                    [repr(exc), exc.__class__.__name__ + repr(exc.args)],
    125                    [exc[-1], args[-1]])
    126         self.interface_test_driver(results)
    127 
    128     @ignore_deprecation_warnings
    129     def test_interface_no_arg(self):
    130         # Make sure that with no args that interface is correct
    131         exc = Exception()
    132         results = ([len(exc.args), 0], [exc.args, tuple()],
    133                    [exc.message, ''],
    134                    [str(exc), ''], [unicode(exc), u''],
    135                    [repr(exc), exc.__class__.__name__ + '()'], [True, True])
    136         self.interface_test_driver(results)
    137 
    138 
    139     def test_message_deprecation(self):
    140         # As of Python 2.6, BaseException.message is deprecated.
    141         with check_warnings(("", DeprecationWarning)):
    142             BaseException().message
    143 
    144 
    145 class UsageTests(unittest.TestCase):
    146 
    147     """Test usage of exceptions"""
    148 
    149     def raise_fails(self, object_):
    150         """Make sure that raising 'object_' triggers a TypeError."""
    151         try:
    152             raise object_
    153         except TypeError:
    154             return  # What is expected.
    155         self.fail("TypeError expected for raising %s" % type(object_))
    156 
    157     def catch_fails(self, object_):
    158         """Catching 'object_' should raise a TypeError."""
    159         try:
    160             try:
    161                 raise StandardError
    162             except object_:
    163                 pass
    164         except TypeError:
    165             pass
    166         except StandardError:
    167             self.fail("TypeError expected when catching %s" % type(object_))
    168 
    169         try:
    170             try:
    171                 raise StandardError
    172             except (object_,):
    173                 pass
    174         except TypeError:
    175             return
    176         except StandardError:
    177             self.fail("TypeError expected when catching %s as specified in a "
    178                         "tuple" % type(object_))
    179 
    180     @ignore_deprecation_warnings
    181     def test_raise_classic(self):
    182         # Raising a classic class is okay (for now).
    183         class ClassicClass:
    184             pass
    185         try:
    186             raise ClassicClass
    187         except ClassicClass:
    188             pass
    189         except:
    190             self.fail("unable to raise classic class")
    191         try:
    192             raise ClassicClass()
    193         except ClassicClass:
    194             pass
    195         except:
    196             self.fail("unable to raise classic class instance")
    197 
    198     def test_raise_new_style_non_exception(self):
    199         # You cannot raise a new-style class that does not inherit from
    200         # BaseException; the ability was not possible until BaseException's
    201         # introduction so no need to support new-style objects that do not
    202         # inherit from it.
    203         class NewStyleClass(object):
    204             pass
    205         self.raise_fails(NewStyleClass)
    206         self.raise_fails(NewStyleClass())
    207 
    208     def test_raise_string(self):
    209         # Raising a string raises TypeError.
    210         self.raise_fails("spam")
    211 
    212     def test_catch_string(self):
    213         # Catching a string should trigger a DeprecationWarning.
    214         with warnings.catch_warnings():
    215             warnings.resetwarnings()
    216             warnings.filterwarnings("error")
    217             str_exc = "spam"
    218             with self.assertRaises(DeprecationWarning):
    219                 try:
    220                     raise StandardError
    221                 except str_exc:
    222                     pass
    223 
    224             # Make sure that even if the string exception is listed in a tuple
    225             # that a warning is raised.
    226             with self.assertRaises(DeprecationWarning):
    227                 try:
    228                     raise StandardError
    229                 except (AssertionError, str_exc):
    230                     pass
    231 
    232 
    233 def test_main():
    234     run_unittest(ExceptionClassTests, UsageTests)
    235 
    236 
    237 
    238 if __name__ == '__main__':
    239     test_main()
    240