summaryrefslogtreecommitdiffstats
path: root/Lib/test
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2023-06-16 16:20:19 (GMT)
committerGitHub <noreply@github.com>2023-06-16 16:20:19 (GMT)
commite6982c58602bba9bc1f7c1e6e9cc5f65610d1c6e (patch)
tree670042478d767f0c9d7f574d72a3ff19d9702569 /Lib/test
parent32d8b56dff65f8e1634ebe965357bd60719241b1 (diff)
downloadcpython-e6982c58602bba9bc1f7c1e6e9cc5f65610d1c6e.zip
cpython-e6982c58602bba9bc1f7c1e6e9cc5f65610d1c6e.tar.gz
cpython-e6982c58602bba9bc1f7c1e6e9cc5f65610d1c6e.tar.bz2
[3.12] gh-105834: Add tests for calling `issubclass()` between two protocols (GH-105835) (#105859)
Some parts of the implementation of `typing.Protocol` had poor test coverage (cherry picked from commit 70c075c194d3739ae10ce76265f05fa82ed46487) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Diffstat (limited to 'Lib/test')
-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 7dc64c6..a1fa54a 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()