Commit | Line | Data |
---|---|---|
86530b38 AT |
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 | from test import test_support | |
7 | import sys | |
8 | ||
9 | ||
10 | \f | |
11 | class 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 | \f | |
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(). | |
90 | class 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 | \f | |
137 | # meta classes for creating abstract classes and instances | |
138 | class 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 | ||
149 | class 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 | |
158 | AbstractSuper = AbstractClass(bases=()) | |
159 | ||
160 | AbstractChild = AbstractClass(bases=(AbstractSuper,)) | |
161 | ||
162 | # normal classes | |
163 | class Super: | |
164 | pass | |
165 | ||
166 | class Child(Super): | |
167 | pass | |
168 | ||
169 | # new-style classes | |
170 | class NewSuper(object): | |
171 | pass | |
172 | ||
173 | class NewChild(NewSuper): | |
174 | pass | |
175 | ||
176 | ||
177 | \f | |
178 | class 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 | self.assertEqual(True, issubclass(str, (unicode, (Child, NewChild, basestring)))) | |
247 | ||
248 | def test_subclass_recursion_limit(self): | |
249 | # make sure that issubclass raises RuntimeError before the C stack is | |
250 | # blown | |
251 | self.assertRaises(RuntimeError, blowstack, issubclass, str, str) | |
252 | ||
253 | def test_isinstance_recursion_limit(self): | |
254 | # make sure that issubclass raises RuntimeError before the C stack is | |
255 | # blown | |
256 | self.assertRaises(RuntimeError, blowstack, isinstance, '', str) | |
257 | ||
258 | def blowstack(fxn, arg, compare_to): | |
259 | # Make sure that calling isinstance with a deeply nested tuple for its | |
260 | # argument will raise RuntimeError eventually. | |
261 | tuple_arg = (compare_to,) | |
262 | for cnt in xrange(sys.getrecursionlimit()+5): | |
263 | tuple_arg = (tuple_arg,) | |
264 | fxn(arg, tuple_arg) | |
265 | ||
266 | \f | |
267 | def test_main(): | |
268 | test_support.run_unittest( | |
269 | TestIsInstanceExceptions, | |
270 | TestIsSubclassExceptions, | |
271 | TestIsInstanceIsSubclass | |
272 | ) | |
273 | ||
274 | ||
275 | if __name__ == '__main__': | |
276 | test_main() |