# 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 | |
from test import test_support | |
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: | |
# | |
# - inst isn't an InstanceType | |
# - cls isn't a ClassType, a TypeType, or a TupleType | |
# - 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()) | |
# 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 | |
# new-style classes | |
class NewSuper(object): | |
pass | |
class NewChild(NewSuper): | |
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(NewChild, (NewChild,))) | |
self.assertEqual(True, issubclass(NewChild, (NewSuper,))) | |
self.assertEqual(False, issubclass(NewSuper, (NewChild,))) | |
self.assertEqual(True, issubclass(NewSuper, (NewChild, NewSuper))) | |
self.assertEqual(False, issubclass(NewChild, ())) | |
self.assertEqual(True, issubclass(NewSuper, (NewChild, (NewSuper,)))) | |
self.assertEqual(True, issubclass(int, (long, (float, int)))) | |
if test_support.have_unicode: | |
self.assertEqual(True, issubclass(str, (unicode, (Child, NewChild, basestring)))) | |
def test_subclass_recursion_limit(self): | |
# make sure that issubclass raises RuntimeError before the C stack is | |
# blown | |
self.assertRaises(RuntimeError, blowstack, issubclass, str, str) | |
def test_isinstance_recursion_limit(self): | |
# make sure that issubclass raises RuntimeError before the C stack is | |
# blown | |
self.assertRaises(RuntimeError, blowstack, isinstance, '', str) | |
def blowstack(fxn, arg, compare_to): | |
# Make sure that calling isinstance with a deeply nested tuple for its | |
# argument will raise RuntimeError eventually. | |
tuple_arg = (compare_to,) | |
for cnt in xrange(sys.getrecursionlimit()+5): | |
tuple_arg = (tuple_arg,) | |
fxn(arg, tuple_arg) | |
def test_main(): | |
test_support.run_unittest( | |
TestIsInstanceExceptions, | |
TestIsSubclassExceptions, | |
TestIsInstanceIsSubclass | |
) | |
if __name__ == '__main__': | |
test_main() |