Skip to content

Commit b1a574f

Browse files
committed
gh-92810: Add more tests for ABC inheritance & registration
1 parent 0cf31cb commit b1a574f

File tree

2 files changed

+217
-31
lines changed

2 files changed

+217
-31
lines changed

Lib/test/test_abc.py

Lines changed: 195 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ def foo(): return 4
7070

7171

7272
class TestABC(unittest.TestCase):
73+
def check_isinstance(self, obj, target_class):
74+
self.assertIsInstance(obj, target_class)
75+
self.assertIsInstance(obj, (target_class,))
76+
self.assertIsInstance(obj, target_class | target_class)
77+
78+
def check_not_isinstance(self, obj, target_class):
79+
self.assertNotIsInstance(obj, target_class)
80+
self.assertNotIsInstance(obj, (target_class,))
81+
self.assertNotIsInstance(obj, target_class | target_class)
82+
83+
def check_issubclass(self, klass, target_class):
84+
self.assertIsSubclass(klass, target_class)
85+
self.assertIsSubclass(klass, (target_class,))
86+
self.assertIsSubclass(klass, target_class | target_class)
87+
88+
def check_not_issubclass(self, klass, target_class):
89+
self.assertNotIsSubclass(klass, target_class)
90+
self.assertNotIsSubclass(klass, (target_class,))
91+
self.assertNotIsSubclass(klass, target_class | target_class)
7392

7493
def test_ABC_helper(self):
7594
# create an ABC using the helper class and perform basic checks
@@ -270,29 +289,75 @@ def x(self):
270289
class C(metaclass=meta):
271290
pass
272291

292+
def test_isinstance_direct_inheritance(self):
293+
class A(metaclass=abc_ABCMeta):
294+
pass
295+
class B(A):
296+
pass
297+
class C(A):
298+
pass
299+
300+
a = A()
301+
b = B()
302+
c = C()
303+
# trigger caching
304+
for _ in range(2):
305+
self.check_isinstance(a, A)
306+
self.check_not_isinstance(a, B)
307+
self.check_not_isinstance(a, C)
308+
309+
self.check_isinstance(b, B)
310+
self.check_isinstance(b, A)
311+
self.check_not_isinstance(b, C)
312+
313+
self.check_isinstance(c, C)
314+
self.check_isinstance(c, A)
315+
self.check_not_isinstance(c, B)
316+
317+
self.check_issubclass(B, A)
318+
self.check_issubclass(C, A)
319+
self.check_not_issubclass(B, C)
320+
self.check_not_issubclass(C, B)
321+
self.check_not_issubclass(A, B)
322+
self.check_not_issubclass(A, C)
323+
273324
def test_registration_basics(self):
274325
class A(metaclass=abc_ABCMeta):
275326
pass
276327
class B(object):
277328
pass
329+
330+
a = A()
278331
b = B()
279-
self.assertNotIsSubclass(B, A)
280-
self.assertNotIsSubclass(B, (A,))
281-
self.assertNotIsInstance(b, A)
282-
self.assertNotIsInstance(b, (A,))
332+
# trigger caching
333+
for _ in range(2):
334+
self.check_not_issubclass(B, A)
335+
self.check_not_isinstance(b, A)
336+
337+
self.check_not_issubclass(A, B)
338+
self.check_not_isinstance(a, B)
339+
283340
B1 = A.register(B)
284-
self.assertIsSubclass(B, A)
285-
self.assertIsSubclass(B, (A,))
286-
self.assertIsInstance(b, A)
287-
self.assertIsInstance(b, (A,))
288-
self.assertIs(B1, B)
341+
# trigger caching
342+
for _ in range(2):
343+
self.check_issubclass(B, A)
344+
self.check_isinstance(b, A)
345+
self.assertIs(B1, B)
346+
347+
self.check_not_issubclass(A, B)
348+
self.check_not_isinstance(a, B)
349+
289350
class C(B):
290351
pass
352+
291353
c = C()
292-
self.assertIsSubclass(C, A)
293-
self.assertIsSubclass(C, (A,))
294-
self.assertIsInstance(c, A)
295-
self.assertIsInstance(c, (A,))
354+
# trigger caching
355+
for _ in range(2):
356+
self.check_issubclass(C, A)
357+
self.check_isinstance(c, A)
358+
359+
self.check_not_issubclass(A, C)
360+
self.check_not_isinstance(a, C)
296361

