• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Tests some corner cases with isinstance() and issubclass().  While these
2# tests use new style classes and properties, they actually do whitebox
3# testing of error conditions uncovered when using extension types.
4
5import unittest
6from test import test_support
7import sys
8
9
10
11class TestIsInstanceExceptions(unittest.TestCase):
12    # Test to make sure that an AttributeError when accessing the instance's
13    # class's bases is masked.  This was actually a bug in Python 2.2 and
14    # 2.2.1 where the exception wasn't caught but it also wasn't being cleared
15    # (leading to an "undetected error" in the debug build).  Set up is,
16    # isinstance(inst, cls) where:
17    #
18    # - inst isn't an InstanceType
19    # - cls isn't a ClassType, a TypeType, or a TupleType
20    # - cls has a __bases__ attribute
21    # - inst has a __class__ attribute
22    # - inst.__class__ as no __bases__ attribute
23    #
24    # Sounds complicated, I know, but this mimics a situation where an
25    # extension type raises an AttributeError when its __bases__ attribute is
26    # gotten.  In that case, isinstance() should return False.
27    def test_class_has_no_bases(self):
28        class I(object):
29            def getclass(self):
30                # This must return an object that has no __bases__ attribute
31                return None
32            __class__ = property(getclass)
33
34        class C(object):
35            def getbases(self):
36                return ()
37            __bases__ = property(getbases)
38
39        self.assertEqual(False, isinstance(I(), C()))
40
41    # Like above except that inst.__class__.__bases__ raises an exception
42    # other than AttributeError
43    def test_bases_raises_other_than_attribute_error(self):
44        class E(object):
45            def getbases(self):
46                raise RuntimeError
47            __bases__ = property(getbases)
48
49        class I(object):
50            def getclass(self):
51                return E()
52            __class__ = property(getclass)
53
54        class C(object):
55            def getbases(self):
56                return ()
57            __bases__ = property(getbases)
58
59        self.assertRaises(RuntimeError, isinstance, I(), C())
60
61    # Here's a situation where getattr(cls, '__bases__') raises an exception.
62    # If that exception is not AttributeError, it should not get masked
63    def test_dont_mask_non_attribute_error(self):
64        class I: pass
65
66        class C(object):
67            def getbases(self):
68                raise RuntimeError
69            __bases__ = property(getbases)
70
71        self.assertRaises(RuntimeError, isinstance, I(), C())
72
73    # Like above, except that getattr(cls, '__bases__') raises an
74    # AttributeError, which /should/ get masked as a TypeError
75    def test_mask_attribute_error(self):
76        class I: pass
77
78        class C(object):
79            def getbases(self):
80                raise AttributeError
81            __bases__ = property(getbases)
82
83        self.assertRaises(TypeError, isinstance, I(), C())
84
85
86
87# These tests are similar to above, but tickle certain code paths in
88# issubclass() instead of isinstance() -- really PyObject_IsSubclass()
89# vs. PyObject_IsInstance().
90class TestIsSubclassExceptions(unittest.TestCase):
91    def test_dont_mask_non_attribute_error(self):
92        class C(object):
93            def getbases(self):
94                raise RuntimeError
95            __bases__ = property(getbases)
96
97        class S(C): pass
98
99        self.assertRaises(RuntimeError, issubclass, C(), S())
100
101    def test_mask_attribute_error(self):
102        class C(object):
103            def getbases(self):
104                raise AttributeError
105            __bases__ = property(getbases)
106
107        class S(C): pass
108
109        self.assertRaises(TypeError, issubclass, C(), S())
110
111    # Like above, but test the second branch, where the __bases__ of the
112    # second arg (the cls arg) is tested.  This means the first arg must
113    # return a valid __bases__, and it's okay for it to be a normal --
114    # unrelated by inheritance -- class.
115    def test_dont_mask_non_attribute_error_in_cls_arg(self):
116        class B: pass
117
118        class C(object):
119            def getbases(self):
120                raise RuntimeError
121            __bases__ = property(getbases)
122
123        self.assertRaises(RuntimeError, issubclass, B, C())
124
125    def test_mask_attribute_error_in_cls_arg(self):
126        class B: pass
127
128        class C(object):
129            def getbases(self):
130                raise AttributeError
131            __bases__ = property(getbases)
132
133        self.assertRaises(TypeError, issubclass, B, C())
134
135
136
137# meta classes for creating abstract classes and instances
138class AbstractClass(object):
139    def __init__(self, bases):
140        self.bases = bases
141
142    def getbases(self):
143        return self.bases
144    __bases__ = property(getbases)
145
146    def __call__(self):
147        return AbstractInstance(self)
148
149class AbstractInstance(object):
150    def __init__(self, klass):
151        self.klass = klass
152
153    def getclass(self):
154        return self.klass
155    __class__ = property(getclass)
156
157# abstract classes
158AbstractSuper = AbstractClass(bases=())
159
160AbstractChild = AbstractClass(bases=(AbstractSuper,))
161
162# normal classes
163class Super:
164    pass
165
166class Child(Super):
167    pass
168
169# new-style classes
170class NewSuper(object):
171    pass
172
173class NewChild(NewSuper):
174    pass
175
176
177
178class TestIsInstanceIsSubclass(unittest.TestCase):
179    # Tests to ensure that isinstance and issubclass work on abstract
180    # classes and instances.  Before the 2.2 release, TypeErrors were
181    # raised when boolean values should have been returned.  The bug was
182    # triggered by mixing 'normal' classes and instances were with
183    # 'abstract' classes and instances.  This case tries to test all
184    # combinations.
185
186    def test_isinstance_normal(self):
187        # normal instances
188        self.assertEqual(True, isinstance(Super(), Super))
189        self.assertEqual(False, isinstance(Super(), Child))
190        self.assertEqual(False, isinstance(Super(), AbstractSuper))
191        self.assertEqual(False, isinstance(Super(), AbstractChild))
192
193        self.assertEqual(True, isinstance(Child(), Super))
194        self.assertEqual(False, isinstance(Child(), AbstractSuper))
195
196    def test_isinstance_abstract(self):
197        # abstract instances
198        self.assertEqual(True, isinstance(AbstractSuper(), AbstractSuper))
199        self.assertEqual(False, isinstance(AbstractSuper(), AbstractChild))
200        self.assertEqual(False, isinstance(AbstractSuper(), Super))
201        self.assertEqual(False, isinstance(AbstractSuper(), Child))
202
203        self.assertEqual(True, isinstance(AbstractChild(), AbstractChild))
204        self.assertEqual(True, isinstance(AbstractChild(), AbstractSuper))
205        self.assertEqual(False, isinstance(AbstractChild(), Super))
206        self.assertEqual(False, isinstance(AbstractChild(), Child))
207
208    def test_subclass_normal(self):
209        # normal classes
210        self.assertEqual(True, issubclass(Super, Super))
211        self.assertEqual(False, issubclass(Super, AbstractSuper))
212        self.assertEqual(False, issubclass(Super, Child))
213
214        self.assertEqual(True, issubclass(Child, Child))
215        self.assertEqual(True, issubclass(Child, Super))
216        self.assertEqual(False, issubclass(Child, AbstractSuper))
217
218    def test_subclass_abstract(self):
219        # abstract classes
220        self.assertEqual(True, issubclass(AbstractSuper, AbstractSuper))
221        self.assertEqual(False, issubclass(AbstractSuper, AbstractChild))
222        self.assertEqual(False, issubclass(AbstractSuper, Child))
223
224        self.assertEqual(True, issubclass(AbstractChild, AbstractChild))
225        self.assertEqual(True, issubclass(AbstractChild, AbstractSuper))
226        self.assertEqual(False, issubclass(AbstractChild, Super))
227        self.assertEqual(False, issubclass(AbstractChild, Child))
228
229    def test_subclass_tuple(self):
230        # test with a tuple as the second argument classes
231        self.assertEqual(True, issubclass(Child, (Child,)))
232        self.assertEqual(True, issubclass(Child, (Super,)))
233        self.assertEqual(False, issubclass(Super, (Child,)))
234        self.assertEqual(True, issubclass(Super, (Child, Super)))
235        self.assertEqual(False, issubclass(Child, ()))
236        self.assertEqual(True, issubclass(Super, (Child, (Super,))))
237
238        self.assertEqual(True, issubclass(NewChild, (NewChild,)))
239        self.assertEqual(True, issubclass(NewChild, (NewSuper,)))
240        self.assertEqual(False, issubclass(NewSuper, (NewChild,)))
241        self.assertEqual(True, issubclass(NewSuper, (NewChild, NewSuper)))
242        self.assertEqual(False, issubclass(NewChild, ()))
243        self.assertEqual(True, issubclass(NewSuper, (NewChild, (NewSuper,))))
244
245        self.assertEqual(True, issubclass(int, (long, (float, int))))
246        if test_support.have_unicode:
247            self.assertEqual(True, issubclass(str, (unicode, (Child, NewChild, basestring))))
248
249    def test_subclass_recursion_limit(self):
250        # make sure that issubclass raises RuntimeError before the C stack is
251        # blown
252        self.assertRaises(RuntimeError, blowstack, issubclass, str, str)
253
254    def test_isinstance_recursion_limit(self):
255        # make sure that issubclass raises RuntimeError before the C stack is
256        # blown
257        self.assertRaises(RuntimeError, blowstack, isinstance, '', str)
258
259def blowstack(fxn, arg, compare_to):
260    # Make sure that calling isinstance with a deeply nested tuple for its
261    # argument will raise RuntimeError eventually.
262    tuple_arg = (compare_to,)
263    for cnt in xrange(sys.getrecursionlimit()+5):
264        tuple_arg = (tuple_arg,)
265        fxn(arg, tuple_arg)
266
267
268def test_main():
269    test_support.run_unittest(
270        TestIsInstanceExceptions,
271        TestIsSubclassExceptions,
272        TestIsInstanceIsSubclass
273    )
274
275
276if __name__ == '__main__':
277    test_main()
278