| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- # Tests some corner cases with isinstance() and issubclass(). While these
- # tests use new style classes and properties, they actually do whitebox
- # testing of error conditions uncovered when using extension types.
- import unittest
- import sys
- import typing
- from test import support
- class TestIsInstanceExceptions(unittest.TestCase):
- # Test to make sure that an AttributeError when accessing the instance's
- # class's bases is masked. This was actually a bug in Python 2.2 and
- # 2.2.1 where the exception wasn't caught but it also wasn't being cleared
- # (leading to an "undetected error" in the debug build). Set up is,
- # isinstance(inst, cls) where:
- #
- # - cls isn't a type, or a tuple
- # - cls has a __bases__ attribute
- # - inst has a __class__ attribute
- # - inst.__class__ as no __bases__ attribute
- #
- # Sounds complicated, I know, but this mimics a situation where an
- # extension type raises an AttributeError when its __bases__ attribute is
- # gotten. In that case, isinstance() should return False.
- def test_class_has_no_bases(self):
- class I(object):
- def getclass(self):
- # This must return an object that has no __bases__ attribute
- return None
- __class__ = property(getclass)
- class C(object):
- def getbases(self):
- return ()
- __bases__ = property(getbases)
- self.assertEqual(False, isinstance(I(), C()))
- # Like above except that inst.__class__.__bases__ raises an exception
- # other than AttributeError
- def test_bases_raises_other_than_attribute_error(self):
- class E(object):
- def getbases(self):
- raise RuntimeError
- __bases__ = property(getbases)
- class I(object):
- def getclass(self):
- return E()
- __class__ = property(getclass)
- class C(object):
- def getbases(self):
- return ()
- __bases__ = property(getbases)
- self.assertRaises(RuntimeError, isinstance, I(), C())
- # Here's a situation where getattr(cls, '__bases__') raises an exception.
- # If that exception is not AttributeError, it should not get masked
- def test_dont_mask_non_attribute_error(self):
- class I: pass
- class C(object):
- def getbases(self):
- raise RuntimeError
- __bases__ = property(getbases)
- self.assertRaises(RuntimeError, isinstance, I(), C())
- # Like above, except that getattr(cls, '__bases__') raises an
- # AttributeError, which /should/ get masked as a TypeError
- def test_mask_attribute_error(self):
- class I: pass
- class C(object):
- def getbases(self):
- raise AttributeError
- __bases__ = property(getbases)
- self.assertRaises(TypeError, isinstance, I(), C())
- # check that we don't mask non AttributeErrors
- # see: http://bugs.python.org/issue1574217
- def test_isinstance_dont_mask_non_attribute_error(self):
- class C(object):
- def getclass(self):
- raise RuntimeError
- __class__ = property(getclass)
- c = C()
- self.assertRaises(RuntimeError, isinstance, c, bool)
- # test another code path
- class D: pass
- self.assertRaises(RuntimeError, isinstance, c, D)
- # These tests are similar to above, but tickle certain code paths in
- # issubclass() instead of isinstance() -- really PyObject_IsSubclass()
- # vs. PyObject_IsInstance().
- class TestIsSubclassExceptions(unittest.TestCase):
- def test_dont_mask_non_attribute_error(self):
- class C(object):
- def getbases(self):
- raise RuntimeError
- __bases__ = property(getbases)
- class S(C): pass
- self.assertRaises(RuntimeError, issubclass, C(), S())
- def test_mask_attribute_error(self):
- class C(object):
- def getbases(self):
- raise AttributeError
- __bases__ = property(getbases)
- class S(C): pass
- self.assertRaises(TypeError, issubclass, C(), S())
- # Like above, but test the second branch, where the __bases__ of the
- # second arg (the cls arg) is tested. This means the first arg must
- # return a valid __bases__, and it's okay for it to be a normal --
- # unrelated by inheritance -- class.
- def test_dont_mask_non_attribute_error_in_cls_arg(self):
- class B: pass
- class C(object):
- def getbases(self):
- raise RuntimeError
- __bases__ = property(getbases)
- self.assertRaises(RuntimeError, issubclass, B, C())
- def test_mask_attribute_error_in_cls_arg(self):
- class B: pass
- class C(object):
- def getbases(self):
- raise AttributeError
- __bases__ = property(getbases)
- self.assertRaises(TypeError, issubclass, B, C())
- # meta classes for creating abstract classes and instances
- class AbstractClass(object):
- def __init__(self, bases):
- self.bases = bases
- def getbases(self):
- return self.bases
- __bases__ = property(getbases)
- def __call__(self):
- return AbstractInstance(self)
- class AbstractInstance(object):
- def __init__(self, klass):
- self.klass = klass
- def getclass(self):
- return self.klass
- __class__ = property(getclass)
- # abstract classes
- AbstractSuper = AbstractClass(bases=())
- AbstractChild = AbstractClass(bases=(AbstractSuper,))
- # normal classes
- class Super:
- pass
- class Child(Super):
- pass
- class TestIsInstanceIsSubclass(unittest.TestCase):
- # Tests to ensure that isinstance and issubclass work on abstract
- # classes and instances. Before the 2.2 release, TypeErrors were
- # raised when boolean values should have been returned. The bug was
- # triggered by mixing 'normal' classes and instances were with
- # 'abstract' classes and instances. This case tries to test all
- # combinations.
- def test_isinstance_normal(self):
- # normal instances
- self.assertEqual(True, isinstance(Super(), Super))
- self.assertEqual(False, isinstance(Super(), Child))
- self.assertEqual(False, isinstance(Super(), AbstractSuper))
- self.assertEqual(False, isinstance(Super(), AbstractChild))
- self.assertEqual(True, isinstance(Child(), Super))
- self.assertEqual(False, isinstance(Child(), AbstractSuper))
- def test_isinstance_abstract(self):
- # abstract instances
- self.assertEqual(True, isinstance(AbstractSuper(), AbstractSuper))
- self.assertEqual(False, isinstance(AbstractSuper(), AbstractChild))
- self.assertEqual(False, isinstance(AbstractSuper(), Super))
- self.assertEqual(False, isinstance(AbstractSuper(), Child))
- self.assertEqual(True, isinstance(AbstractChild(), AbstractChild))
- self.assertEqual(True, isinstance(AbstractChild(), AbstractSuper))
- self.assertEqual(False, isinstance(AbstractChild(), Super))
- self.assertEqual(False, isinstance(AbstractChild(), Child))
- def test_isinstance_with_or_union(self):
- self.assertTrue(isinstance(Super(), Super | int))
- self.assertFalse(isinstance(None, str | int))
- self.assertTrue(isinstance(3, str | int))
- self.assertTrue(isinstance("", str | int))
- self.assertTrue(isinstance([], typing.List | typing.Tuple))
- self.assertTrue(isinstance(2, typing.List | int))
- self.assertFalse(isinstance(2, typing.List | typing.Tuple))
- self.assertTrue(isinstance(None, int | None))
- self.assertFalse(isinstance(3.14, int | str))
- with self.assertRaises(TypeError):
- isinstance(2, list[int])
- with self.assertRaises(TypeError):
- isinstance(2, list[int] | int)
- with self.assertRaises(TypeError):
- isinstance(2, float | str | list[int] | int)
- def test_subclass_normal(self):
- # normal classes
- self.assertEqual(True, issubclass(Super, Super))
- self.assertEqual(False, issubclass(Super, AbstractSuper))
- self.assertEqual(False, issubclass(Super, Child))
- self.assertEqual(True, issubclass(Child, Child))
- self.assertEqual(True, issubclass(Child, Super))
- self.assertEqual(False, issubclass(Child, AbstractSuper))
- self.assertTrue(issubclass(typing.List, typing.List|typing.Tuple))
- self.assertFalse(issubclass(int, typing.List|typing.Tuple))
- def test_subclass_abstract(self):
- # abstract classes
- self.assertEqual(True, issubclass(AbstractSuper, AbstractSuper))
- self.assertEqual(False, issubclass(AbstractSuper, AbstractChild))
- self.assertEqual(False, issubclass(AbstractSuper, Child))
- self.assertEqual(True, issubclass(AbstractChild, AbstractChild))
- self.assertEqual(True, issubclass(AbstractChild, AbstractSuper))
- self.assertEqual(False, issubclass(AbstractChild, Super))
- self.assertEqual(False, issubclass(AbstractChild, Child))
- def test_subclass_tuple(self):
- # test with a tuple as the second argument classes
- self.assertEqual(True, issubclass(Child, (Child,)))
- self.assertEqual(True, issubclass(Child, (Super,)))
- self.assertEqual(False, issubclass(Super, (Child,)))
- self.assertEqual(True, issubclass(Super, (Child, Super)))
- self.assertEqual(False, issubclass(Child, ()))
- self.assertEqual(True, issubclass(Super, (Child, (Super,))))
- self.assertEqual(True, issubclass(int, (int, (float, int))))
- self.assertEqual(True, issubclass(str, (str, (Child, str))))
- def test_subclass_recursion_limit(self):
- # make sure that issubclass raises RecursionError before the C stack is
- # blown
- with support.infinite_recursion():
- self.assertRaises(RecursionError, blowstack, issubclass, str, str)
- def test_isinstance_recursion_limit(self):
- # make sure that issubclass raises RecursionError before the C stack is
- # blown
- with support.infinite_recursion():
- self.assertRaises(RecursionError, blowstack, isinstance, '', str)
- def test_subclass_with_union(self):
- self.assertTrue(issubclass(int, int | float | int))
- self.assertTrue(issubclass(str, str | Child | str))
- self.assertFalse(issubclass(dict, float|str))
- self.assertFalse(issubclass(object, float|str))
- with self.assertRaises(TypeError):
- issubclass(2, Child | Super)
- with self.assertRaises(TypeError):
- issubclass(int, list[int] | Child)
- def test_issubclass_refcount_handling(self):
- # bpo-39382: abstract_issubclass() didn't hold item reference while
- # peeking in the bases tuple, in the single inheritance case.
- class A:
- @property
- def __bases__(self):
- return (int, )
- class B:
- def __init__(self):
- # setting this here increases the chances of exhibiting the bug,
- # probably due to memory layout changes.
- self.x = 1
- @property
- def __bases__(self):
- return (A(), )
- self.assertEqual(True, issubclass(B(), int))
- def test_infinite_recursion_in_bases(self):
- class X:
- @property
- def __bases__(self):
- return self.__bases__
- with support.infinite_recursion():
- self.assertRaises(RecursionError, issubclass, X(), int)
- self.assertRaises(RecursionError, issubclass, int, X())
- self.assertRaises(RecursionError, isinstance, 1, X())
- def test_infinite_recursion_via_bases_tuple(self):
- """Regression test for bpo-30570."""
- class Failure(object):
- def __getattr__(self, attr):
- return (self, None)
- with support.infinite_recursion():
- with self.assertRaises(RecursionError):
- issubclass(Failure(), int)
- def test_infinite_cycle_in_bases(self):
- """Regression test for bpo-30570."""
- class X:
- @property
- def __bases__(self):
- return (self, self, self)
- with support.infinite_recursion():
- self.assertRaises(RecursionError, issubclass, X(), int)
- def test_infinitely_many_bases(self):
- """Regression test for bpo-30570."""
- class X:
- def __getattr__(self, attr):
- self.assertEqual(attr, "__bases__")
- class A:
- pass
- class B:
- pass
- A.__getattr__ = B.__getattr__ = X.__getattr__
- return (A(), B())
- with support.infinite_recursion():
- self.assertRaises(RecursionError, issubclass, X(), int)
- def blowstack(fxn, arg, compare_to):
- # Make sure that calling isinstance with a deeply nested tuple for its
- # argument will raise RecursionError eventually.
- tuple_arg = (compare_to,)
- for cnt in range(sys.getrecursionlimit()+5):
- tuple_arg = (tuple_arg,)
- fxn(arg, tuple_arg)
- if __name__ == '__main__':
- unittest.main()
|