1# Python test set -- part 5, built-in exceptions
2
3import os
4import sys
5import unittest
6import pickle, cPickle
7
8from test.test_support import (TESTFN, unlink, run_unittest, captured_output,
9                               check_warnings, cpython_only)
10from test.test_pep352 import ignore_deprecation_warnings
11
12# XXX This is not really enough, each *operation* should be tested!
13
14class ExceptionTests(unittest.TestCase):
15
16    def testReload(self):
17        # Reloading the built-in exceptions module failed prior to Py2.2, while it
18        # should act the same as reloading built-in sys.
19        try:
20            from imp import reload
21            import exceptions
22            reload(exceptions)
23        except ImportError, e:
24            self.fail("reloading exceptions: %s" % e)
25
26    def raise_catch(self, exc, excname):
27        try:
28            raise exc, "spam"
29        except exc, err:
30            buf1 = str(err)
31        try:
32            raise exc("spam")
33        except exc, err:
34            buf2 = str(err)
35        self.assertEqual(buf1, buf2)
36        self.assertEqual(exc.__name__, excname)
37
38    def testRaising(self):
39        self.raise_catch(AttributeError, "AttributeError")
40        self.assertRaises(AttributeError, getattr, sys, "undefined_attribute")
41
42        self.raise_catch(EOFError, "EOFError")
43        fp = open(TESTFN, 'w')
44        fp.close()
45        fp = open(TESTFN, 'r')
46        savestdin = sys.stdin
47        try:
48            try:
49                sys.stdin = fp
50                x = raw_input()
51            except EOFError:
52                pass
53        finally:
54            sys.stdin = savestdin
55            fp.close()
56            unlink(TESTFN)
57
58        self.raise_catch(IOError, "IOError")
59        self.assertRaises(IOError, open, 'this file does not exist', 'r')
60
61        self.raise_catch(ImportError, "ImportError")
62        self.assertRaises(ImportError, __import__, "undefined_module")
63
64        self.raise_catch(IndexError, "IndexError")
65        x = []
66        self.assertRaises(IndexError, x.__getitem__, 10)
67
68        self.raise_catch(KeyError, "KeyError")
69        x = {}
70        self.assertRaises(KeyError, x.__getitem__, 'key')
71
72        self.raise_catch(KeyboardInterrupt, "KeyboardInterrupt")
73
74        self.raise_catch(MemoryError, "MemoryError")
75
76        self.raise_catch(NameError, "NameError")
77        try: x = undefined_variable
78        except NameError: pass
79
80        self.raise_catch(OverflowError, "OverflowError")
81        x = 1
82        for dummy in range(128):
83            x += x  # this simply shouldn't blow up
84
85        self.raise_catch(RuntimeError, "RuntimeError")
86
87        self.raise_catch(SyntaxError, "SyntaxError")
88        try: exec '/\n'
89        except SyntaxError: pass
90
91        self.raise_catch(IndentationError, "IndentationError")
92
93        self.raise_catch(TabError, "TabError")
94        # can only be tested under -tt, and is the only test for -tt
95        #try: compile("try:\n\t1/0\n    \t1/0\nfinally:\n pass\n", '<string>', 'exec')
96        #except TabError: pass
97        #else: self.fail("TabError not raised")
98
99        self.raise_catch(SystemError, "SystemError")
100
101        self.raise_catch(SystemExit, "SystemExit")
102        self.assertRaises(SystemExit, sys.exit, 0)
103
104        self.raise_catch(TypeError, "TypeError")
105        try: [] + ()
106        except TypeError: pass
107
108        self.raise_catch(ValueError, "ValueError")
109        self.assertRaises(ValueError, chr, 10000)
110
111        self.raise_catch(ZeroDivisionError, "ZeroDivisionError")
112        try: x = 1 // 0
113        except ZeroDivisionError: pass
114
115        self.raise_catch(Exception, "Exception")
116        try: x = 1 // 0
117        except Exception, e: pass
118
119    def testSyntaxErrorMessage(self):
120        # make sure the right exception message is raised for each of
121        # these code fragments
122
123        def ckmsg(src, msg):
124            try:
125                compile(src, '<fragment>', 'exec')
126            except SyntaxError, e:
127                if e.msg != msg:
128                    self.fail("expected %s, got %s" % (msg, e.msg))
129            else:
130                self.fail("failed to get expected SyntaxError")
131
132        s = '''while 1:
133            try:
134                pass
135            finally:
136                continue'''
137
138        if not sys.platform.startswith('java'):
139            ckmsg(s, "'continue' not supported inside 'finally' clause")
140
141        s = '''if 1:
142        try:
143            continue
144        except:
145            pass'''
146
147        ckmsg(s, "'continue' not properly in loop")
148        ckmsg("continue\n", "'continue' not properly in loop")
149
150    @cpython_only
151    def testSettingException(self):
152        # test that setting an exception at the C level works even if the
153        # exception object can't be constructed.
154
155        class BadException:
156            def __init__(self_):
157                raise RuntimeError, "can't instantiate BadException"
158
159        def test_capi1():
160            import _testcapi
161            try:
162                _testcapi.raise_exception(BadException, 1)
163            except TypeError, err:
164                exc, err, tb = sys.exc_info()
165                co = tb.tb_frame.f_code
166                self.assertEqual(co.co_name, "test_capi1")
167                self.assertTrue(co.co_filename.endswith('test_exceptions'+os.extsep+'py'))
168            else:
169                self.fail("Expected exception")
170
171        def test_capi2():
172            import _testcapi
173            try:
174                _testcapi.raise_exception(BadException, 0)
175            except RuntimeError, err:
176                exc, err, tb = sys.exc_info()
177                co = tb.tb_frame.f_code
178                self.assertEqual(co.co_name, "__init__")
179                self.assertTrue(co.co_filename.endswith('test_exceptions'+os.extsep+'py'))
180                co2 = tb.tb_frame.f_back.f_code
181                self.assertEqual(co2.co_name, "test_capi2")
182            else:
183                self.fail("Expected exception")
184
185        if not sys.platform.startswith('java'):
186            test_capi1()
187            test_capi2()
188
189    def test_WindowsError(self):
190        try:
191            WindowsError
192        except NameError:
193            pass
194        else:
195            self.assertEqual(str(WindowsError(1001)),
196                                 "1001")
197            self.assertEqual(str(WindowsError(1001, "message")),
198                                 "[Error 1001] message")
199            self.assertEqual(WindowsError(1001, "message").errno, 22)
200            self.assertEqual(WindowsError(1001, "message").winerror, 1001)
201
202    @ignore_deprecation_warnings
203    def testAttributes(self):
204        # test that exception attributes are happy
205
206        exceptionList = [
207            (BaseException, (), {'message' : '', 'args' : ()}),
208            (BaseException, (1, ), {'message' : 1, 'args' : (1,)}),
209            (BaseException, ('foo',),
210                {'message' : 'foo', 'args' : ('foo',)}),
211            (BaseException, ('foo', 1),
212                {'message' : '', 'args' : ('foo', 1)}),
213            (SystemExit, ('foo',),
214                {'message' : 'foo', 'args' : ('foo',), 'code' : 'foo'}),
215            (IOError, ('foo',),
216                {'message' : 'foo', 'args' : ('foo',), 'filename' : None,
217                 'errno' : None, 'strerror' : None}),
218            (IOError, ('foo', 'bar'),
219                {'message' : '', 'args' : ('foo', 'bar'), 'filename' : None,
220                 'errno' : 'foo', 'strerror' : 'bar'}),
221            (IOError, ('foo', 'bar', 'baz'),
222                {'message' : '', 'args' : ('foo', 'bar'), 'filename' : 'baz',
223                 'errno' : 'foo', 'strerror' : 'bar'}),
224            (IOError, ('foo', 'bar', 'baz', 'quux'),
225                {'message' : '', 'args' : ('foo', 'bar', 'baz', 'quux')}),
226            (EnvironmentError, ('errnoStr', 'strErrorStr', 'filenameStr'),
227                {'message' : '', 'args' : ('errnoStr', 'strErrorStr'),
228                 'strerror' : 'strErrorStr', 'errno' : 'errnoStr',
229                 'filename' : 'filenameStr'}),
230            (EnvironmentError, (1, 'strErrorStr', 'filenameStr'),
231                {'message' : '', 'args' : (1, 'strErrorStr'), 'errno' : 1,
232                 'strerror' : 'strErrorStr', 'filename' : 'filenameStr'}),
233            (SyntaxError, (), {'message' : '', 'msg' : None, 'text' : None,
234                'filename' : None, 'lineno' : None, 'offset' : None,
235                'print_file_and_line' : None}),
236            (SyntaxError, ('msgStr',),
237                {'message' : 'msgStr', 'args' : ('msgStr',), 'text' : None,
238                 'print_file_and_line' : None, 'msg' : 'msgStr',
239                 'filename' : None, 'lineno' : None, 'offset' : None}),
240            (SyntaxError, ('msgStr', ('filenameStr', 'linenoStr', 'offsetStr',
241                           'textStr')),
242                {'message' : '', 'offset' : 'offsetStr', 'text' : 'textStr',
243                 'args' : ('msgStr', ('filenameStr', 'linenoStr',
244                                      'offsetStr', 'textStr')),
245                 'print_file_and_line' : None, 'msg' : 'msgStr',
246                 'filename' : 'filenameStr', 'lineno' : 'linenoStr'}),
247            (SyntaxError, ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
248                           'textStr', 'print_file_and_lineStr'),
249                {'message' : '', 'text' : None,
250                 'args' : ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
251                           'textStr', 'print_file_and_lineStr'),
252                 'print_file_and_line' : None, 'msg' : 'msgStr',
253                 'filename' : None, 'lineno' : None, 'offset' : None}),
254            (UnicodeError, (), {'message' : '', 'args' : (),}),
255            (UnicodeEncodeError, ('ascii', u'a', 0, 1, 'ordinal not in range'),
256                {'message' : '', 'args' : ('ascii', u'a', 0, 1,
257                                           'ordinal not in range'),
258                 'encoding' : 'ascii', 'object' : u'a',
259                 'start' : 0, 'reason' : 'ordinal not in range'}),
260            (UnicodeDecodeError, ('ascii', '\xff', 0, 1, 'ordinal not in range'),
261                {'message' : '', 'args' : ('ascii', '\xff', 0, 1,
262                                           'ordinal not in range'),
263                 'encoding' : 'ascii', 'object' : '\xff',
264                 'start' : 0, 'reason' : 'ordinal not in range'}),
265            (UnicodeTranslateError, (u"\u3042", 0, 1, "ouch"),
266                {'message' : '', 'args' : (u'\u3042', 0, 1, 'ouch'),
267                 'object' : u'\u3042', 'reason' : 'ouch',
268                 'start' : 0, 'end' : 1}),
269        ]
270        try:
271            exceptionList.append(
272                (WindowsError, (1, 'strErrorStr', 'filenameStr'),
273                    {'message' : '', 'args' : (1, 'strErrorStr'),
274                     'strerror' : 'strErrorStr', 'winerror' : 1,
275                     'errno' : 22, 'filename' : 'filenameStr'})
276            )
277        except NameError:
278            pass
279
280        for exc, args, expected in exceptionList:
281            try:
282                raise exc(*args)
283            except BaseException, e:
284                if type(e) is not exc:
285                    raise
286                # Verify module name
287                self.assertEqual(type(e).__module__, 'exceptions')
288                # Verify no ref leaks in Exc_str()
289                s = str(e)
290                for checkArgName in expected:
291                    self.assertEqual(repr(getattr(e, checkArgName)),
292                                     repr(expected[checkArgName]),
293                                     'exception "%s", attribute "%s"' %
294                                      (repr(e), checkArgName))
295
296                # test for pickling support
297                for p in pickle, cPickle:
298                    for protocol in range(p.HIGHEST_PROTOCOL + 1):
299                        new = p.loads(p.dumps(e, protocol))
300                        for checkArgName in expected:
301                            got = repr(getattr(new, checkArgName))
302                            want = repr(expected[checkArgName])
303                            self.assertEqual(got, want,
304                                             'pickled "%r", attribute "%s"' %
305                                             (e, checkArgName))
306
307
308    def testDeprecatedMessageAttribute(self):
309        # Accessing BaseException.message and relying on its value set by
310        # BaseException.__init__ triggers a deprecation warning.
311        exc = BaseException("foo")
312        with check_warnings(("BaseException.message has been deprecated "
313                             "as of Python 2.6", DeprecationWarning)) as w:
314            self.assertEqual(exc.message, "foo")
315        self.assertEqual(len(w.warnings), 1)
316
317    def testRegularMessageAttribute(self):
318        # Accessing BaseException.message after explicitly setting a value
319        # for it does not trigger a deprecation warning.
320        exc = BaseException("foo")
321        exc.message = "bar"
322        with check_warnings(quiet=True) as w:
323            self.assertEqual(exc.message, "bar")
324        self.assertEqual(len(w.warnings), 0)
325        # Deleting the message is supported, too.
326        del exc.message
327        with self.assertRaises(AttributeError):
328            exc.message
329
330    @ignore_deprecation_warnings
331    def testPickleMessageAttribute(self):
332        # Pickling with message attribute must work, as well.
333        e = Exception("foo")
334        f = Exception("foo")
335        f.message = "bar"
336        for p in pickle, cPickle:
337            ep = p.loads(p.dumps(e))
338            self.assertEqual(ep.message, "foo")
339            fp = p.loads(p.dumps(f))
340            self.assertEqual(fp.message, "bar")
341
342    @ignore_deprecation_warnings
343    def testSlicing(self):
344        # Test that you can slice an exception directly instead of requiring
345        # going through the 'args' attribute.
346        args = (1, 2, 3)
347        exc = BaseException(*args)
348        self.assertEqual(exc[:], args)
349        self.assertEqual(exc.args[:], args)
350
351    def testKeywordArgs(self):
352        # test that builtin exception don't take keyword args,
353        # but user-defined subclasses can if they want
354        self.assertRaises(TypeError, BaseException, a=1)
355
356        class DerivedException(BaseException):
357            def __init__(self, fancy_arg):
358                BaseException.__init__(self)
359                self.fancy_arg = fancy_arg
360
361        x = DerivedException(fancy_arg=42)
362        self.assertEqual(x.fancy_arg, 42)
363
364    def testInfiniteRecursion(self):
365        def f():
366            return f()
367        self.assertRaises(RuntimeError, f)
368
369        def g():
370            try:
371                return g()
372            except ValueError:
373                return -1
374
375        # The test prints an unraisable recursion error when
376        # doing "except ValueError", this is because subclass
377        # checking has recursion checking too.
378        with captured_output("stderr"):
379            try:
380                g()
381            except RuntimeError:
382                pass
383            except:
384                self.fail("Should have raised KeyError")
385            else:
386                self.fail("Should have raised KeyError")
387
388    def testUnicodeStrUsage(self):
389        # Make sure both instances and classes have a str and unicode
390        # representation.
391        self.assertTrue(str(Exception))
392        self.assertTrue(unicode(Exception))
393        self.assertTrue(str(Exception('a')))
394        self.assertTrue(unicode(Exception(u'a')))
395        self.assertTrue(unicode(Exception(u'\xe1')))
396
397    def testUnicodeChangeAttributes(self):
398        # See issue 7309. This was a crasher.
399
400        u = UnicodeEncodeError('baz', u'xxxxx', 1, 5, 'foo')
401        self.assertEqual(str(u), "'baz' codec can't encode characters in position 1-4: foo")
402        u.end = 2
403        self.assertEqual(str(u), "'baz' codec can't encode character u'\\x78' in position 1: foo")
404        u.end = 5
405        u.reason = 0x345345345345345345
406        self.assertEqual(str(u), "'baz' codec can't encode characters in position 1-4: 965230951443685724997")
407        u.encoding = 4000
408        self.assertEqual(str(u), "'4000' codec can't encode characters in position 1-4: 965230951443685724997")
409        u.start = 1000
410        self.assertEqual(str(u), "'4000' codec can't encode characters in position 1000-4: 965230951443685724997")
411
412        u = UnicodeDecodeError('baz', 'xxxxx', 1, 5, 'foo')
413        self.assertEqual(str(u), "'baz' codec can't decode bytes in position 1-4: foo")
414        u.end = 2
415        self.assertEqual(str(u), "'baz' codec can't decode byte 0x78 in position 1: foo")
416        u.end = 5
417        u.reason = 0x345345345345345345
418        self.assertEqual(str(u), "'baz' codec can't decode bytes in position 1-4: 965230951443685724997")
419        u.encoding = 4000
420        self.assertEqual(str(u), "'4000' codec can't decode bytes in position 1-4: 965230951443685724997")
421        u.start = 1000
422        self.assertEqual(str(u), "'4000' codec can't decode bytes in position 1000-4: 965230951443685724997")
423
424        u = UnicodeTranslateError(u'xxxx', 1, 5, 'foo')
425        self.assertEqual(str(u), "can't translate characters in position 1-4: foo")
426        u.end = 2
427        self.assertEqual(str(u), "can't translate character u'\\x78' in position 1: foo")
428        u.end = 5
429        u.reason = 0x345345345345345345
430        self.assertEqual(str(u), "can't translate characters in position 1-4: 965230951443685724997")
431        u.start = 1000
432        self.assertEqual(str(u), "can't translate characters in position 1000-4: 965230951443685724997")
433
434    def test_badisinstance(self):
435        # Bug #2542: if issubclass(e, MyException) raises an exception,
436        # it should be ignored
437        class Meta(type):
438            def __subclasscheck__(cls, subclass):
439                raise ValueError()
440
441        class MyException(Exception):
442            __metaclass__ = Meta
443            pass
444
445        with captured_output("stderr") as stderr:
446            try:
447                raise KeyError()
448            except MyException, e:
449                self.fail("exception should not be a MyException")
450            except KeyError:
451                pass
452            except:
453                self.fail("Should have raised KeyError")
454            else:
455                self.fail("Should have raised KeyError")
456
457        with captured_output("stderr") as stderr:
458            def g():
459                try:
460                    return g()
461                except RuntimeError:
462                    return sys.exc_info()
463            e, v, tb = g()
464            self.assertTrue(e is RuntimeError, e)
465            self.assertIn("maximum recursion depth exceeded", str(v))
466
467    def test_new_returns_invalid_instance(self):
468        # See issue #11627.
469        class MyException(Exception):
470            def __new__(cls, *args):
471                return object()
472
473        with self.assertRaises(TypeError):
474            raise MyException
475
476    def test_assert_with_tuple_arg(self):
477        try:
478            assert False, (3,)
479        except AssertionError as e:
480            self.assertEqual(str(e), "(3,)")
481
482    def test_bad_exception_clearing(self):
483        # See issue 16445: use of Py_XDECREF instead of Py_CLEAR in
484        # BaseException_set_message gave a possible way to segfault the
485        # interpreter.
486        class Nasty(str):
487            def __del__(message):
488                del e.message
489
490        e = ValueError(Nasty("msg"))
491        e.args = ()
492        del e.message
493
494
495# Helper class used by TestSameStrAndUnicodeMsg
496class ExcWithOverriddenStr(Exception):
497    """Subclass of Exception that accepts a keyword 'msg' arg that is
498    returned by __str__. 'msg' won't be included in self.args"""
499    def __init__(self, *args, **kwargs):
500        self.msg = kwargs.pop('msg') # msg should always be present
501        super(ExcWithOverriddenStr, self).__init__(*args, **kwargs)
502    def __str__(self):
503        return self.msg
504
505
506class TestSameStrAndUnicodeMsg(unittest.TestCase):
507    """unicode(err) should return the same message of str(err). See #6108"""
508
509    def check_same_msg(self, exc, msg):
510        """Helper function that checks if str(exc) == unicode(exc) == msg"""
511        self.assertEqual(str(exc), msg)
512        self.assertEqual(str(exc), unicode(exc))
513
514    def test_builtin_exceptions(self):
515        """Check same msg for built-in exceptions"""
516        # These exceptions implement a __str__ method that uses the args
517        # to create a better error message. unicode(e) should return the same
518        # message.
519        exceptions = [
520            SyntaxError('invalid syntax', ('<string>', 1, 3, '2+*3')),
521            IOError(2, 'No such file or directory'),
522            KeyError('both should have the same quotes'),
523            UnicodeDecodeError('ascii', '\xc3\xa0', 0, 1,
524                               'ordinal not in range(128)'),
525            UnicodeEncodeError('ascii', u'\u1234', 0, 1,
526                               'ordinal not in range(128)')
527        ]
528        for exception in exceptions:
529            self.assertEqual(str(exception), unicode(exception))
530
531    def test_0_args(self):
532        """Check same msg for Exception with 0 args"""
533        # str() and unicode() on an Exception with no args should return an
534        # empty string
535        self.check_same_msg(Exception(), '')
536
537    def test_0_args_with_overridden___str__(self):
538        """Check same msg for exceptions with 0 args and overridden __str__"""
539        # str() and unicode() on an exception with overridden __str__ that
540        # returns an ascii-only string should return the same string
541        for msg in ('foo', u'foo'):
542            self.check_same_msg(ExcWithOverriddenStr(msg=msg), msg)
543
544        # if __str__ returns a non-ascii unicode string str() should fail
545        # but unicode() should return the unicode string
546        e = ExcWithOverriddenStr(msg=u'f\xf6\xf6') # no args
547        self.assertRaises(UnicodeEncodeError, str, e)
548        self.assertEqual(unicode(e), u'f\xf6\xf6')
549
550    def test_1_arg(self):
551        """Check same msg for Exceptions with 1 arg"""
552        for arg in ('foo', u'foo'):
553            self.check_same_msg(Exception(arg), arg)
554
555        # if __str__ is not overridden and self.args[0] is a non-ascii unicode
556        # string, str() should try to return str(self.args[0]) and fail.
557        # unicode() should return unicode(self.args[0]) and succeed.
558        e = Exception(u'f\xf6\xf6')
559        self.assertRaises(UnicodeEncodeError, str, e)
560        self.assertEqual(unicode(e), u'f\xf6\xf6')
561
562    def test_1_arg_with_overridden___str__(self):
563        """Check same msg for exceptions with overridden __str__ and 1 arg"""
564        # when __str__ is overridden and __unicode__ is not implemented
565        # unicode(e) returns the same as unicode(e.__str__()).
566        for msg in ('foo', u'foo'):
567            self.check_same_msg(ExcWithOverriddenStr('arg', msg=msg), msg)
568
569        # if __str__ returns a non-ascii unicode string, str() should fail
570        # but unicode() should succeed.
571        e = ExcWithOverriddenStr('arg', msg=u'f\xf6\xf6') # 1 arg
572        self.assertRaises(UnicodeEncodeError, str, e)
573        self.assertEqual(unicode(e), u'f\xf6\xf6')
574
575    def test_many_args(self):
576        """Check same msg for Exceptions with many args"""
577        argslist = [
578            (3, 'foo'),
579            (1, u'foo', 'bar'),
580            (4, u'f\xf6\xf6', u'bar', 'baz')
581        ]
582        # both str() and unicode() should return a repr() of the args
583        for args in argslist:
584            self.check_same_msg(Exception(*args), repr(args))
585
586    def test_many_args_with_overridden___str__(self):
587        """Check same msg for exceptions with overridden __str__ and many args"""
588        # if __str__ returns an ascii string / ascii unicode string
589        # both str() and unicode() should succeed
590        for msg in ('foo', u'foo'):
591            e = ExcWithOverriddenStr('arg1', u'arg2', u'f\xf6\xf6', msg=msg)
592            self.check_same_msg(e, msg)
593
594        # if __str__ returns a non-ascii unicode string, str() should fail
595        # but unicode() should succeed
596        e = ExcWithOverriddenStr('arg1', u'f\xf6\xf6', u'arg3', # 3 args
597                                 msg=u'f\xf6\xf6')
598        self.assertRaises(UnicodeEncodeError, str, e)
599        self.assertEqual(unicode(e), u'f\xf6\xf6')
600
601    @cpython_only
602    def test_exception_with_doc(self):
603        import _testcapi
604        doc2 = "This is a test docstring."
605        doc4 = "This is another test docstring."
606
607        self.assertRaises(SystemError, _testcapi.make_exception_with_doc,
608                          "error1")
609
610        # test basic usage of PyErr_NewException
611        error1 = _testcapi.make_exception_with_doc("_testcapi.error1")
612        self.assertIs(type(error1), type)
613        self.assertTrue(issubclass(error1, Exception))
614        self.assertIsNone(error1.__doc__)
615
616        # test with given docstring
617        error2 = _testcapi.make_exception_with_doc("_testcapi.error2", doc2)
618        self.assertEqual(error2.__doc__, doc2)
619
620        # test with explicit base (without docstring)
621        error3 = _testcapi.make_exception_with_doc("_testcapi.error3",
622                                                   base=error2)
623        self.assertTrue(issubclass(error3, error2))
624
625        # test with explicit base tuple
626        class C(object):
627            pass
628        error4 = _testcapi.make_exception_with_doc("_testcapi.error4", doc4,
629                                                   (error3, C))
630        self.assertTrue(issubclass(error4, error3))
631        self.assertTrue(issubclass(error4, C))
632        self.assertEqual(error4.__doc__, doc4)
633
634        # test with explicit dictionary
635        error5 = _testcapi.make_exception_with_doc("_testcapi.error5", "",
636                                                   error4, {'a': 1})
637        self.assertTrue(issubclass(error5, error4))
638        self.assertEqual(error5.a, 1)
639        self.assertEqual(error5.__doc__, "")
640
641
642def test_main():
643    run_unittest(ExceptionTests, TestSameStrAndUnicodeMsg)
644
645if __name__ == '__main__':
646    test_main()
647