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
5 import unittest
6 import sys
7 import typing
8 from test import support
9
10
11 class ESC[4;38;5;81mTestIsInstanceExceptions(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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 # - cls isn't a type, or a tuple
19 # - cls has a __bases__ attribute
20 # - inst has a __class__ attribute
21 # - inst.__class__ as no __bases__ attribute
22 #
23 # Sounds complicated, I know, but this mimics a situation where an
24 # extension type raises an AttributeError when its __bases__ attribute is
25 # gotten. In that case, isinstance() should return False.
26 def test_class_has_no_bases(self):
27 class ESC[4;38;5;81mI(ESC[4;38;5;149mobject):
28 def getclass(self):
29 # This must return an object that has no __bases__ attribute
30 return None
31 __class__ = property(getclass)
32
33 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
34 def getbases(self):
35 return ()
36 __bases__ = property(getbases)
37
38 self.assertEqual(False, isinstance(I(), C()))
39
40 # Like above except that inst.__class__.__bases__ raises an exception
41 # other than AttributeError
42 def test_bases_raises_other_than_attribute_error(self):
43 class ESC[4;38;5;81mE(ESC[4;38;5;149mobject):
44 def getbases(self):
45 raise RuntimeError
46 __bases__ = property(getbases)
47
48 class ESC[4;38;5;81mI(ESC[4;38;5;149mobject):
49 def getclass(self):
50 return E()
51 __class__ = property(getclass)
52
53 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
54 def getbases(self):
55 return ()
56 __bases__ = property(getbases)
57
58 self.assertRaises(RuntimeError, isinstance, I(), C())
59
60 # Here's a situation where getattr(cls, '__bases__') raises an exception.
61 # If that exception is not AttributeError, it should not get masked
62 def test_dont_mask_non_attribute_error(self):
63 class ESC[4;38;5;81mI: pass
64
65 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
66 def getbases(self):
67 raise RuntimeError
68 __bases__ = property(getbases)
69
70 self.assertRaises(RuntimeError, isinstance, I(), C())
71
72 # Like above, except that getattr(cls, '__bases__') raises an
73 # AttributeError, which /should/ get masked as a TypeError
74 def test_mask_attribute_error(self):
75 class ESC[4;38;5;81mI: pass
76
77 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
78 def getbases(self):
79 raise AttributeError
80 __bases__ = property(getbases)
81
82 self.assertRaises(TypeError, isinstance, I(), C())
83
84 # check that we don't mask non AttributeErrors
85 # see: http://bugs.python.org/issue1574217
86 def test_isinstance_dont_mask_non_attribute_error(self):
87 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
88 def getclass(self):
89 raise RuntimeError
90 __class__ = property(getclass)
91
92 c = C()
93 self.assertRaises(RuntimeError, isinstance, c, bool)
94
95 # test another code path
96 class ESC[4;38;5;81mD: pass
97 self.assertRaises(RuntimeError, isinstance, c, D)
98
99
100 # These tests are similar to above, but tickle certain code paths in
101 # issubclass() instead of isinstance() -- really PyObject_IsSubclass()
102 # vs. PyObject_IsInstance().
103 class ESC[4;38;5;81mTestIsSubclassExceptions(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
104 def test_dont_mask_non_attribute_error(self):
105 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
106 def getbases(self):
107 raise RuntimeError
108 __bases__ = property(getbases)
109
110 class ESC[4;38;5;81mS(ESC[4;38;5;149mC): pass
111
112 self.assertRaises(RuntimeError, issubclass, C(), S())
113
114 def test_mask_attribute_error(self):
115 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
116 def getbases(self):
117 raise AttributeError
118 __bases__ = property(getbases)
119
120 class ESC[4;38;5;81mS(ESC[4;38;5;149mC): pass
121
122 self.assertRaises(TypeError, issubclass, C(), S())
123
124 # Like above, but test the second branch, where the __bases__ of the
125 # second arg (the cls arg) is tested. This means the first arg must
126 # return a valid __bases__, and it's okay for it to be a normal --
127 # unrelated by inheritance -- class.
128 def test_dont_mask_non_attribute_error_in_cls_arg(self):
129 class ESC[4;38;5;81mB: pass
130
131 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
132 def getbases(self):
133 raise RuntimeError
134 __bases__ = property(getbases)
135
136 self.assertRaises(RuntimeError, issubclass, B, C())
137
138 def test_mask_attribute_error_in_cls_arg(self):
139 class ESC[4;38;5;81mB: pass
140
141 class ESC[4;38;5;81mC(ESC[4;38;5;149mobject):
142 def getbases(self):
143 raise AttributeError
144 __bases__ = property(getbases)
145
146 self.assertRaises(TypeError, issubclass, B, C())
147
148
149 # meta classes for creating abstract classes and instances
150 class ESC[4;38;5;81mAbstractClass(ESC[4;38;5;149mobject):
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
161 class ESC[4;38;5;81mAbstractInstance(ESC[4;38;5;149mobject):
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
170 AbstractSuper = AbstractClass(bases=())
171
172 AbstractChild = AbstractClass(bases=(AbstractSuper,))
173
174 # normal classes
175 class ESC[4;38;5;81mSuper:
176 pass
177
178 class ESC[4;38;5;81mChild(ESC[4;38;5;149mSuper):
179 pass
180
181 class ESC[4;38;5;81mTestIsInstanceIsSubclass(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
182 # Tests to ensure that isinstance and issubclass work on abstract
183 # classes and instances. Before the 2.2 release, TypeErrors were
184 # raised when boolean values should have been returned. The bug was
185 # triggered by mixing 'normal' classes and instances were with
186 # 'abstract' classes and instances. This case tries to test all
187 # combinations.
188
189 def test_isinstance_normal(self):
190 # normal instances
191 self.assertEqual(True, isinstance(Super(), Super))
192 self.assertEqual(False, isinstance(Super(), Child))
193 self.assertEqual(False, isinstance(Super(), AbstractSuper))
194 self.assertEqual(False, isinstance(Super(), AbstractChild))
195
196 self.assertEqual(True, isinstance(Child(), Super))
197 self.assertEqual(False, isinstance(Child(), AbstractSuper))
198
199 def test_isinstance_abstract(self):
200 # abstract instances
201 self.assertEqual(True, isinstance(AbstractSuper(), AbstractSuper))
202 self.assertEqual(False, isinstance(AbstractSuper(), AbstractChild))
203 self.assertEqual(False, isinstance(AbstractSuper(), Super))
204 self.assertEqual(False, isinstance(AbstractSuper(), Child))
205
206 self.assertEqual(True, isinstance(AbstractChild(), AbstractChild))
207 self.assertEqual(True, isinstance(AbstractChild(), AbstractSuper))
208 self.assertEqual(False, isinstance(AbstractChild(), Super))
209 self.assertEqual(False, isinstance(AbstractChild(), Child))
210
211 def test_isinstance_with_or_union(self):
212 self.assertTrue(isinstance(Super(), Super | int))
213 self.assertFalse(isinstance(None, str | int))
214 self.assertTrue(isinstance(3, str | int))
215 self.assertTrue(isinstance("", str | int))
216 self.assertTrue(isinstance([], typing.List | typing.Tuple))
217 self.assertTrue(isinstance(2, typing.List | int))
218 self.assertFalse(isinstance(2, typing.List | typing.Tuple))
219 self.assertTrue(isinstance(None, int | None))
220 self.assertFalse(isinstance(3.14, int | str))
221 with self.assertRaises(TypeError):
222 isinstance(2, list[int])
223 with self.assertRaises(TypeError):
224 isinstance(2, list[int] | int)
225 with self.assertRaises(TypeError):
226 isinstance(2, float | str | list[int] | int)
227
228
229
230 def test_subclass_normal(self):
231 # normal classes
232 self.assertEqual(True, issubclass(Super, Super))
233 self.assertEqual(False, issubclass(Super, AbstractSuper))
234 self.assertEqual(False, issubclass(Super, Child))
235
236 self.assertEqual(True, issubclass(Child, Child))
237 self.assertEqual(True, issubclass(Child, Super))
238 self.assertEqual(False, issubclass(Child, AbstractSuper))
239 self.assertTrue(issubclass(typing.List, typing.List|typing.Tuple))
240 self.assertFalse(issubclass(int, typing.List|typing.Tuple))
241
242 def test_subclass_abstract(self):
243 # abstract classes
244 self.assertEqual(True, issubclass(AbstractSuper, AbstractSuper))
245 self.assertEqual(False, issubclass(AbstractSuper, AbstractChild))
246 self.assertEqual(False, issubclass(AbstractSuper, Child))
247
248 self.assertEqual(True, issubclass(AbstractChild, AbstractChild))
249 self.assertEqual(True, issubclass(AbstractChild, AbstractSuper))
250 self.assertEqual(False, issubclass(AbstractChild, Super))
251 self.assertEqual(False, issubclass(AbstractChild, Child))
252
253 def test_subclass_tuple(self):
254 # test with a tuple as the second argument classes
255 self.assertEqual(True, issubclass(Child, (Child,)))
256 self.assertEqual(True, issubclass(Child, (Super,)))
257 self.assertEqual(False, issubclass(Super, (Child,)))
258 self.assertEqual(True, issubclass(Super, (Child, Super)))
259 self.assertEqual(False, issubclass(Child, ()))
260 self.assertEqual(True, issubclass(Super, (Child, (Super,))))
261
262 self.assertEqual(True, issubclass(int, (int, (float, int))))
263 self.assertEqual(True, issubclass(str, (str, (Child, str))))
264
265 def test_subclass_recursion_limit(self):
266 # make sure that issubclass raises RecursionError before the C stack is
267 # blown
268 with support.infinite_recursion():
269 self.assertRaises(RecursionError, blowstack, issubclass, str, str)
270
271 def test_isinstance_recursion_limit(self):
272 # make sure that issubclass raises RecursionError before the C stack is
273 # blown
274 with support.infinite_recursion():
275 self.assertRaises(RecursionError, blowstack, isinstance, '', str)
276
277 def test_subclass_with_union(self):
278 self.assertTrue(issubclass(int, int | float | int))
279 self.assertTrue(issubclass(str, str | Child | str))
280 self.assertFalse(issubclass(dict, float|str))
281 self.assertFalse(issubclass(object, float|str))
282 with self.assertRaises(TypeError):
283 issubclass(2, Child | Super)
284 with self.assertRaises(TypeError):
285 issubclass(int, list[int] | Child)
286
287 def test_issubclass_refcount_handling(self):
288 # bpo-39382: abstract_issubclass() didn't hold item reference while
289 # peeking in the bases tuple, in the single inheritance case.
290 class ESC[4;38;5;81mA:
291 @property
292 def __bases__(self):
293 return (int, )
294
295 class ESC[4;38;5;81mB:
296 def __init__(self):
297 # setting this here increases the chances of exhibiting the bug,
298 # probably due to memory layout changes.
299 self.x = 1
300
301 @property
302 def __bases__(self):
303 return (A(), )
304
305 self.assertEqual(True, issubclass(B(), int))
306
307 def test_infinite_recursion_in_bases(self):
308 class ESC[4;38;5;81mX:
309 @property
310 def __bases__(self):
311 return self.__bases__
312 with support.infinite_recursion():
313 self.assertRaises(RecursionError, issubclass, X(), int)
314 self.assertRaises(RecursionError, issubclass, int, X())
315 self.assertRaises(RecursionError, isinstance, 1, X())
316
317 def test_infinite_recursion_via_bases_tuple(self):
318 """Regression test for bpo-30570."""
319 class ESC[4;38;5;81mFailure(ESC[4;38;5;149mobject):
320 def __getattr__(self, attr):
321 return (self, None)
322 with support.infinite_recursion():
323 with self.assertRaises(RecursionError):
324 issubclass(Failure(), int)
325
326 def test_infinite_cycle_in_bases(self):
327 """Regression test for bpo-30570."""
328 class ESC[4;38;5;81mX:
329 @property
330 def __bases__(self):
331 return (self, self, self)
332 with support.infinite_recursion():
333 self.assertRaises(RecursionError, issubclass, X(), int)
334
335 def test_infinitely_many_bases(self):
336 """Regression test for bpo-30570."""
337 class ESC[4;38;5;81mX:
338 def __getattr__(self, attr):
339 self.assertEqual(attr, "__bases__")
340 class ESC[4;38;5;81mA:
341 pass
342 class ESC[4;38;5;81mB:
343 pass
344 A.__getattr__ = B.__getattr__ = X.__getattr__
345 return (A(), B())
346 with support.infinite_recursion():
347 self.assertRaises(RecursionError, issubclass, X(), int)
348
349
350 def blowstack(fxn, arg, compare_to):
351 # Make sure that calling isinstance with a deeply nested tuple for its
352 # argument will raise RecursionError eventually.
353 tuple_arg = (compare_to,)
354 for cnt in range(sys.getrecursionlimit()+5):
355 tuple_arg = (tuple_arg,)
356 fxn(arg, tuple_arg)
357
358
359 if __name__ == '__main__':
360 unittest.main()