297362
def test_register_as_class_deco(self):
298363
class A(metaclass=abc_ABCMeta):
@@ -377,39 +442,95 @@ class A(metaclass=abc_ABCMeta):
377442
pass
378443
self.assertIsSubclass(A, A)
379444
self.assertIsSubclass(A, (A,))
445+
380446
class B(metaclass=abc_ABCMeta):
381447
pass
382448
self.assertNotIsSubclass(A, B)
383449
self.assertNotIsSubclass(A, (B,))
384450
self.assertNotIsSubclass(B, A)
385451
self.assertNotIsSubclass(B, (A,))
452+
386453
class C(metaclass=abc_ABCMeta):
387454
pass
388455
A.register(B)
389456
class B1(B):
390457
pass
391-
self.assertIsSubclass(B1, A)
392-
self.assertIsSubclass(B1, (A,))
458+
# trigger caching
459+
for _ in range(2):
460+
self.assertIsSubclass(B1, A)
461+
self.assertIsSubclass(B1, (A,))
462+
393463
class C1(C):
394464
pass
395465
B1.register(C1)
396-
self.assertNotIsSubclass(C, B)
397-
self.assertNotIsSubclass(C, (B,))
398-
self.assertNotIsSubclass(C, B1)
399-
self.assertNotIsSubclass(C, (B1,))
400-
self.assertIsSubclass(C1, A)
401-
self.assertIsSubclass(C1, (A,))
402-
self.assertIsSubclass(C1, B)
403-
self.assertIsSubclass(C1, (B,))
404-
self.assertIsSubclass(C1, B1)
405-
self.assertIsSubclass(C1, (B1,))
466+
# trigger caching
467+
for _ in range(2):
468+
self.assertNotIsSubclass(C, B)
469+
self.assertNotIsSubclass(C, (B,))
470+
self.assertNotIsSubclass(C, B1)
471+
self.assertNotIsSubclass(C, (B1,))
472+
self.assertIsSubclass(C1, A)
473+
self.assertIsSubclass(C1, (A,))
474+
self.assertIsSubclass(C1, B)
475+
self.assertIsSubclass(C1, (B,))
476+
self.assertIsSubclass(C1, B1)
477+
self.assertIsSubclass(C1, (B1,))
478+
406479
C1.register(int)
407480
class MyInt(int):
408481
pass
409-
self.assertIsSubclass(MyInt, A)
410-
self.assertIsSubclass(MyInt, (A,))
411-
self.assertIsInstance(42, A)
412-
self.assertIsInstance(42, (A,))
482+
# trigger caching
483+
for _ in range(2):
484+
self.assertIsSubclass(MyInt, A)
485+
self.assertIsSubclass(MyInt, (A,))
486+
self.assertIsInstance(42, A)
487+
self.assertIsInstance(42, (A,))
488+
489+
def test_custom_subclasses(self):
490+
class A: pass
491+
class B(A): pass
492+
493+
class C: pass
494+
class D(C): pass
495+
496+
class Root(metaclass=abc_ABCMeta): pass
497+
498+
class Parent1(Root):
499+
@classmethod
500+
def __subclasses__(cls):
501+
return [A]
502+
503+
class Parent2(Root):
504+
__subclasses__ = lambda: [A]
505+
506+
# trigger caching
507+
for _ in range(2):
508+
self.check_isinstance(A(), Parent1)
509+
self.check_isinstance(B(), Parent1)
510+
self.check_issubclass(A, Parent1)
511+
self.check_issubclass(B, Parent1)
512+
self.check_not_isinstance(C(), Parent1)
513+
self.check_not_isinstance(D(), Parent1)
514+
self.check_not_issubclass(C, Parent1)
515+
self.check_not_issubclass(D, Parent1)
516+
517+
self.check_isinstance(A(), Parent2)
518+
self.check_isinstance(B(), Parent2)
519+
self.check_issubclass(A, Parent2)
520+
self.check_issubclass(B, Parent2)
521+
self.check_not_isinstance(C(), Parent2)
522+
self.check_not_isinstance(D(), Parent2)
523+
self.check_not_issubclass(C, Parent2)
524+
self.check_not_issubclass(D, Parent2)
525+
526+
self.check_isinstance(A(), Root)
527+
self.check_isinstance(B(), Root)
528+
self.check_issubclass(A, Root)
529+
self.check_issubclass(B, Root)
530+
self.check_not_isinstance(C(), Root)
531+
self.check_not_isinstance(D(), Root)
532+
self.check_not_issubclass(C, Root)
533+
self.check_not_issubclass(D, Root)
413534

