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