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