414535
def test_issubclass_bad_arguments(self):
415536
class A(metaclass=abc_ABCMeta):
@@ -460,8 +581,32 @@ class S(metaclass=abc_ABCMeta):
460581
with self.assertRaisesRegex(CustomError, exc_msg):
461582
issubclass(int, S)
462583

463-
def test_subclasshook(self):
584+
def test_issubclass_bad_class(self):
464585
class A(metaclass=abc.ABCMeta):
586+
pass
587+
588+
A._abc_impl = 1
589+
error_msg = "_abc_impl is set to a wrong type"
590+
with self.assertRaisesRegex(TypeError, error_msg):
591+
issubclass(A, A)
592+
593+
class B(metaclass=_py_abc.ABCMeta):
594+
pass
595+
596+
B._abc_cache = 1
597+
error_msg = "argument of type 'int' is not a container or iterable"
598+
with self.assertRaisesRegex(TypeError, error_msg):
599+
issubclass(B, B)
600+
601+
class C(metaclass=_py_abc.ABCMeta):
602+
pass
603+
604+
C._abc_negative_cache = 1
605+
with self.assertRaisesRegex(TypeError, error_msg):
606+
issubclass(C, C)
607+
608+
def test_subclasshook(self):
609+
class A(metaclass=abc_ABCMeta):
465610
@classmethod
466611
def __subclasshook__(cls, C):
467612
if cls is A:
@@ -478,6 +623,26 @@ class C:
478623
self.assertNotIsSubclass(C, A)
479624
self.assertNotIsSubclass(C, (A,))
480625

626+
def test_subclasshook_exception(self):
627+
# Check that issubclass() propagates exceptions raised by
628+
# __subclasshook__.
629+
class CustomError(Exception): ...
630+
exc_msg = "exception from __subclasshook__"
631+
class A(metaclass=abc_ABCMeta):
632+
@classmethod
633+
def __subclasshook__(cls, C):
634+
raise CustomError(exc_msg)
635+
with self.assertRaisesRegex(CustomError, exc_msg):
636+
issubclass(A, A)
637+
class B(A):
638+
pass
639+
with self.assertRaisesRegex(CustomError, exc_msg):
640+
issubclass(B, A)
641+
class C:
642+
pass
643+
with self.assertRaisesRegex(CustomError, exc_msg):
644+
issubclass(C, A)
645+
481646
def test_all_new_methods_are_called(self):
482647
class A(metaclass=abc_ABCMeta):
483648
pass
@@ -522,7 +687,6 @@ def foo(self):
522687
self.assertEqual(A.__abstractmethods__, set())
523688
A()
524689

525-
526690
def test_update_new_abstractmethods(self):
527691
class A(metaclass=abc_ABCMeta):
528692
@abc.abstractmethod

Lib/test/test_isinstance.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,28 @@ class B:
353353
with support.infinite_recursion(25):
354354
self.assertRaises(RecursionError, issubclass, X(), int)
355355

356+
def test_custom_subclasses_are_ignored(self):
357+
class A: pass
358+
class B: pass
359+
360+
class Parent1:
361+
@classmethod
362+
def __subclasses__(cls):
363+
return [A, B]
364+
365+
class Parent2:
366+
__subclasses__ = lambda: [A, B]
367+
368+
self.assertNotIsInstance(A(), Parent1)
369+
self.assertNotIsInstance(B(), Parent1)
370+
self.assertNotIsSubclass(A, Parent1)
371+
self.assertNotIsSubclass(B, Parent1)
372+
373+
self.assertNotIsInstance(A(), Parent2)
374+
self.assertNotIsInstance(B(), Parent2)
375+
self.assertNotIsSubclass(A, Parent2)
376+
self.assertNotIsSubclass(B, Parent2)
377+
356378

357379
def blowstack(fxn, arg, compare_to):
358380
# Make sure that calling isinstance with a deeply nested tuple for its

0 commit comments

Comments
 (0)