| # 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 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_tuple_subclass_methods_not_called(self): | |
| # gh-83731: make sure isinstance() reads the items of a tuple | |
| # subclass directly, without calling its __iter__(), | |
| # __getitem__() or __len__(). | |
| class T(tuple): | |
| def __iter__(self): | |
| raise AssertionError('__iter__ called') | |
| def __getitem__(self, index): | |
| raise AssertionError('__getitem__ called') | |
| def __len__(self): | |
| raise AssertionError('__len__ called') | |
| self.assertTrue(isinstance(1, T((int,)))) | |
| @support.skip_if_huge_c_stack() | |
| @support.skip_wasi_stack_overflow() | |
| @support.skip_emscripten_stack_overflow() | |
| def test_subclass_recursion_limit(self): | |
| # make sure that issubclass raises RecursionError before the C stack is | |
| # blown | |
| self.assertRaises(RecursionError, blowstack, issubclass, str, str) | |
| @support.skip_if_huge_c_stack() | |
| @support.skip_wasi_stack_overflow() | |
| @support.skip_emscripten_stack_overflow() | |
| def test_isinstance_recursion_limit(self): | |
| # make sure that issubclass raises RecursionError before the C stack is | |
| # blown | |
| 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(25): | |
| self.assertRaises(RecursionError, issubclass, X(), int) | |
| self.assertRaises(RecursionError, issubclass, int, X()) | |
| self.assertRaises(RecursionError, isinstance, 1, X()) | |
| @support.skip_if_huge_c_stack() | |
| @support.skip_emscripten_stack_overflow() | |
| @support.skip_wasi_stack_overflow() | |
| 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) | |
| @support.skip_if_huge_c_stack() | |
| @support.skip_emscripten_stack_overflow() | |
| @support.skip_wasi_stack_overflow() | |
| 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(25): | |
| 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,) | |
| while True: | |
| for _ in range(100): | |
| tuple_arg = (tuple_arg,) | |
| fxn(arg, tuple_arg) | |
| if __name__ == '__main__': | |
| unittest.main() |