Skip to content

Commit 7a3b537

Browse files
committed
[3.11] pythongh-105834: Add tests for calling issubclass() between two protocols (python#105835)
Some parts of the implementation of `typing.Protocol` had poor test coverage
1 parent a9d4f51 commit 7a3b537

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

Lib/test/test_typing.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,6 +2643,104 @@ def x(self): ...
26432643
with self.assertRaises(TypeError):
26442644
issubclass(PG, PG[int])
26452645

2646+
only_classes_allowed = r"issubclass\(\) arg 1 must be a class"
2647+
2648+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2649+
issubclass(1, P)
2650+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2651+
issubclass(1, PG)
2652+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2653+
issubclass(1, BadP)
2654+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2655+
issubclass(1, BadPG)
2656+
2657+
def test_implicit_issubclass_between_two_protocols(self):
2658+
@runtime_checkable
2659+
class CallableMembersProto(Protocol):
2660+
def meth(self): ...
2661+
2662+
# All the below protocols should be considered "subclasses"
2663+
# of CallableMembersProto at runtime,
2664+
# even though none of them explicitly subclass CallableMembersProto
2665+
2666+
class IdenticalProto(Protocol):
2667+
def meth(self): ...
2668+
2669+
class SupersetProto(Protocol):
2670+
def meth(self): ...
2671+
def meth2(self): ...
2672+
2673+
class NonCallableMembersProto(Protocol):
2674+
meth: Callable[[], None]
2675+
2676+
class NonCallableMembersSupersetProto(Protocol):
2677+
meth: Callable[[], None]
2678+
meth2: Callable[[str, int], bool]
2679+
2680+
class MixedMembersProto1(Protocol):
2681+
meth: Callable[[], None]
2682+
def meth2(self): ...
2683+
2684+
class MixedMembersProto2(Protocol):
2685+
def meth(self): ...
2686+
meth2: Callable[[str, int], bool]
2687+
2688+
for proto in (
2689+
IdenticalProto, SupersetProto, NonCallableMembersProto,
2690+
NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2
2691+
):
2692+
with self.subTest(proto=proto.__name__):
2693+
self.assertIsSubclass(proto, CallableMembersProto)
2694+
2695+
# These two shouldn't be considered subclasses of CallableMembersProto, however,
2696+
# since they don't have the `meth` protocol member
2697+
2698+
class EmptyProtocol(Protocol): ...
2699+
class UnrelatedProtocol(Protocol):
2700+
def wut(self): ...
2701+
2702+
self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto)
2703+
self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto)
2704+
2705+
# These aren't protocols at all (despite having annotations),
2706+
# so they should only be considered subclasses of CallableMembersProto
2707+
# if they *actually have an attribute* matching the `meth` member
2708+
# (just having an annotation is insufficient)
2709+
2710+
class AnnotatedButNotAProtocol:
2711+
meth: Callable[[], None]
2712+
2713+
class NotAProtocolButAnImplicitSubclass:
2714+
def meth(self): pass
2715+
2716+
class NotAProtocolButAnImplicitSubclass2:
2717+
meth: Callable[[], None]
2718+
def meth(self): pass
2719+
2720+
class NotAProtocolButAnImplicitSubclass3:
2721+
meth: Callable[[], None]
2722+
meth2: Callable[[int, str], bool]
2723+
def meth(self): pass
2724+
def meth(self, x, y): return True
2725+
2726+
self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto)
2727+
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto)
2728+
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto)
2729+
self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto)
2730+
2731+
def test_isinstance_checks_not_at_whim_of_gc(self):
2732+
self.addCleanup(gc.enable)
2733+
gc.disable()
2734+
2735+
with self.assertRaisesRegex(
2736+
TypeError,
2737+
"Protocols can only inherit from other protocols"
2738+
):
2739+
class Foo(collections.abc.Mapping, Protocol):
2740+
pass
2741+
2742+
self.assertNotIsInstance([], collections.abc.Mapping)
2743+
26462744
def test_protocols_issubclass_non_callable(self):
26472745
class C:
26482746
x = 1

0 commit comments

Comments
 (0)