• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import unittest
2import __builtin__
3import exceptions
4import warnings
5from test.test_support import run_unittest, check_warnings
6import os
7import sys
8from platform import system as platform_system
9
10DEPRECATION_WARNINGS = ["BaseException.message has been deprecated"]
11
12if 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
21def 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
28class 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
145class 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
233def test_main():
234    run_unittest(ExceptionClassTests, UsageTests)
235
236
237
238if __name__ == '__main__':
239    test_main()
240