summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2023-06-16 15:47:55 (GMT)
committerGitHub <noreply@github.com>2023-06-16 15:47:55 (GMT)
commit70c075c194d3739ae10ce76265f05fa82ed46487 (patch)
treef57d0c43be3b2624cc4fa45abe5f352ca2f178ec
parent101d5ec7d7fe122fa81a377c8ab8b562d1add9ee (diff)
downloadcpython-70c075c194d3739ae10ce76265f05fa82ed46487.zip
cpython-70c075c194d3739ae10ce76265f05fa82ed46487.tar.gz
cpython-70c075c194d3739ae10ce76265f05fa82ed46487.tar.bz2
gh-105834: Add tests for calling `issubclass()` between two protocols (#105835)
Some parts of the implementation of `typing.Protocol` had poor test coverage
-rw-r--r--Lib/test/test_typing.py74
1 files changed, 74 insertions, 0 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 3eb0fca..ad67568 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -2759,6 +2759,80 @@ class ProtocolTests(BaseTestCase):
with self.assertRaisesRegex(TypeError, only_classes_allowed):
issubclass(1, BadPG)
+ def test_implicit_issubclass_between_two_protocols(self):
+ @runtime_checkable
+ class CallableMembersProto(Protocol):
+ def meth(self): ...
+
+ # All the below protocols should be considered "subclasses"
+ # of CallableMembersProto at runtime,
+ # even though none of them explicitly subclass CallableMembersProto
+
+ class IdenticalProto(Protocol):
+ def meth(self): ...
+
+ class SupersetProto(Protocol):
+ def meth(self): ...
+ def meth2(self): ...
+
+ class NonCallableMembersProto(Protocol):
+ meth: Callable[[], None]
+
+ class NonCallableMembersSupersetProto(Protocol):
+ meth: Callable[[], None]
+ meth2: Callable[[str, int], bool]
+
+ class MixedMembersProto1(Protocol):
+ meth: Callable[[], None]
+ def meth2(self): ...
+
+ class MixedMembersProto2(Protocol):
+ def meth(self): ...
+ meth2: Callable[[str, int], bool]
+
+ for proto in (
+ IdenticalProto, SupersetProto, NonCallableMembersProto,
+ NonCallableMembersSupersetProto, MixedMembersProto1, MixedMembersProto2
+ ):
+ with self.subTest(proto=proto.__name__):
+ self.assertIsSubclass(proto, CallableMembersProto)
+
+ # These two shouldn't be considered subclasses of CallableMembersProto, however,
+ # since they don't have the `meth` protocol member
+
+ class EmptyProtocol(Protocol): ...
+ class UnrelatedProtocol(Protocol):
+ def wut(self): ...
+
+ self.assertNotIsSubclass(EmptyProtocol, CallableMembersProto)
+ self.assertNotIsSubclass(UnrelatedProtocol, CallableMembersProto)
+
+ # These aren't protocols at all (despite having annotations),
+ # so they should only be considered subclasses of CallableMembersProto
+ # if they *actually have an attribute* matching the `meth` member
+ # (just having an annotation is insufficient)
+
+ class AnnotatedButNotAProtocol:
+ meth: Callable[[], None]
+
+ class NotAProtocolButAnImplicitSubclass:
+ def meth(self): pass
+
+ class NotAProtocolButAnImplicitSubclass2:
+ meth: Callable[[], None]
+ def meth(self): pass
+
+ class NotAProtocolButAnImplicitSubclass3:
+ meth: Callable[[], None]
+ meth2: Callable[[int, str], bool]
+ def meth(self): pass
+ def meth(self, x, y): return True
+
+ self.assertNotIsSubclass(AnnotatedButNotAProtocol, CallableMembersProto)
+ self.assertIsSubclass(NotAProtocolButAnImplicitSubclass, CallableMembersProto)
+ self.assertIsSubclass(NotAProtocolButAnImplicitSubclass2, CallableMembersProto)
+ self.assertIsSubclass(NotAProtocolButAnImplicitSubclass3, CallableMembersProto)
+
def test_isinstance_checks_not_at_whim_of_gc(self):
self.addCleanup(gc.enable)
gc.disable()