mirror of
				https://github.com/python/cpython.git
				synced 2025-10-25 15:58:57 +00:00 
			
		
		
		
	 1c56f8ffad
			
		
	
	
		1c56f8ffad
		
			
		
	
	
	
	
		
			
			Hold reference of __bases__ tuple until tuple item is done with, because by dropping the reference the item may be destroyed.
		
			
				
	
	
		
			285 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 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_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))
 | ||
| 
 | ||
|     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
 | ||
|         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
 | ||
|         self.assertRaises(RecursionError, blowstack, isinstance, '', str)
 | ||
| 
 | ||
|     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 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()
 |