• 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
6import sys
7import typing
8from test import support
9
10
11
12class TestIsInstanceExceptions(unittest.TestCase):
13    # Test to make sure that an AttributeError when accessing the instance's
14    # class's bases is masked.  This was actually a bug in Python 2.2 and
15    # 2.2.1 where the exception wasn't caught but it also wasn't being cleared
16    # (leading to an "undetected error" in the debug build).  Set up is,
17    # isinstance(inst, cls) where:
18    #
19    # - cls isn't a type, or a tuple
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    # check that we don't mask non AttributeErrors
86    # see: http://bugs.python.org/issue1574217
87    def test_isinstance_dont_mask_non_attribute_error(self):
88        class C(object):
89            def getclass(self):
90                raise RuntimeError
91            __class__ = property(getclass)
92
93        c = C()
94        self.assertRaises(RuntimeError, isinstance, c, bool)
95
96        # test another code path
97        class D: pass
98        self.assertRaises(RuntimeError, isinstance, c, D)
99
100
101# These tests are similar to above, but tickle certain code paths in
102# issubclass() instead of isinstance() -- really PyObject_IsSubclass()
103# vs. PyObject_IsInstance().
104class TestIsSubclassExceptions(unittest.TestCase):
105    def test_dont_mask_non_attribute_error(self):
106        class C(object):
107            def getbases(self):
108                raise RuntimeError
109            __bases__ = property(getbases)
110
111        class S(C): pass
112
113        self.assertRaises(RuntimeError, issubclass, C(), S())
114
115    def test_mask_attribute_error(self):
116        class C(object):
117            def getbases(self):
118                raise AttributeError
119            __bases__ = property(getbases)
120
121        class S(C): pass
122
123        self.assertRaises(TypeError, issubclass, C(), S())
124
125    # Like above, but test the second branch, where the __bases__ of the
126    # second arg (the cls arg) is tested.  This means the first arg must
127    # return a valid __bases__, and it's okay for it to be a normal --
128    # unrelated by inheritance -- class.
129    def test_dont_mask_non_attribute_error_in_cls_arg(self):
130        class B: pass
131
132        class C(object):
133            def getbases(self):
134                raise RuntimeError
135            __bases__ = property(getbases)
136
137        self.assertRaises(RuntimeError, issubclass, B, C())
138
139    def test_mask_attribute_error_in_cls_arg(self):
140        class B: pass
141
142        class C(object):
143            def getbases(self):
144                raise AttributeError
145            __bases__ = property(getbases)
146
147        self.assertRaises(TypeError, issubclass, B, C())
148
149
150
151# meta classes for creating abstract classes and instances
152class AbstractClass(object):
153    def __init__(self, bases):
154        self.bases = bases
155
156    def getbases(self):
157        return self.bases
158    __bases__ = property(getbases)
159
160    def __call__(self):
161        return AbstractInstance(self)
162
163class AbstractInstance(object):
164    def __init__(self, klass):
165        self.klass = klass
166
167    def getclass(self):
168        return self.klass
169    __class__ = property(getclass)
170
171# abstract classes
172AbstractSuper = AbstractClass(bases=())
173
174AbstractChild = AbstractClass(bases=(AbstractSuper,))
175
176# normal classes
177class Super:
178    pass
179
180class Child(Super):
181    pass
182
183class TestIsInstanceIsSubclass(unittest.TestCase):
184    # Tests to ensure that isinstance and issubclass work on abstract
185    # classes and instances.  Before the 2.2 release, TypeErrors were
186    # raised when boolean values should have been returned.  The bug was
187    # triggered by mixing 'normal' classes and instances were with
188    # 'abstract' classes and instances.  This case tries to test all
189    # combinations.
190
191    def test_isinstance_normal(self):
192        # normal instances
193        self.assertEqual(True, isinstance(Super(), Super))
194        self.assertEqual(False, isinstance(Super(), Child))
195        self.assertEqual(False, isinstance(Super(), AbstractSuper))
196        self.assertEqual(False, isinstance(Super(), AbstractChild))
197
198        self.assertEqual(True, isinstance(Child(), Super))
199        self.assertEqual(False, isinstance(Child(), AbstractSuper))
200
201    def test_isinstance_abstract(self):
202        # abstract instances
203        self.assertEqual(True, isinstance(AbstractSuper(), AbstractSuper))
204        self.assertEqual(False, isinstance(AbstractSuper(), AbstractChild))
205        self.assertEqual(False, isinstance(AbstractSuper(), Super))
206        self.assertEqual(False, isinstance(AbstractSuper(), Child))
207
208        self.assertEqual(True, isinstance(AbstractChild(), AbstractChild))
209        self.assertEqual(True, isinstance(AbstractChild(), AbstractSuper))
210        self.assertEqual(False, isinstance(AbstractChild(), Super))
211        self.assertEqual(False, isinstance(AbstractChild(), Child))
212
213    def test_isinstance_with_or_union(self):
214        self.assertTrue(isinstance(Super(), Super | int))
215        self.assertFalse(isinstance(None, str | int))
216        self.assertTrue(isinstance(3, str | int))
217        self.assertTrue(isinstance("", str | int))
218        self.assertTrue(isinstance([], typing.List | typing.Tuple))
219        self.assertTrue(isinstance(2, typing.List | int))
220        self.assertFalse(isinstance(2, typing.List | typing.Tuple))
221        self.assertTrue(isinstance(None, int | None))
222        self.assertFalse(isinstance(3.14, int | str))
223        with self.assertRaises(TypeError):
224            isinstance(2, list[int])
225        with self.assertRaises(TypeError):
226            isinstance(2, list[int] | int)
227        with self.assertRaises(TypeError):
228            isinstance(2, int | str | list[int] | float)
229
230
231
232    def test_subclass_normal(self):
233        # normal classes
234        self.assertEqual(True, issubclass(Super, Super))
235        self.assertEqual(False, issubclass(Super, AbstractSuper))
236        self.assertEqual(False, issubclass(Super, Child))
237
238        self.assertEqual(True, issubclass(Child, Child))
239        self.assertEqual(True, issubclass(Child, Super))
240        self.assertEqual(False, issubclass(Child, AbstractSuper))
241        self.assertTrue(issubclass(typing.List, typing.List|typing.Tuple))
242        self.assertFalse(issubclass(int, typing.List|typing.Tuple))
243
244    def test_subclass_abstract(self):
245        # abstract classes
246        self.assertEqual(True, issubclass(AbstractSuper, AbstractSuper))
247        self.assertEqual(False, issubclass(AbstractSuper, AbstractChild))
248        self.assertEqual(False, issubclass(AbstractSuper, Child))
249
250        self.assertEqual(True, issubclass(AbstractChild, AbstractChild))
251        self.assertEqual(True, issubclass(AbstractChild, AbstractSuper))
252        self.assertEqual(False, issubclass(AbstractChild, Super))
253        self.assertEqual(False, issubclass(AbstractChild, Child))
254
255    def test_subclass_tuple(self):
256        # test with a tuple as the second argument classes
257        self.assertEqual(True, issubclass(Child, (Child,)))
258        self.assertEqual(True, issubclass(Child, (Super,)))
259        self.assertEqual(False, issubclass(Super, (Child,)))
260        self.assertEqual(True, issubclass(Super, (Child, Super)))
261        self.assertEqual(False, issubclass(Child, ()))
262        self.assertEqual(True, issubclass(Super, (Child, (Super,))))
263
264        self.assertEqual(True, issubclass(int, (int, (float, int))))
265        self.assertEqual(True, issubclass(str, (str, (Child, str))))
266
267    def test_subclass_recursion_limit(self):
268        # make sure that issubclass raises RecursionError before the C stack is
269        # blown
270        with support.infinite_recursion():
271            self.assertRaises(RecursionError, blowstack, issubclass, str, str)
272
273    def test_isinstance_recursion_limit(self):
274        # make sure that issubclass raises RecursionError before the C stack is
275        # blown
276        with support.infinite_recursion():
277            self.assertRaises(RecursionError, blowstack, isinstance, '', str)
278
279    def test_subclass_with_union(self):
280        self.assertTrue(issubclass(int, int | float | int))
281        self.assertTrue(issubclass(str, str | Child | str))
282        self.assertFalse(issubclass(dict, float|str))
283        self.assertFalse(issubclass(object, float|str))
284        with self.assertRaises(TypeError):
285            issubclass(2, Child | Super)
286        with self.assertRaises(TypeError):
287            issubclass(int, list[int] | Child)
288
289    def test_issubclass_refcount_handling(self):
290        # bpo-39382: abstract_issubclass() didn't hold item reference while
291        # peeking in the bases tuple, in the single inheritance case.
292        class A:
293            @property
294            def __bases__(self):
295                return (int, )
296
297        class B:
298            def __init__(self):
299                # setting this here increases the chances of exhibiting the bug,
300                # probably due to memory layout changes.
301                self.x = 1
302
303            @property
304            def __bases__(self):
305                return (A(), )
306
307        self.assertEqual(True, issubclass(B(), int))
308
309    def test_infinite_recursion_in_bases(self):
310        class X:
311            @property
312            def __bases__(self):
313                return self.__bases__
314        with support.infinite_recursion():
315            self.assertRaises(RecursionError, issubclass, X(), int)
316            self.assertRaises(RecursionError, issubclass, int, X())
317            self.assertRaises(RecursionError, isinstance, 1, X())
318
319    def test_infinite_recursion_via_bases_tuple(self):
320        """Regression test for bpo-30570."""
321        class Failure(object):
322            def __getattr__(self, attr):
323                return (self, None)
324        with support.infinite_recursion():
325            with self.assertRaises(RecursionError):
326                issubclass(Failure(), int)
327
328    def test_infinite_cycle_in_bases(self):
329        """Regression test for bpo-30570."""
330        class X:
331            @property
332            def __bases__(self):
333                return (self, self, self)
334        with support.infinite_recursion():
335            self.assertRaises(RecursionError, issubclass, X(), int)
336
337    def test_infinitely_many_bases(self):
338        """Regression test for bpo-30570."""
339        class X:
340            def __getattr__(self, attr):
341                self.assertEqual(attr, "__bases__")
342                class A:
343                    pass
344                class B:
345                    pass
346                A.__getattr__ = B.__getattr__ = X.__getattr__
347                return (A(), B())
348        with support.infinite_recursion():
349            self.assertRaises(RecursionError, issubclass, X(), int)
350
351
352def blowstack(fxn, arg, compare_to):
353    # Make sure that calling isinstance with a deeply nested tuple for its
354    # argument will raise RecursionError eventually.
355    tuple_arg = (compare_to,)
356    for cnt in range(sys.getrecursionlimit()+5):
357        tuple_arg = (tuple_arg,)
358        fxn(arg, tuple_arg)
359
360
361if __name__ == '__main__':
362    unittest.main()
363